From 83c669eda8fe032f1d9f5733b99bd69a0767ab2d Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Mon, 28 Aug 2023 12:11:55 -0500 Subject: [PATCH] feat(spike): taker fee (#6034) * add taker fee determination and extraction * remove osmo multi hop logic * route to both community pool and staking rewards * fix a handfull of tests * assign taker fee to pool at time of creation * pull taker fee direct from pool struct * fix more tests * fix tests, set back up osmo multi hop discount * correct taker fee extraction * regen mocks * abstract taker fee to pool manager (highest lvl) * fix extract cmd * update changelog * tidy * remove param that no longer exists * add extra params to genesis logic * add back osmo multihop tests * add comment * fix e2e keeping prints * fixes * more test fixes * remove prints * more naive approach to determining taker fee * fix e2e * re-enable disabled test * minor cleaning * simplify params * not nullable * clean up * add whitelist set message denom pair taker fees * use real addresses * add gov prop for denom pair taker fee update * clean * move logic to its own taker_fee.go file * add CLI for gov prop for denomPairTakerFee * add admin address denomPairTakerFee cli msg * clean and simplifications * use authorized quote denoms from poolmanger * remove stableswap taker fee * sim msg change * add test for CLI * msg server tests * add route_test test * add gov_test.go * add msgs_test.go * remove print line * change from v18 to v19 * Update upgrades.go * set default taker fee to zero in upgrade handler * conflicts * Update proto/osmosis/poolmanager/v1beta1/genesis.proto * Update proto/osmosis/poolmanager/v1beta1/genesis.proto * Update x/poolmanager/taker_fee.go * Generated protofile changes * rename extractTakerFeeAndDistribute to chargeTakerFee * clean up * comment * rename * godoc for NonNativeFeeCollectorForCommunityPoolName * baseDenom to defaultFeesDenom name change * fix upgrade handler * fix AfterEpochEnd order of denoms when getting pools; reduce code dupl; tests * fix track volume * add key separator for pool manager * add comment to denom pair route * add basic lexicographical key test * update chargeTakerFee godoc * set the default taker fee back to non zero for e2e * change PoolMangerGetParams API to GetAuthorizedQuoteDenoms (same for setters) * update txfees & poolmanager READMEs with takerFee * fix merge main * add comment to denom pair taker fee * de-dup taker fee param validation * Update x/poolmanager/types/msgs.go * Apply suggestions from code review * Update x/poolmanager/taker_fee.go * remove unneeded setup test * get pool creation free from previous * take non native out of name * undo param pull instead of default * use current pool creation fee * use default * e2e --------- Co-authored-by: devbot-wizard <141283918+devbot-wizard@users.noreply.github.com> Co-authored-by: Roman Co-authored-by: github-actions Co-authored-by: alpo Co-authored-by: Dev Ojha Co-authored-by: Dev Ojha (cherry picked from commit 5c8fd80f54e4724c2c9e4867f0db239a58a17d91) # Conflicts: # CHANGELOG.md # tests/e2e/e2e_test.go # x/poolmanager/export_test.go # x/poolmanager/router.go # x/poolmanager/types/keys.go --- .github/workflows/test.yml | 4 +- CHANGELOG.md | 16 + app/apptesting/concentrated_liquidity.go | 6 +- app/modules.go | 50 +- app/upgrades/v15/upgrades.go | 3 +- app/upgrades/v19/upgrades.go | 20 +- .../osmosis/poolmanager/v1beta1/genesis.proto | 76 ++ proto/osmosis/poolmanager/v1beta1/tx.proto | 4 +- tests/cl-genesis-positions/go.mod | 47 +- tests/cl-genesis-positions/go.sum | 80 +- tests/e2e/e2e_test.go | 119 ++- tests/e2e/helpers_e2e_test.go | 5 +- x/concentrated-liquidity/keeper.go | 16 + x/concentrated-liquidity/pool.go | 5 +- x/concentrated-liquidity/pool_test.go | 4 +- .../simulation/sim_msgs.go | 9 +- .../types/expected_keepers.go | 2 + x/gamm/keeper/msg_server_test.go | 19 +- x/gamm/keeper/pool.go | 6 + x/gamm/simulation/sim_msgs.go | 23 +- x/gamm/types/expected_keepers.go | 2 + x/poolmanager/README.md | 10 + x/poolmanager/client/cli/tx.go | 173 ++++ x/poolmanager/client/cli/tx_test.go | 51 ++ x/poolmanager/create_pool.go | 6 +- x/poolmanager/create_pool_test.go | 6 +- x/poolmanager/export_test.go | 15 + x/poolmanager/gov.go | 6 +- x/poolmanager/keeper_test.go | 48 +- x/poolmanager/msg_server.go | 21 +- x/poolmanager/msg_server_test.go | 93 +- x/poolmanager/router.go | 230 ++++- x/poolmanager/router_test.go | 87 +- x/poolmanager/taker_fee.go | 185 ++++ x/poolmanager/types/events.go | 3 + x/poolmanager/types/genesis.pb.go | 815 +++++++++++++++++- x/poolmanager/types/gov.go | 8 +- x/poolmanager/types/gov_test.go | 146 ++++ x/poolmanager/types/keys.go | 18 + x/poolmanager/types/keys_test.go | 36 + x/poolmanager/types/msgs.go | 10 +- x/poolmanager/types/msgs_test.go | 86 ++ x/poolmanager/types/params.go | 185 +++- x/poolmanager/types/tx.pb.go | 2 + x/protorev/keeper/keeper_test.go | 4 + x/protorev/keeper/rebalance_test.go | 1 - x/superfluid/keeper/stake_test.go | 4 +- x/twap/keeper_test.go | 6 +- x/twap/store.go | 1 + x/txfees/README.md | 16 + x/txfees/keeper/export_test.go | 7 + x/txfees/keeper/feedecorator.go | 8 +- x/txfees/keeper/feedecorator_test.go | 2 +- x/txfees/keeper/hooks.go | 90 +- x/txfees/keeper/hooks_test.go | 74 +- x/txfees/keeper/keeper_test.go | 16 + x/txfees/types/expected_keepers.go | 16 +- x/txfees/types/keys.go | 8 +- 58 files changed, 2745 insertions(+), 264 deletions(-) create mode 100644 x/poolmanager/taker_fee.go create mode 100644 x/poolmanager/types/gov_test.go create mode 100644 x/poolmanager/types/keys_test.go create mode 100644 x/txfees/keeper/export_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77e1ac8fd35..3b823c4db0d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,8 +100,8 @@ jobs: e2e: needs: get_diff if: needs.get_diff.outputs.git_diff - runs-on: self-hosted - timeout-minutes: 25 + runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Clean up Pre-Existing E2E Docker containers run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d8621211d..312b9ff56f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## v18.0.0 +<<<<<<< HEAD +======= +### Misc Improvements + +* [#6161](https://github.com/osmosis-labs/osmosis/pull/6161) Reduce CPU time of epochs + +### Bug Fixes + +* [#6162](https://github.com/osmosis-labs/osmosis/pull/6162) allow zero qualifying balancer shares in CL incentives + +### Features + +* [#6034](https://github.com/osmosis-labs/osmosis/pull/6034) feat(spike): taker fee +## v18.0.0 + +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) Fixes mainnet bugs w/ incorrect accumulation sumtrees, and CL handling for a balancer pool with 0 bonded shares. ### Improvements diff --git a/app/apptesting/concentrated_liquidity.go b/app/apptesting/concentrated_liquidity.go index a75dfde46df..0f485c18e04 100644 --- a/app/apptesting/concentrated_liquidity.go +++ b/app/apptesting/concentrated_liquidity.go @@ -7,6 +7,7 @@ import ( clmodel "github.com/osmosis-labs/osmosis/v19/x/concentrated-liquidity/model" "github.com/osmosis-labs/osmosis/v19/x/concentrated-liquidity/types" + poolmanagertypes "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" cl "github.com/osmosis-labs/osmosis/v19/x/concentrated-liquidity" ) @@ -139,6 +140,9 @@ func (s *KeeperTestHelper) SetupConcentratedLiquidityDenomsAndPoolCreation() { // modify authorized quote denoms to include test denoms. defaultParams := types.DefaultParams() defaultParams.IsPermissionlessPoolCreationEnabled = true - defaultParams.AuthorizedQuoteDenoms = append(defaultParams.AuthorizedQuoteDenoms, ETH, USDC, BAR, BAZ, FOO, UOSMO, STAKE, WBTC) s.App.ConcentratedLiquidityKeeper.SetParams(s.Ctx, defaultParams) + + poolManagerParams := s.App.PoolManagerKeeper.GetParams(s.Ctx) + poolManagerParams.AuthorizedQuoteDenoms = append(poolmanagertypes.DefaultParams().AuthorizedQuoteDenoms, ETH, USDC, BAR, BAZ, FOO, UOSMO, STAKE, WBTC) + s.App.PoolManagerKeeper.SetParams(s.Ctx, poolManagerParams) } diff --git a/app/modules.go b/app/modules.go index 48c16f659b0..cfcbf7bca69 100644 --- a/app/modules.go +++ b/app/modules.go @@ -98,31 +98,31 @@ import ( // moduleAccountPermissions defines module account permissions // TODO: Having to input nil's here is unacceptable, we need a way to automatically derive this. var moduleAccountPermissions = map[string][]string{ - authtypes.FeeCollectorName: nil, - distrtypes.ModuleName: nil, - ibchookstypes.ModuleName: nil, - icatypes.ModuleName: nil, - icqtypes.ModuleName: nil, - minttypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - minttypes.DeveloperVestingModuleAcctName: nil, - stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, - stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, - govtypes.ModuleName: {authtypes.Burner}, - ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - gammtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - incentivestypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - protorevtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - lockuptypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - poolincentivestypes.ModuleName: nil, - superfluidtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - txfeestypes.ModuleName: nil, - txfeestypes.NonNativeFeeCollectorForStakingRewardsName: nil, - txfeestypes.NonNativeFeeCollectorForCommunityPoolName: nil, - wasm.ModuleName: {authtypes.Burner}, - tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - valsetpreftypes.ModuleName: {authtypes.Staking}, - poolmanagertypes.ModuleName: nil, - cosmwasmpooltypes.ModuleName: nil, + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + ibchookstypes.ModuleName: nil, + icatypes.ModuleName: nil, + icqtypes.ModuleName: nil, + minttypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + minttypes.DeveloperVestingModuleAcctName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + govtypes.ModuleName: {authtypes.Burner}, + ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + gammtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + incentivestypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + protorevtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + lockuptypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + poolincentivestypes.ModuleName: nil, + superfluidtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + txfeestypes.ModuleName: nil, + txfeestypes.FeeCollectorForStakingRewardsName: nil, + txfeestypes.FeeCollectorForCommunityPoolName: nil, + wasm.ModuleName: {authtypes.Burner}, + tokenfactorytypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + valsetpreftypes.ModuleName: {authtypes.Staking}, + poolmanagertypes.ModuleName: nil, + cosmwasmpooltypes.ModuleName: nil, } // appModules return modules to initialize module manager. diff --git a/app/upgrades/v15/upgrades.go b/app/upgrades/v15/upgrades.go index 27ba00a4b44..7778ee1d165 100644 --- a/app/upgrades/v15/upgrades.go +++ b/app/upgrades/v15/upgrades.go @@ -37,7 +37,8 @@ func CreateUpgradeHandler( keepers *keepers.AppKeepers, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { - poolmanagerParams := poolmanagertypes.NewParams(keepers.GAMMKeeper.GetParams(ctx).PoolCreationFee) + poolmanagerParams := keepers.PoolManagerKeeper.GetParams(ctx) + poolmanagerParams.PoolCreationFee = keepers.GAMMKeeper.GetParams(ctx).PoolCreationFee keepers.PoolManagerKeeper.SetParams(ctx, poolmanagerParams) keepers.PacketForwardKeeper.SetParams(ctx, packetforwardtypes.DefaultParams()) diff --git a/app/upgrades/v19/upgrades.go b/app/upgrades/v19/upgrades.go index 09ac1d3c3e8..33dfab20993 100644 --- a/app/upgrades/v19/upgrades.go +++ b/app/upgrades/v19/upgrades.go @@ -9,10 +9,11 @@ import ( "github.com/osmosis-labs/osmosis/v19/app/keepers" "github.com/osmosis-labs/osmosis/v19/app/upgrades" + v18 "github.com/osmosis-labs/osmosis/v19/app/upgrades/v18" + poolmanagertypes "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" ) -// OSMO / DAI CL pool ID -const lastPoolToCorrect = 1066 +const lastPoolToCorrect = v18.FirstCLPoolId - 1 func CreateUpgradeHandler( mm *module.Manager, @@ -28,9 +29,22 @@ func CreateUpgradeHandler( return nil, err } - for id := 1; id < lastPoolToCorrect; id++ { + for id := 1; id <= lastPoolToCorrect; id++ { resetSuperfluidSumtree(keepers, ctx, uint64(id)) } + + // Move the current authorized quote denoms from the concentrated liquidity params to the pool manager params. + // This needs to be moved because the pool manager requires access to these denoms to determine if the taker fee should + // be swapped into OSMO or not. The concentrated liquidity module already requires access to the pool manager keeper, + // so the right move in this case is to move this parameter upwards in order to prevent circular dependencies. + // TODO: In v20 upgrade handler, delete this param from the concentrated liquidity params. + currentConcentratedLiquidityParams := keepers.ConcentratedLiquidityKeeper.GetParams(ctx) + defaultPoolManagerParams := poolmanagertypes.DefaultParams() + + defaultPoolManagerParams.AuthorizedQuoteDenoms = currentConcentratedLiquidityParams.AuthorizedQuoteDenoms + defaultPoolManagerParams.TakerFeeParams.DefaultTakerFee = sdk.ZeroDec() + keepers.PoolManagerKeeper.SetParams(ctx, defaultPoolManagerParams) + return migrations, nil } } diff --git a/proto/osmosis/poolmanager/v1beta1/genesis.proto b/proto/osmosis/poolmanager/v1beta1/genesis.proto index bd10936c8b3..398d6967cbb 100644 --- a/proto/osmosis/poolmanager/v1beta1/genesis.proto +++ b/proto/osmosis/poolmanager/v1beta1/genesis.proto @@ -17,6 +17,21 @@ message Params { (gogoproto.moretags) = "yaml:\"pool_creation_fee\"", (gogoproto.nullable) = false ]; + // taker_fee_params is the container of taker fee parameters. + TakerFeeParams taker_fee_params = 2 [ + (gogoproto.moretags) = "yaml:\"taker_fee_params\"", + (gogoproto.nullable) = false + ]; + // authorized_quote_denoms is a list of quote denoms that can be used as + // token1 when creating a concentrated pool. We limit the quote assets to a + // small set for the purposes of having convinient price increments stemming + // from tick to price conversion. These increments are in a human readable + // magnitude only for token1 as a quote. For limit orders in the future, this + // will be a desirable property in terms of UX as to allow users to set limit + // orders at prices in terms of token1 (quote asset) that are easy to reason + // about. + repeated string authorized_quote_denoms = 3 + [ (gogoproto.moretags) = "yaml:\"authorized_quote_denoms\"" ]; } // GenesisState defines the poolmanager module's genesis state. @@ -28,3 +43,64 @@ message GenesisState { // pool_routes is the container of the mappings from pool id to pool type. repeated ModuleRoute pool_routes = 3 [ (gogoproto.nullable) = false ]; } + +// TakerFeeParams consolidates the taker fee parameters for the poolmanager. +message TakerFeeParams { + // default_taker_fee is the fee used when creating a new pool that doesn't + // fall under a custom pool taker fee or stableswap taker fee category. + string default_taker_fee = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.customname) = "DefaultTakerFee", + (gogoproto.nullable) = false + ]; + // osmo_taker_fee_distribution defines the distribution of taker fees + // generated in OSMO. As of this writing, it has two catagories: + // - staking_rewards: the percent of the taker fee that gets distributed to + // stakers. + // - community_pool: the percent of the taker fee that gets sent to the + // community pool. + TakerFeeDistributionPercentage osmo_taker_fee_distribution = 2 [ + (gogoproto.customname) = "OsmoTakerFeeDistribution", + (gogoproto.nullable) = false + ]; + // non_osmo_taker_fee_distribution defines the distribution of taker fees + // generated in non-OSMO. As of this writing, it has two categories: + // - staking_rewards: the percent of the taker fee that gets swapped to OSMO + // and then distirbuted to stakers. + // - community_pool: the percent of the taker fee that gets sent to the + // community pool. Note: If the non-OSMO asset is an authorized_quote_denom, + // that denom is sent directly to the community pool. Otherwise, it is + // swapped to the community_pool_denom_to_swap_non_whitelisted_assets_to and + // then sent to the community pool as that denom. + TakerFeeDistributionPercentage non_osmo_taker_fee_distribution = 3 [ + (gogoproto.customname) = "NonOsmoTakerFeeDistribution", + (gogoproto.nullable) = false + ]; + // admin_addresses is a list of addresses that are allowed to set and remove + // custom taker fees for denom pairs. Governance also has the ability to set + // and remove custom taker fees for denom pairs, but with the normal + // governance delay. + repeated string admin_addresses = 4 + [ (gogoproto.moretags) = "yaml:\"admin_addresses\"" ]; + // community_pool_denom_to_swap_non_whitelisted_assets_to is the denom that + // non-whitelisted taker fees will be swapped to before being sent to + // the community pool. + string community_pool_denom_to_swap_non_whitelisted_assets_to = 5 + [ (gogoproto.moretags) = + "yaml:\"community_pool_denom_to_swap_non_whitelisted_assets_to\"" ]; +} + +// TakerFeeDistributionPercentage defines what percent of the taker fee category +// gets distributed to the available categories. +message TakerFeeDistributionPercentage { + string staking_rewards = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.moretags) = "yaml:\"staking_rewards\"", + (gogoproto.nullable) = false + ]; + string community_pool = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.moretags) = "yaml:\"community_pool\"", + (gogoproto.nullable) = false + ]; +} diff --git a/proto/osmosis/poolmanager/v1beta1/tx.proto b/proto/osmosis/poolmanager/v1beta1/tx.proto index 1798be6312d..934e2afdbc0 100644 --- a/proto/osmosis/poolmanager/v1beta1/tx.proto +++ b/proto/osmosis/poolmanager/v1beta1/tx.proto @@ -131,6 +131,8 @@ message MsgSetDenomPairTakerFee { message MsgSetDenomPairTakerFeeResponse { bool success = 1; } message DenomPairTakerFee { + // denom0 and denom1 get automatically lexigographically sorted + // when being stored, so the order of input here does not matter. string denom0 = 1 [ (gogoproto.moretags) = "yaml:\"denom0\"" ]; string denom1 = 2 [ (gogoproto.moretags) = "yaml:\"denom1\"" ]; string taker_fee = 3 [ @@ -138,4 +140,4 @@ message DenomPairTakerFee { (gogoproto.moretags) = "yaml:\"taker_fee\"", (gogoproto.nullable) = false ]; -} \ No newline at end of file +} diff --git a/tests/cl-genesis-positions/go.mod b/tests/cl-genesis-positions/go.mod index df094db4eea..a5ebd81a35f 100644 --- a/tests/cl-genesis-positions/go.mod +++ b/tests/cl-genesis-positions/go.mod @@ -3,17 +3,17 @@ module cl-get-positions-subgraph go 1.20 require ( - github.com/cosmos/cosmos-sdk v0.47.3 + github.com/cosmos/cosmos-sdk v0.47.4 github.com/ignite/cli v0.23.0 - github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230621002052-afb82fbaa312 - github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230623115558-38aaab07d343 + github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230804142026-a81cfe3ddde7 + github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230807183608-16c217dedde5 github.com/tendermint/tendermint v0.37.0-rc1 ) -require github.com/osmosis-labs/osmosis/v16 v16.0.0-20230630175215-d5fcd089a71c +require github.com/osmosis-labs/osmosis/v17 v17.0.0-20230811221421-2e3f19d86dd4 require ( - cosmossdk.io/errors v1.0.0-beta.7 // indirect + cosmossdk.io/errors v1.0.0 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/99designs/keyring v1.2.1 // indirect @@ -26,11 +26,11 @@ require ( github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/btcsuite/btcd v0.22.3 // indirect + github.com/bytedance/sonic v1.9.1 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/coinbase/rosetta-sdk-go v0.7.9 // indirect github.com/confio/ics23/go v0.9.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect @@ -39,7 +39,8 @@ require ( github.com/cosmos/gogoproto v1.4.6 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/iavl v0.19.5 // indirect - github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230524151648-c02fa46c2860 // indirect + github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v4 v4.0.0-20230803185752-97c9635dd74a // indirect + github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230803151532-5c60ac789ef7 // indirect github.com/cosmos/ibc-go/v4 v4.3.1 // indirect github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect github.com/creachadair/taskgroup v0.3.2 // indirect @@ -53,15 +54,17 @@ require ( github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/gogo/gateway v1.1.0 // indirect github.com/gogo/protobuf v1.3.3 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -82,12 +85,12 @@ require ( github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect - github.com/iancoleman/orderedmap v0.2.0 // indirect + github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/klauspost/compress v1.15.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -100,7 +103,7 @@ require ( github.com/mtibben/percent v0.2.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/osmosis-labs/osmosis/x/epochs v0.0.0-20230328024000-175ec88e4304 // indirect - github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230602130523-f9a94d8bbd10 // indirect + github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230807183608-16c217dedde5 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -121,7 +124,6 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.16.0 // indirect - github.com/strangelove-ventures/packet-forward-middleware/v4 v4.0.5 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect @@ -130,23 +132,24 @@ require ( github.com/tendermint/go-amino v0.16.0 // indirect github.com/tendermint/tm-db v0.6.8-0.20220506192307-f628bb5dc95b // indirect github.com/tidwall/btree v1.6.0 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect github.com/zondax/hid v0.9.1 // indirect github.com/zondax/ledger-go v0.14.1 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230131160201-f062dba9d201 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/sync v0.2.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.55.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect + google.golang.org/grpc v1.56.2 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tests/cl-genesis-positions/go.sum b/tests/cl-genesis-positions/go.sum index 19b91e77932..91db2de6be9 100644 --- a/tests/cl-genesis-positions/go.sum +++ b/tests/cl-genesis-positions/go.sum @@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w= -cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= +cosmossdk.io/errors v1.0.0 h1:nxF07lmlBbB8NKQhtJ+sJm6ef5uV1XkvPXG2bUntb04= +cosmossdk.io/errors v1.0.0/go.mod h1:+hJZLuhdDE0pYN8HkOrVNwrIOYvUGnn6+4fjnJs/oV0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= @@ -148,6 +148,7 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f 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= @@ -204,8 +205,10 @@ github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4 github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.19.4 h1:t82sN+Y0WeqxDLJRSpNd8YFX5URIrT+p8n6oJbJ2Dok= github.com/cosmos/iavl v0.19.4/go.mod h1:X9PKD3J0iFxdmgNLa7b2LYWdsGd90ToV5cAONApkEPw= -github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230524151648-c02fa46c2860 h1:25/KpA4WJqdFjKFsa3VEL0ctWRovkEsqIn2phCAi9v0= -github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230524151648-c02fa46c2860/go.mod h1:X/dLZ6QxTImzno7qvD6huLhh6ZZBcRt2URn4YCLcXFY= +github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v4 v4.0.0-20230803185752-97c9635dd74a h1:FKEtbHE+kULYf6ghxa3FvEfyR7BNde0940gAyuWborE= +github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v4 v4.0.0-20230803185752-97c9635dd74a/go.mod h1:Mn/jr9pIYr1ofFuptLEi9N6MjcshTT0cpoOY4ln1DeA= +github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230803151532-5c60ac789ef7 h1:q6OzbGXmWRbMedDejhuctDbb3M6fWyJg3DY3bzJbYVs= +github.com/cosmos/ibc-apps/modules/async-icq/v4 v4.0.0-20230803151532-5c60ac789ef7/go.mod h1:X/dLZ6QxTImzno7qvD6huLhh6ZZBcRt2URn4YCLcXFY= github.com/cosmos/ibc-go/v4 v4.3.1 h1:xbg0CaCdxK3lvgGvSaI91ROOLd7s30UqEcexH6Ba4Ys= github.com/cosmos/ibc-go/v4 v4.3.1/go.mod h1:89E+K9CxpkS/etLEcG026jPM/RSnVMcfesvRYp/0aKI= github.com/cosmos/interchain-accounts v0.2.6 h1:TV2M2g1/Rb9MCNw1YePdBKE0rcEczNj1RGHT+2iRYas= @@ -291,6 +294,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -326,9 +330,9 @@ github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTM github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -340,6 +344,7 @@ 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= @@ -350,8 +355,8 @@ github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw= +github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -505,8 +510,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.3-0.20220313090229-ca81a64b4204/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= -github.com/iancoleman/orderedmap v0.2.0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= @@ -565,10 +570,10 @@ 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/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 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= @@ -588,6 +593,7 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -705,16 +711,16 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/osmosis-labs/cosmos-sdk v0.45.0-rc1.0.20230703010110-ed4eb883f2a6 h1:oUhTd/4OcubK8u8GN36GdppGYGglCOLGUy5FiFsEk8M= github.com/osmosis-labs/cosmos-sdk v0.45.0-rc1.0.20230703010110-ed4eb883f2a6/go.mod h1:9KGhMg+7ZWgZ50Wa/x8w/jN19O0TSqYLlqUj+2wwxLU= -github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230621002052-afb82fbaa312 h1:7Y/948riUlpIfFSAUIx0rmRy3B+Mdk6alHwOa8lTNXY= -github.com/osmosis-labs/osmosis/osmomath v0.0.3-dev.0.20230621002052-afb82fbaa312/go.mod h1:JTym95/bqrSnG5MPcXr1YDhv43JdCeo3p+iDbazoX68= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230623115558-38aaab07d343 h1:7V2b3+mSnLnK0Px+Dl3vnxAQgk4SV8e9ohfJ/tKsq0M= -github.com/osmosis-labs/osmosis/osmoutils v0.0.0-20230623115558-38aaab07d343/go.mod h1:FqFOfj9+e5S1I7hR3baGUHrqje3g32EOHAXoOf7R00M= -github.com/osmosis-labs/osmosis/v16 v16.0.0-20230630175215-d5fcd089a71c h1:ZtqSK9+wQ3qNLlgCFyXn4ife04LXYQz9vfZ4j/agRcQ= -github.com/osmosis-labs/osmosis/v16 v16.0.0-20230630175215-d5fcd089a71c/go.mod h1:bkS/L9o/y1HSMViWzByzCNZtlulks9dpchOXFWM28g4= +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-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/v17 v17.0.0-20230811221421-2e3f19d86dd4 h1:EMbnmbZSJ1znKoN8Z4bwQxDN/Iq/n14KlH2QjzqeMSg= +github.com/osmosis-labs/osmosis/v17 v17.0.0-20230811221421-2e3f19d86dd4/go.mod h1:lw/ztTxBFpFj8ZuJrCYRL+b7LPy13A/tDflPVYCM0xM= 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.0-20230602130523-f9a94d8bbd10 h1:XrES5AHZMZ/Y78boW35PTignkhN9h8VvJ1sP8EJDIu8= -github.com/osmosis-labs/osmosis/x/ibc-hooks v0.0.0-20230602130523-f9a94d8bbd10/go.mod h1:Ln6CKcXg/CJLSBE6Fd96/MIKPyA4iHuQTKSbl9q7vYo= +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.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= @@ -851,8 +857,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= -github.com/strangelove-ventures/packet-forward-middleware/v4 v4.0.5 h1:KKUqeGhVBK38+1LwThC8IeIcsJZ6COX5kvhiJroFqCM= -github.com/strangelove-ventures/packet-forward-middleware/v4 v4.0.5/go.mod h1:4zAtg449/JISRmf+sbmqolqSLP+QJBh+EtWkWtt/AKE= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -871,6 +875,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -907,11 +912,11 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS 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 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= @@ -982,8 +987,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1077,8 +1082,8 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1178,14 +1183,13 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1195,8 +1199,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1342,8 +1346,12 @@ google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e h1:xIXmWJ303kJCuogpj0bHq+dcjcZHU+XFyc1I0Yl9cRg= +google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:0ggbjUrZYpy1q+ANUS30SEoGZ53cdfwtbuG7Ptgy108= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU= +google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 h1:2FZP5XuJY9zQyGM5N0rtovnoXjiMUEIUMvw0m9wlpLc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:8mL13HKkDa+IuJ8yruA3ci0q+0vsUz4m//+ottjwS5o= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1358,8 +1366,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 9b174f9b8e0..20b253ebf9a 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -44,6 +44,12 @@ var ( // TODO: Find more scalable way to do this func (s *IntegrationTestSuite) TestAllE2E() { + // Reset the default taker fee to 0.15%, so we can actually run tests with it activated + s.T().Run("SetDefaultTakerFeeBothChains", func(t *testing.T) { + s.T().Log("resetting the default taker fee to 0.15%") + s.SetDefaultTakerFeeBothChains() + }) + // Zero Dependent Tests s.T().Run("CreateConcentratedLiquidityPoolVoting_And_TWAP", func(t *testing.T) { t.Parallel() @@ -311,6 +317,7 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { tickSpacing uint64 = 100 spreadFactor = "0.001" // 0.1% spreadFactorDec = sdk.MustNewDecFromStr("0.001") + takerFee = sdk.MustNewDecFromStr("0.0015") ) chainAB, chainABNode, err := s.getChainCfgs() @@ -326,6 +333,17 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { err = chainABNode.ParamChangeProposal("concentratedliquidity", string(cltypes.KeyIsPermisionlessPoolCreationEnabled), []byte("true"), chainAB) s.Require().NoError(err) +<<<<<<< HEAD +======= + // Update the protorev admin address to a known wallet we can control + adminWalletAddr := chainABNode.CreateWalletAndFund("admin", []string{"4000000uosmo"}, chainAB) + err = chainABNode.ParamChangeProposal("protorev", string(protorevtypes.ParamStoreKeyAdminAccount), []byte(fmt.Sprintf(`"%s"`, adminWalletAddr)), chainAB) + s.Require().NoError(err) + + // Update the weight of CL pools so that this test case is not back run by protorev. + chainABNode.SetMaxPoolPointsPerTx(7, adminWalletAddr) + +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) // Confirm that the parameter has been changed. isPermisionlessCreationEnabledStr = chainABNode.QueryParams(cltypes.ModuleName, string(cltypes.KeyIsPermisionlessPoolCreationEnabled)) if !strings.EqualFold(isPermisionlessCreationEnabledStr, "true") { @@ -357,16 +375,25 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { address2 := chainABNode.CreateWalletAndFund("addr2", fundTokens, chainAB) address3 := chainABNode.CreateWalletAndFund("addr3", fundTokens, chainAB) + // When claiming rewards, a small portion of dust is forfeited and is redistributed to everyone. We must track the total + // liquidity across all positions (even if not active), in order to calculate how much to increase the reward growth global per share by. + totalLiquidity := sdk.ZeroDec() + // Create 2 positions for address1: overlap together, overlap with 2 address3 positions - 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) + _, liquidity := chainABNode.CreateConcentratedPosition(address1, "[-120000]", "40000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + totalLiquidity = totalLiquidity.Add(liquidity) + _, liquidity = chainABNode.CreateConcentratedPosition(address1, "[-40000]", "120000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + totalLiquidity = totalLiquidity.Add(liquidity) // Create 1 position for address2: does not overlap with anything, ends at maximum - chainABNode.CreateConcentratedPosition(address2, "220000", fmt.Sprintf("%d", cltypes.MaxTick), fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + _, liquidity = chainABNode.CreateConcentratedPosition(address2, "220000", fmt.Sprintf("%d", cltypes.MaxTick), fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + totalLiquidity = totalLiquidity.Add(liquidity) // Create 2 positions for address3: overlap together, overlap with 2 address1 positions, one position starts from minimum - 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) + _, liquidity = chainABNode.CreateConcentratedPosition(address3, "[-160000]", "[-20000]", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + totalLiquidity = totalLiquidity.Add(liquidity) + _, liquidity = chainABNode.CreateConcentratedPosition(address3, fmt.Sprintf("[%d]", cltypes.MinInitializedTick), "140000", fmt.Sprintf("10000000%s,10000000%s", denom0, denom1), 0, 0, poolID) + totalLiquidity = totalLiquidity.Add(liquidity) // Get newly created positions positionsAddress1 := chainABNode.QueryConcentratedPositions(address1) @@ -421,7 +448,11 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // Perform swap (not crossing initialized ticks) 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())) + uosmoInDec_Swap1_SubTakerFee := uosmoInDec_Swap1.SDKDec().Mul(sdk.OneDec().Sub(takerFee)).TruncateDec() + uosmoInDec_Swap1_SubTakerFee_SubSpreadFactor := uosmoInDec_Swap1_SubTakerFee.Mul(sdk.OneDec().Sub(spreadFactorDec)) + totalSpreadReward := uosmoInDec_Swap1_SubTakerFee.Sub(uosmoInDec_Swap1_SubTakerFee_SubSpreadFactor).TruncateDec() + + spreadRewardGrowthGlobal.AddMut(calculateSpreadRewardGrowthGlobal(totalSpreadReward, concentratedPool.GetLiquidity())) // Update pool and track liquidity and sqrt price liquidityBeforeSwap := concentratedPool.GetLiquidity() @@ -436,10 +467,8 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { s.Require().Equal(liquidityAfterSwap.String(), liquidityBeforeSwap.String()) // Assert current sqrt price - inAmountSubSpreadReward := uosmoInDec_Swap1.Mul(osmomath.OneDec().Sub(osmomath.BigDecFromSDKDec(spreadFactorDec))) - expectedSqrtPriceDelta := inAmountSubSpreadReward.QuoTruncate(osmomath.BigDecFromSDKDec(concentratedPool.GetLiquidity())) // Δ(sqrtPrice) = Δy / L + expectedSqrtPriceDelta := osmomath.BigDecFromSDKDec(uosmoInDec_Swap1_SubTakerFee_SubSpreadFactor).QuoTruncate(osmomath.BigDecFromSDKDec(concentratedPool.GetLiquidity())) // Δ(sqrtPrice) = Δy / L expectedSqrtPrice := sqrtPriceBeforeSwap.Add(expectedSqrtPriceDelta) - s.Require().Equal(expectedSqrtPrice.String(), sqrtPriceAfterSwap.String()) // Collect SpreadRewards: Swap 1 @@ -463,6 +492,17 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { spreadRewardGrowthGlobal, ) + // Note the global spread reward growth before dust redistribution + spreadRewardGrowthGlobalBeforeDustRedistribution := spreadRewardGrowthGlobal.Clone() + + // Determine forfeited dust amount + forfeitedDustAmt := spreadRewardsUncollectedAddress1Position1_Swap1.Sub(spreadRewardsUncollectedAddress1Position1_Swap1.TruncateDec()) + forfeitedDust := sdk.NewDecCoins(sdk.NewDecCoinFromDec("uosmo", forfeitedDustAmt)) + forfeitedDustPerShare := forfeitedDust.QuoDecTruncate(totalLiquidity) + + // Add forfeited dust back to the global spread reward growth + spreadRewardGrowthGlobal.AddMut(forfeitedDustPerShare.AmountOf("uosmo")) + // Assert s.Require().Equal( addr1BalancesBefore.AmountOf("uosmo").Add(spreadRewardsUncollectedAddress1Position1_Swap1.TruncateInt()).String(), @@ -514,10 +554,13 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { // uosmoInDec_Swap2_NoSpreadReward is calculated such that swapping this amount (not considering spread reward) moves the price over the next initialized tick uosmoInDec_Swap2_NoSpreadReward = amountInToGetToNextInitTick.Add(amountInToGetToTickAfterInitialized) uosmoInDec_Swap2 = uosmoInDec_Swap2_NoSpreadReward.Quo(sdk.OneDec().Sub(spreadFactorDec)).TruncateDec() // account for spread factor of 1% - uosmoIn_Swap2 = fmt.Sprintf("%suosmo", uosmoInDec_Swap2.String()) - spreadRewardGrowthGlobal_Swap1 = spreadRewardGrowthGlobal.Clone() + spreadRewardGrowthGlobal_Swap1 = spreadRewardGrowthGlobalBeforeDustRedistribution.Clone() ) + + uosmoInDec_Swap2_AddTakerFee := uosmoInDec_Swap2.Quo(sdk.OneDec().Sub(takerFee)).TruncateDec() // account for taker fee + uosmoIn_Swap2 := fmt.Sprintf("%suosmo", uosmoInDec_Swap2_AddTakerFee.String()) + // Perform a swap chainABNode.SwapExactAmountIn(uosmoIn_Swap2, outMinAmt, fmt.Sprintf("%d", poolID), denom0, initialization.ValidatorWalletName) @@ -569,9 +612,9 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { spreadRewardsUncollectedAddress1Position1_Swap2 := calculateUncollectedSpreadRewards( positionsAddress1[0].Position.Liquidity, sdk.ZeroDec(), - sdk.ZeroDec(), - calculateSpreadRewardGrowthInside(spreadRewardGrowthGlobal_Swap1, sdk.ZeroDec(), sdk.ZeroDec()), - spreadRewardGrowthGlobal_Swap1.Add(spreadRewardCharge_Swap2_Step1), // cannot use spreadRewardGrowthGlobal, it was already increased by second swap's step + spreadRewardCharge_Swap2_Step2, + spreadRewardGrowthGlobal_Swap1, + spreadRewardGrowthGlobal, ) // Assert @@ -653,18 +696,21 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity() { amountInToGetToTickBelowInitialized := liquidityBeforeSwap.Add(positionsAddress1[0].Position.Liquidity).Mul(fractionBelowNextInitializedTick) amountInToGetToNextInitTick = liquidityBeforeSwap.Mul(fractionAtNextInitializedTick.SDKDec()) + // Collect spread rewards for address1 position1 to avoid overhead computations (swap2 already asserted spread rewards are aggregated correctly from multiple swaps) + chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + var ( // Swap parameters uionInDec_Swap3_NoSpreadReward = amountInToGetToNextInitTick.Add(amountInToGetToTickBelowInitialized) // amount of uion to move price from current to desired (not considering spreadFactor) uionInDec_Swap3 = uionInDec_Swap3_NoSpreadReward.Quo(sdk.OneDec().Sub(spreadFactorDec)).TruncateDec() // consider spreadFactor - uionIn_Swap3 = fmt.Sprintf("%suion", uionInDec_Swap3.String()) // Save variables from previous swaps spreadRewardGrowthGlobal_Swap2 = spreadRewardGrowthGlobal.Clone() - spreadRewardGrowthInsideAddress1Position1Last = spreadRewardGrowthGlobal_Swap1.Add(spreadRewardCharge_Swap2_Step1) + spreadRewardGrowthInsideAddress1Position1Last = spreadRewardGrowthGlobal.Sub(spreadRewardCharge_Swap2_Step2).Clone() ) - // Collect spread rewards for address1 position1 to avoid overhead computations (swap2 already asserted spread rewards are aggregated correctly from multiple swaps) - chainABNode.CollectSpreadRewards(address1, fmt.Sprint(positionsAddress1[0].Position.PositionId)) + + uionInDec_Swap3_AddTakerFee := uionInDec_Swap3.Quo(sdk.OneDec().Sub(takerFee)).TruncateDec() // account for taker fee + uionIn_Swap3 := fmt.Sprintf("%suion", uionInDec_Swap3_AddTakerFee.String()) // Perform a swap chainABNode.SwapExactAmountIn(uionIn_Swap3, outMinAmt, fmt.Sprintf("%d", poolID), denom1, initialization.ValidatorWalletName) @@ -890,7 +936,7 @@ func (s *IntegrationTestSuite) StableSwapPostUpgrade() { minAmountOut = "1" ) - coinAIn, coinBIn := fmt.Sprintf("20000%s", denomA), fmt.Sprintf("1%s", denomB) + coinAIn, coinBIn := fmt.Sprintf("20000%s", denomA), fmt.Sprintf("2%s", denomB) chainABNode.BankSend(initialization.WalletFeeTokens.String(), sender, config.StableswapWallet[index]) chainABNode.BankSend(coinAIn, sender, config.StableswapWallet[index]) @@ -1787,6 +1833,7 @@ func (s *IntegrationTestSuite) GeometricTWAP() { osmoassert.DecApproxEq(s.T(), sdk.NewDecWithPrec(5, 1), afterSwapTwapBOverA, sdk.NewDecWithPrec(1, 2)) } +<<<<<<< HEAD // START: CAN REMOVE POST v17 UPGRADE // Tests that v17 upgrade correctly creates the canonical pools in the upgrade handler. @@ -1847,3 +1894,37 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { } // END: CAN REMOVE POST v17 UPGRADE +======= +func (s *IntegrationTestSuite) SetDefaultTakerFeeBothChains() { + var wg sync.WaitGroup + wg.Add(2) + + // Chain A + + go func() { + defer wg.Done() + chainA, chainANode, err := s.getChainACfgs() + s.Require().NoError(err) + s.SetDefaultTakerFee(chainA, chainANode) + }() + + // Chain B + + go func() { + defer wg.Done() + chainB, chainBNode, err := s.getChainBCfgs() + s.Require().NoError(err) + s.SetDefaultTakerFee(chainB, chainBNode) + }() + + // Wait for all goroutines to complete + wg.Wait() +} + +func (s *IntegrationTestSuite) SetDefaultTakerFee(chain *chain.Config, chainNode *chain.NodeConfig) { + // Change the parameter to set the default taker fee to a non zero value + + err := chainNode.ParamChangeProposal("poolmanager", string(poolmanagertypes.KeyDefaultTakerFee), json.RawMessage(`"0.001500000000000000"`), chain) + s.Require().NoError(err) +} +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) diff --git a/tests/e2e/helpers_e2e_test.go b/tests/e2e/helpers_e2e_test.go index facbfe6cb22..51442f8c673 100644 --- a/tests/e2e/helpers_e2e_test.go +++ b/tests/e2e/helpers_e2e_test.go @@ -22,12 +22,11 @@ var defaultFeePerTx = sdk.NewInt(1000) // amountIn - amount being swapped // spreadFactor - pool's spread factor // poolLiquidity - current pool liquidity -func calculateSpreadRewardGrowthGlobal(amountIn, spreadFactor, poolLiquidity sdk.Dec) sdk.Dec { +func calculateSpreadRewardGrowthGlobal(spreadRewardChargeTotal, poolLiquidity sdk.Dec) sdk.Dec { // First we get total spread reward charge for the swap (ΔY * spreadFactor) - spreadRewardChargeTotal := amountIn.Mul(spreadFactor) // Calculating spread reward growth global (dividing by pool liquidity to find spread reward growth per unit of virtual liquidity) - spreadRewardGrowthGlobal := spreadRewardChargeTotal.Quo(poolLiquidity) + spreadRewardGrowthGlobal := spreadRewardChargeTotal.QuoTruncate(poolLiquidity) return spreadRewardGrowthGlobal } diff --git a/x/concentrated-liquidity/keeper.go b/x/concentrated-liquidity/keeper.go index 097df96b7b3..ba80b6094d7 100644 --- a/x/concentrated-liquidity/keeper.go +++ b/x/concentrated-liquidity/keeper.go @@ -126,3 +126,19 @@ func (k Keeper) ValidatePermissionlessPoolCreationEnabled(ctx sdk.Context) error } return nil } + +// GetAuthorizedQuoteDenoms gets the authorized quote denoms from the poolmanager keeper. +// This method is meant to be used for getting access to x/poolmanager params +// for use in sim_msgs.go for the CL module. +func (k Keeper) GetAuthorizedQuoteDenoms(ctx sdk.Context) []string { + return k.poolmanagerKeeper.GetParams(ctx).AuthorizedQuoteDenoms +} + +// SetAuthorizedQuoteDenoms sets the authorized quote denoms in the poolmanager keeper. +// This method is meant to be used for getting access to x/poolmanager params +// for use in sim_msgs.go for the CL module. +func (k Keeper) SetAuthorizedQuoteDenoms(ctx sdk.Context, authorizedQuoteDenoms []string) { + params := k.poolmanagerKeeper.GetParams(ctx) + params.AuthorizedQuoteDenoms = authorizedQuoteDenoms + k.poolmanagerKeeper.SetParams(ctx, params) +} diff --git a/x/concentrated-liquidity/pool.go b/x/concentrated-liquidity/pool.go index decd6cff2ec..211ab40fe1d 100644 --- a/x/concentrated-liquidity/pool.go +++ b/x/concentrated-liquidity/pool.go @@ -41,6 +41,7 @@ func (k Keeper) InitializePool(ctx sdk.Context, poolI poolmanagertypes.PoolI, cr spreadFactor := concentratedPool.GetSpreadFactor(ctx) poolId := concentratedPool.GetId() quoteAsset := concentratedPool.GetToken1() + poolManagerParams := k.poolmanagerKeeper.GetParams(ctx) if !k.validateTickSpacing(params, tickSpacing) { return types.UnauthorizedTickSpacingError{ProvidedTickSpacing: tickSpacing, AuthorizedTickSpacings: params.AuthorizedTickSpacing} @@ -50,8 +51,8 @@ func (k Keeper) InitializePool(ctx sdk.Context, poolI poolmanagertypes.PoolI, cr return types.UnauthorizedSpreadFactorError{ProvidedSpreadFactor: spreadFactor, AuthorizedSpreadFactors: params.AuthorizedSpreadFactors} } - if !validateAuthorizedQuoteDenoms(quoteAsset, params.AuthorizedQuoteDenoms) { - return types.UnauthorizedQuoteDenomError{ProvidedQuoteDenom: quoteAsset, AuthorizedQuoteDenoms: params.AuthorizedQuoteDenoms} + if !validateAuthorizedQuoteDenoms(quoteAsset, poolManagerParams.AuthorizedQuoteDenoms) { + return types.UnauthorizedQuoteDenomError{ProvidedQuoteDenom: quoteAsset, AuthorizedQuoteDenoms: poolManagerParams.AuthorizedQuoteDenoms} } if err := k.createSpreadRewardAccumulator(ctx, poolId); err != nil { diff --git a/x/concentrated-liquidity/pool_test.go b/x/concentrated-liquidity/pool_test.go index 93a6dacc1fe..d0159b9f173 100644 --- a/x/concentrated-liquidity/pool_test.go +++ b/x/concentrated-liquidity/pool_test.go @@ -86,9 +86,9 @@ func (s *KeeperTestSuite) TestInitializePool() { s.SetupTest() if len(test.authorizedDenomsOverwrite) > 0 { - params := s.App.ConcentratedLiquidityKeeper.GetParams(s.Ctx) + params := s.App.PoolManagerKeeper.GetParams(s.Ctx) params.AuthorizedQuoteDenoms = test.authorizedDenomsOverwrite - s.App.ConcentratedLiquidityKeeper.SetParams(s.Ctx, params) + s.App.PoolManagerKeeper.SetParams(s.Ctx, params) } s.setListenerMockOnConcentratedLiquidityKeeper() diff --git a/x/concentrated-liquidity/simulation/sim_msgs.go b/x/concentrated-liquidity/simulation/sim_msgs.go index 033dc62e3f3..757fa5df113 100644 --- a/x/concentrated-liquidity/simulation/sim_msgs.go +++ b/x/concentrated-liquidity/simulation/sim_msgs.go @@ -25,13 +25,16 @@ func RandomMsgCreateConcentratedPool(k clkeeper.Keeper, sim *osmosimtypes.SimCtx return nil, err } - // make sure the denoms are valid authorized quote denoms + // set permissionless pool creation to true defaultParams := cltypes.DefaultParams() defaultParams.IsPermissionlessPoolCreationEnabled = true - defaultParams.AuthorizedQuoteDenoms = append(defaultParams.AuthorizedQuoteDenoms, coin1.Denom, coin0.Denom) - k.SetParams(ctx, defaultParams) + // make sure the denoms are valid authorized quote denoms + authorizedQuoteDenoms := k.GetAuthorizedQuoteDenoms(ctx) + authorizedQuoteDenoms = append(defaultParams.AuthorizedQuoteDenoms, coin1.Denom, coin0.Denom) + k.SetAuthorizedQuoteDenoms(ctx, authorizedQuoteDenoms) + denomMetaData := banktypes.Metadata{ DenomUnits: []*banktypes.DenomUnit{{ Denom: appParams.BaseCoinUnit, diff --git a/x/concentrated-liquidity/types/expected_keepers.go b/x/concentrated-liquidity/types/expected_keepers.go index 20d67e104a9..8746040bbba 100644 --- a/x/concentrated-liquidity/types/expected_keepers.go +++ b/x/concentrated-liquidity/types/expected_keepers.go @@ -30,6 +30,8 @@ type BankKeeper interface { // PoolManagerKeeper defines the interface needed to be fulfilled for // the poolmanager keeper. type PoolManagerKeeper interface { + GetParams(ctx sdk.Context) (params poolmanagertypes.Params) + SetParams(ctx sdk.Context, params poolmanagertypes.Params) CreatePool(ctx sdk.Context, msg poolmanagertypes.CreatePoolMsg) (uint64, error) GetNextPoolId(ctx sdk.Context) uint64 CreateConcentratedPoolAsPoolManager(ctx sdk.Context, msg poolmanagertypes.CreatePoolMsg) (poolmanagertypes.PoolI, error) diff --git a/x/gamm/keeper/msg_server_test.go b/x/gamm/keeper/msg_server_test.go index b51bca98f82..963bb67dbaa 100644 --- a/x/gamm/keeper/msg_server_test.go +++ b/x/gamm/keeper/msg_server_test.go @@ -19,7 +19,7 @@ const ( func (s *KeeperTestSuite) TestSwapExactAmountIn_Events() { const ( tokenInMinAmount = 1 - tokenIn = 5 + tokenIn = 10 ) testcases := map[string]struct { @@ -46,7 +46,7 @@ func (s *KeeperTestSuite) TestSwapExactAmountIn_Events() { tokenIn: sdk.NewCoin("foo", sdk.NewInt(tokenIn)), tokenOutMinAmount: sdk.NewInt(tokenInMinAmount), expectedSwapEvents: 1, - expectedMessageEvents: 3, // 1 gamm + 2 events emitted by other keeper methods. + expectedMessageEvents: 5, // 1 gamm + 4 events emitted by other keeper methods. }, "two hops": { routes: []poolmanagertypes.SwapAmountInRoute{ @@ -62,7 +62,7 @@ func (s *KeeperTestSuite) TestSwapExactAmountIn_Events() { tokenIn: sdk.NewCoin("foo", sdk.NewInt(tokenIn)), tokenOutMinAmount: sdk.NewInt(tokenInMinAmount), expectedSwapEvents: 2, - expectedMessageEvents: 5, // 1 gamm + 4 events emitted by other keeper methods. + expectedMessageEvents: 9, // 1 gamm + 8 events emitted by other keeper methods. }, "invalid - two hops, denom does not exist": { routes: []poolmanagertypes.SwapAmountInRoute{ @@ -75,9 +75,10 @@ func (s *KeeperTestSuite) TestSwapExactAmountIn_Events() { TokenOutDenom: "baz", }, }, - tokenIn: sdk.NewCoin(doesNotExistDenom, sdk.NewInt(tokenIn)), - tokenOutMinAmount: sdk.NewInt(tokenInMinAmount), - expectError: true, + tokenIn: sdk.NewCoin(doesNotExistDenom, sdk.NewInt(tokenIn)), + tokenOutMinAmount: sdk.NewInt(tokenInMinAmount), + expectedMessageEvents: 1, // 1 event gets triggered prior to failure. + expectError: true, }, } @@ -118,7 +119,7 @@ func (s *KeeperTestSuite) TestSwapExactAmountIn_Events() { func (s *KeeperTestSuite) TestSwapExactAmountOut_Events() { const ( tokenInMaxAmount = int64Max - tokenOut = 5 + tokenOut = 10 ) testcases := map[string]struct { @@ -145,7 +146,7 @@ func (s *KeeperTestSuite) TestSwapExactAmountOut_Events() { tokenOut: sdk.NewCoin("foo", sdk.NewInt(tokenOut)), tokenInMaxAmount: sdk.NewInt(tokenInMaxAmount), expectedSwapEvents: 1, - expectedMessageEvents: 3, // 1 gamm + 2 events emitted by other keeper methods. + expectedMessageEvents: 5, // 1 gamm + 4 events emitted by other keeper methods. }, "two hops": { routes: []poolmanagertypes.SwapAmountOutRoute{ @@ -161,7 +162,7 @@ func (s *KeeperTestSuite) TestSwapExactAmountOut_Events() { tokenOut: sdk.NewCoin("foo", sdk.NewInt(tokenOut)), tokenInMaxAmount: sdk.NewInt(tokenInMaxAmount), expectedSwapEvents: 2, - expectedMessageEvents: 5, // 1 gamm + 4 events emitted by other keeper methods. + expectedMessageEvents: 9, // 1 gamm + 8 events emitted by other keeper methods. }, "invalid - two hops, denom does not exist": { routes: []poolmanagertypes.SwapAmountOutRoute{ diff --git a/x/gamm/keeper/pool.go b/x/gamm/keeper/pool.go index 40ad1e2a35f..9b70f618b2e 100644 --- a/x/gamm/keeper/pool.go +++ b/x/gamm/keeper/pool.go @@ -354,3 +354,9 @@ func asCFMMPool(pool poolmanagertypes.PoolI) (types.CFMMPoolI, error) { } return cfmmPool, nil } + +// GetTradingPairTakerFee is a wrapper for poolmanager's GetTradingPairTakerFee, and is solely used +// to get access to this method for use in sim_msgs.go for the GAMM module. +func (k Keeper) GetTradingPairTakerFee(ctx sdk.Context, denom0, denom1 string) (sdk.Dec, error) { + return k.poolManager.GetTradingPairTakerFee(ctx, denom0, denom1) +} diff --git a/x/gamm/simulation/sim_msgs.go b/x/gamm/simulation/sim_msgs.go index f3905a0b0aa..b97ee4109a4 100644 --- a/x/gamm/simulation/sim_msgs.go +++ b/x/gamm/simulation/sim_msgs.go @@ -145,7 +145,18 @@ func RandomSwapExactAmountIn(k keeper.Keeper, sim *simtypes.SimCtx, ctx sdk.Cont randomCoinSubset := sim.RandSubsetCoins(sdk.NewCoins(sdk.NewCoin(accCoinIn.Denom, accCoinIn.Amount))) // calculate the minimum number of tokens received from input of tokenIn - tokenOutMin, err := pool.CalcOutAmtGivenIn(ctx, randomCoinSubset, coinOut.Denom, pool.GetSpreadFactor(ctx)) + + // N.B. Calling MsgSwapExactAmountIn executes the swap via the pool manager, which charges the taker fee. + // We therefore need to remove the taker fee from the amountIn before calling the calc method. + takerFee, err := k.GetTradingPairTakerFee(ctx, coinIn.Denom, coinOut.Denom) + if err != nil { + return nil, err + } + + amountInAfterSubTakerFee := randomCoinSubset[0].Amount.ToDec().Mul(sdk.OneDec().Sub(takerFee)) + tokenInAfterSubTakerFee := sdk.NewCoin(randomCoinSubset[0].Denom, amountInAfterSubTakerFee.TruncateInt()) + + tokenOutMin, err := pool.CalcOutAmtGivenIn(ctx, sdk.NewCoins(tokenInAfterSubTakerFee), coinOut.Denom, pool.GetSpreadFactor(ctx)) if err != nil { return nil, err } @@ -192,6 +203,16 @@ func RandomSwapExactAmountOut(k keeper.Keeper, sim *simtypes.SimCtx, ctx sdk.Con return nil, err } + // N.B. Calling MsgSwapExactAmountOut executes the swap via the pool manager, which charges the taker fee. + // We therefore need to add the taker fee to the amountIn after calling the calc method. + takerFee, err := k.GetTradingPairTakerFee(ctx, coinIn.Denom, coinOut.Denom) + if err != nil { + return nil, err + } + + amountInAfterAddTakerFee := tokenInMax.Amount.ToDec().Quo(sdk.OneDec().Sub(takerFee)) + tokenInMax = sdk.NewCoin(tokenInMax.Denom, amountInAfterAddTakerFee.TruncateInt()) + return &types.MsgSwapExactAmountOut{ Sender: senderAcc.Address.String(), Routes: route, diff --git a/x/gamm/types/expected_keepers.go b/x/gamm/types/expected_keepers.go index c8a106ce259..a77f2a62324 100644 --- a/x/gamm/types/expected_keepers.go +++ b/x/gamm/types/expected_keepers.go @@ -95,6 +95,8 @@ type PoolManager interface { GetPool(ctx sdk.Context, poolId uint64) (poolmanagertypes.PoolI, error) CreateConcentratedPoolAsPoolManager(ctx sdk.Context, msg poolmanagertypes.CreatePoolMsg) (poolmanagertypes.PoolI, error) + + GetTradingPairTakerFee(ctx sdk.Context, denom0, denom1 string) (sdk.Dec, error) } type PoolIncentivesKeeper interface { diff --git a/x/poolmanager/README.md b/x/poolmanager/README.md index f5bb0a82f26..656087e5a3c 100644 --- a/x/poolmanager/README.md +++ b/x/poolmanager/README.md @@ -269,6 +269,12 @@ user should be putting in is done through the following formula: `tokenBalanceIn * [{tokenBalanceOut / (tokenBalanceOut - tokenAmountOut)} ^ (tokenWeightOut / tokenWeightIn) -1] / tokenAmountIn` +With the introduction of a `takerFee`, the actual amount of `tokenIn` that is used to calculate the amount of `tokenOut` is reduced by the `takerFee` amount. If governance or a governance approved DAO adds a specified trading pair to the `takerFee` module store, the fee associated with that pair is used. Otherwise, the `defaultTakerFee` defined in the poolmanger's parameters is used. + +The poolmanager only concerns itself with proportionally distributing the takerFee to the respective staking rewards and community pool txfees module accounts. For swaps originating in OSMO, the poolmanger distributes these fees based on the `OsmoTakerFeeDistribution` parameter. For swaps originating in non-OSMO assets, the poolmanager distributes these fees based on the `NonOsmoTakerFeeDistribution` parameter. For taker fees generated in non whitelisted quote denoms assets, the amount that goes to the community pool (defined by the `NonOsmoTakerFeeDistribution` above) is swapped to the `community_pool_denom_to_swap_non_whitelisted_assets_to` parameter defined in poolmanager. For instance, if a taker fee is generated in BTC, the respective community pool percent is sent directly to the community pool since it is a whitelisted quote denom. If it is generated in FOO, which is not a whitelisted quote denom, the respective community pool percent is swapped to the `community_pool_denom_to_swap_non_whitelisted_assets_to` parameter defined in poolmanager and send to the community pool as that denom at epoch. + +For more information on how the final distribution of these fees and how they are swapped, see the txfees module README. + Existing Swap types: - SwapExactAmountIn - SwapExactAmountOut @@ -291,6 +297,10 @@ Existing Swap types: [MsgSplitRouteSwapExactAmountOut](https://github.com/osmosis-labs/osmosis/blob/46e6a0c2051a3a5ef8cdd4ecebfff7305b13ab98/proto/osmosis/poolmanager/v1beta1/tx.proto#L85) +## MsgSetDenomPairTakerFee + +[MsgSplitRouteSwapExactAmountOut](https://github.com/osmosis-labs/osmosis/blob/d129ea37f5490d8a212932a78cd35cb864c799c7/proto/osmosis/poolmanager/v1beta1/tx.proto#L121) + ## Multi-Hop All tokens are swapped using a multi-hop mechanism. That is, all swaps diff --git a/x/poolmanager/client/cli/tx.go b/x/poolmanager/client/cli/tx.go index 43cf9a7137d..2608566610c 100644 --- a/x/poolmanager/client/cli/tx.go +++ b/x/poolmanager/client/cli/tx.go @@ -14,6 +14,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/spf13/cobra" sdk "github.com/cosmos/cosmos-sdk/types" @@ -446,3 +449,173 @@ func ParseCoinsNoSort(coinsStr string) (sdk.Coins, error) { } return sdk.NormalizeCoins(decCoins), nil } + +// NewCmdHandleDenomPairTakerFeeProposal implements a command handler for denom pair taker fee proposal +func NewCmdHandleDenomPairTakerFeeProposal() *cobra.Command { + cmd := &cobra.Command{ + Use: "denom-pair-taker-fee-proposal [denom-pairs-with-taker-fee] [flags]", + Args: cobra.ExactArgs(1), + Short: "Submit a denom pair taker fee proposal", + Long: strings.TrimSpace(`Submit a denom pair taker fee proposal. + +Passing in denom-pairs-with-taker-fee separated by commas would be parsed automatically to pairs of denomPairTakerFee records. +Ex) denom-pair-taker-fee-proposal uion,uosmo,0.0016,stake,uosmo,0.005,uatom,uosmo,0.0015 -> +[uion<>uosmo, takerFee 0.16%] +[stake<>uosmo, takerFee 0.5%] +[uatom<>uosmo, removes from state since its being set to the default takerFee value] + + `), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + content, err := parseDenomPairTakerFeeArgToContent(cmd, args[0]) + if err != nil { + return err + } + + from := clientCtx.GetFromAddress() + + depositStr, err := cmd.Flags().GetString(govcli.FlagDeposit) + if err != nil { + return err + } + deposit, err := sdk.ParseCoinsNormalized(depositStr) + if err != nil { + return err + } + + msg, err := govtypes.NewMsgSubmitProposal(content, deposit, from) + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + cmd.Flags().String(govcli.FlagTitle, "", "title of proposal") + cmd.Flags().String(govcli.FlagDescription, "", "description of proposal") + cmd.Flags().String(govcli.FlagDeposit, "", "deposit of proposal") + cmd.Flags().Bool(govcli.FlagIsExpedited, false, "If true, makes the proposal an expedited one") + cmd.Flags().String(govcli.FlagProposal, "", "Proposal file path (if this path is given, other proposal flags are ignored)") + + return cmd +} + +func NewSetDenomPairTakerFeeCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "set-denom-pair-taker-fee [flags]", + Short: "allows admin addresses to set the taker fee for a denom pair", + Long: strings.TrimSpace(`Allows admin addresses to set the taker fee for a denom pair. + +Passing in set-denom-pair-taker-fee separated by commas would be parsed automatically to pairs of denomPairTakerFee records. +Ex) set-denom-pair-taker-fee uion,uosmo,0.0016,stake,uosmo,0.005,uatom,uosmo,0.0015 -> +[uion<>uosmo, takerFee 0.16%] +[stake<>uosmo, takerFee 0.5%] +[uatom<>uosmo, removes from state since its being set to the default takerFee value] + + `), + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msg, err := parseDenomPairTakerFeeArgToMsg(clientCtx, args[0]) + if err != nil { + return err + } + + if err = msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + cmd.Flags().AddFlagSet(FlagSetCreatePool()) + flags.AddTxFlagsToCmd(cmd) + + _ = cmd.MarkFlagRequired(FlagPoolFile) + + return cmd +} + +func parseDenomPairTakerFeeArgToContent(cmd *cobra.Command, arg string) (govtypes.Content, error) { + title, err := cmd.Flags().GetString(govcli.FlagTitle) + if err != nil { + return nil, err + } + + description, err := cmd.Flags().GetString(govcli.FlagDescription) + if err != nil { + return nil, err + } + + denomPairTakerFee, err := ParseDenomPairTakerFee(arg) + if err != nil { + return nil, err + } + + content := &types.DenomPairTakerFeeProposal{ + Title: title, + Description: description, + DenomPairTakerFee: denomPairTakerFee, + } + + return content, nil +} + +func parseDenomPairTakerFeeArgToMsg(clientCtx client.Context, arg string) (sdk.Msg, error) { + denomPairTakerFee, err := ParseDenomPairTakerFee(arg) + if err != nil { + return nil, err + } + + msg := &types.MsgSetDenomPairTakerFee{ + Sender: clientCtx.GetFromAddress().String(), + DenomPairTakerFee: denomPairTakerFee, + } + + return msg, nil +} + +func ParseDenomPairTakerFee(arg string) ([]types.DenomPairTakerFee, error) { + denomPairTakerFeeRecords := strings.Split(arg, ",") + + if len(denomPairTakerFeeRecords)%3 != 0 { + return nil, fmt.Errorf("denomPairTakerFeeRecords must be a list of denom0, denom1, and takerFee separated by commas") + } + + finaldenomPairTakerFeeRecordsRecords := []types.DenomPairTakerFee{} + i := 0 + for i < len(denomPairTakerFeeRecords) { + denom0 := denomPairTakerFeeRecords[i] + denom1 := denomPairTakerFeeRecords[i+1] + + takerFeeStr := denomPairTakerFeeRecords[i+2] + takerFee, err := sdk.NewDecFromStr(takerFeeStr) + if err != nil { + return nil, err + } + + finaldenomPairTakerFeeRecordsRecords = append(finaldenomPairTakerFeeRecordsRecords, types.DenomPairTakerFee{ + Denom0: denom0, + Denom1: denom1, + TakerFee: takerFee, + }) + + // increase counter by the next 3 + i = i + 3 + } + + return finaldenomPairTakerFeeRecordsRecords, nil +} diff --git a/x/poolmanager/client/cli/tx_test.go b/x/poolmanager/client/cli/tx_test.go index 27ead9cd71f..493cb28dbef 100644 --- a/x/poolmanager/client/cli/tx_test.go +++ b/x/poolmanager/client/cli/tx_test.go @@ -1,12 +1,14 @@ package cli_test import ( + "fmt" "testing" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/osmosis-labs/osmosis/v19/x/poolmanager/client/cli" + "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" ) func TestParseCoinsNoSort(t *testing.T) { @@ -68,3 +70,52 @@ func TestParseCoinsNoSort(t *testing.T) { }) } } + +func TestParseDenomPairTakerFee(t *testing.T) { + tests := map[string]struct { + denomPairTakerFeeStr string + expectedDenomPairTakerFee []types.DenomPairTakerFee + expectedError error + }{ + "one set": { + denomPairTakerFeeStr: "uion,uosmo,0.0016", + expectedDenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "uion", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0016"), + }}, + }, + "two sets": { + denomPairTakerFeeStr: "uion,uosmo,0.0016,stake,uosmo,0.005", + expectedDenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "uion", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0016"), + }, + { + Denom0: "stake", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.005"), + }, + }, + }, + "error: wrong format": { + denomPairTakerFeeStr: "uion,uosmo,0.0016,stake", + expectedError: fmt.Errorf("denomPairTakerFeeRecords must be a list of denom0, denom1, and takerFee separated by commas"), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + denomPairTakerFee, err := cli.ParseDenomPairTakerFee(tc.denomPairTakerFeeStr) + if tc.expectedError != nil { + require.ErrorAs(t, err, &tc.expectedError) + return + } + require.NoError(t, err) + require.Equal(t, tc.expectedDenomPairTakerFee, denomPairTakerFee) + }) + } +} diff --git a/x/poolmanager/create_pool.go b/x/poolmanager/create_pool.go index 43b784210b0..0dfd0c00114 100644 --- a/x/poolmanager/create_pool.go +++ b/x/poolmanager/create_pool.go @@ -105,6 +105,8 @@ func (k Keeper) createPoolZeroLiquidityNoCreationFee(ctx sdk.Context, msg types. // Get the next pool ID and increment the pool ID counter. poolId := k.getNextPoolIdAndIncrement(ctx) + poolType := msg.GetPoolType() + // Create the pool with the given pool ID. pool, err := msg.CreatePool(ctx, poolId) if err != nil { @@ -112,7 +114,7 @@ func (k Keeper) createPoolZeroLiquidityNoCreationFee(ctx sdk.Context, msg types. } // Store the pool ID to pool type mapping in state. - k.SetPoolRoute(ctx, poolId, msg.GetPoolType()) + k.SetPoolRoute(ctx, poolId, poolType) // Validates the pool address and pool ID stored match what was expected. if err := k.validateCreatedPool(poolId, pool); err != nil { @@ -120,7 +122,7 @@ func (k Keeper) createPoolZeroLiquidityNoCreationFee(ctx sdk.Context, msg types. } // Run the respective pool type's initialization logic. - swapModule := k.routes[msg.GetPoolType()] + swapModule := k.routes[poolType] if err := swapModule.InitializePool(ctx, pool, msg.PoolCreator()); err != nil { return nil, err } diff --git a/x/poolmanager/create_pool_test.go b/x/poolmanager/create_pool_test.go index ff430a3f0ed..504fbc01a62 100644 --- a/x/poolmanager/create_pool_test.go +++ b/x/poolmanager/create_pool_test.go @@ -65,9 +65,9 @@ func (s *KeeperTestSuite) TestPoolCreationFee() { poolmanagerKeeper := s.App.PoolManagerKeeper // set pool creation fee - poolmanagerKeeper.SetParams(s.Ctx, types.Params{ - PoolCreationFee: test.poolCreationFee, - }) + newParams := params + newParams.PoolCreationFee = test.poolCreationFee + poolmanagerKeeper.SetParams(s.Ctx, newParams) // fund sender test account sender, err := sdk.AccAddressFromBech32(test.msg.Sender) diff --git a/x/poolmanager/export_test.go b/x/poolmanager/export_test.go index c418775f760..10c1a38a6c7 100644 --- a/x/poolmanager/export_test.go +++ b/x/poolmanager/export_test.go @@ -62,3 +62,18 @@ func (k Keeper) CreateOsmoMultihopExpectedSwapOuts( ) ([]sdk.Int, error) { return k.createOsmoMultihopExpectedSwapOuts(ctx, route, tokenOut, cumulativeRouteSwapFee, sumOfSwapFees) } +<<<<<<< HEAD +======= + +func (k Keeper) CalcTakerFeeExactIn(tokenIn sdk.Coin, takerFee sdk.Dec) (sdk.Coin, sdk.Coin) { + return k.calcTakerFeeExactIn(tokenIn, takerFee) +} + +func (k Keeper) CalcTakerFeeExactOut(tokenOut sdk.Coin, takerFee sdk.Dec) (sdk.Coin, sdk.Coin) { + return k.calcTakerFeeExactOut(tokenOut, takerFee) +} + +func (k Keeper) TrackVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) { + k.trackVolume(ctx, poolId, volumeGenerated) +} +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) diff --git a/x/poolmanager/gov.go b/x/poolmanager/gov.go index 55dd3937e6c..b309d31bf7e 100644 --- a/x/poolmanager/gov.go +++ b/x/poolmanager/gov.go @@ -1,7 +1,6 @@ package poolmanager import ( - "errors" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -12,7 +11,10 @@ import ( ) func (k Keeper) HandleDenomPairTakerFeeProposal(ctx sdk.Context, p *types.DenomPairTakerFeeProposal) error { - return errors.New("TODO: unimplemented") + for _, denomPair := range p.DenomPairTakerFee { + k.SetDenomPairTakerFee(ctx, denomPair.Denom0, denomPair.Denom1, denomPair.TakerFee) + } + return nil } func NewPoolManagerProposalHandler(k Keeper) govtypes.Handler { diff --git a/x/poolmanager/keeper_test.go b/x/poolmanager/keeper_test.go index 0e67d8fe9a0..9c97918ce1b 100644 --- a/x/poolmanager/keeper_test.go +++ b/x/poolmanager/keeper_test.go @@ -18,8 +18,21 @@ type KeeperTestSuite struct { const testExpectedPoolId = 3 var ( - testPoolCreationFee = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000_000_000)} - testPoolRoute = []types.ModuleRoute{ + testPoolCreationFee = sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000_000_000)} + testDefaultTakerFee = sdk.MustNewDecFromStr("0.0015") + testOsmoTakerFeeDistribution = types.TakerFeeDistributionPercentage{ + StakingRewards: sdk.MustNewDecFromStr("0.3"), + CommunityPool: sdk.MustNewDecFromStr("0.7"), + } + testNonOsmoTakerFeeDistribution = types.TakerFeeDistributionPercentage{ + StakingRewards: sdk.MustNewDecFromStr("0.2"), + CommunityPool: sdk.MustNewDecFromStr("0.8"), + } + testAdminAddresses = []string{"osmo106x8q2nv7xsg7qrec2zgdf3vvq0t3gn49zvaha", "osmo105l5r3rjtynn7lg362r2m9hkpfvmgmjtkglsn9"} + testCommunityPoolDenomToSwapNonWhitelistedAssetsTo = "uusdc" + testAuthorizedQuoteDenoms = []string{"uosmo", "uion", "uatom"} + + testPoolRoute = []types.ModuleRoute{ { PoolId: 1, PoolType: types.Balancer, @@ -69,13 +82,28 @@ func (s *KeeperTestSuite) TestInitGenesis() { s.App.PoolManagerKeeper.InitGenesis(s.Ctx, &types.GenesisState{ Params: types.Params{ PoolCreationFee: testPoolCreationFee, + TakerFeeParams: types.TakerFeeParams{ + DefaultTakerFee: testDefaultTakerFee, + OsmoTakerFeeDistribution: testOsmoTakerFeeDistribution, + NonOsmoTakerFeeDistribution: testNonOsmoTakerFeeDistribution, + AdminAddresses: testAdminAddresses, + CommunityPoolDenomToSwapNonWhitelistedAssetsTo: testCommunityPoolDenomToSwapNonWhitelistedAssetsTo, + }, + AuthorizedQuoteDenoms: testAuthorizedQuoteDenoms, }, NextPoolId: testExpectedPoolId, PoolRoutes: testPoolRoute, }) + params := s.App.PoolManagerKeeper.GetParams(s.Ctx) s.Require().Equal(uint64(testExpectedPoolId), s.App.PoolManagerKeeper.GetNextPoolId(s.Ctx)) - s.Require().Equal(testPoolCreationFee, s.App.PoolManagerKeeper.GetParams(s.Ctx).PoolCreationFee) + s.Require().Equal(testPoolCreationFee, params.PoolCreationFee) + s.Require().Equal(testDefaultTakerFee, params.TakerFeeParams.DefaultTakerFee) + s.Require().Equal(testOsmoTakerFeeDistribution, params.TakerFeeParams.OsmoTakerFeeDistribution) + s.Require().Equal(testNonOsmoTakerFeeDistribution, params.TakerFeeParams.NonOsmoTakerFeeDistribution) + s.Require().Equal(testAdminAddresses, params.TakerFeeParams.AdminAddresses) + s.Require().Equal(testCommunityPoolDenomToSwapNonWhitelistedAssetsTo, params.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) + s.Require().Equal(testAuthorizedQuoteDenoms, params.AuthorizedQuoteDenoms) s.Require().Equal(testPoolRoute, s.App.PoolManagerKeeper.GetAllPoolRoutes(s.Ctx)) } @@ -83,6 +111,14 @@ func (s *KeeperTestSuite) TestExportGenesis() { s.App.PoolManagerKeeper.InitGenesis(s.Ctx, &types.GenesisState{ Params: types.Params{ PoolCreationFee: testPoolCreationFee, + TakerFeeParams: types.TakerFeeParams{ + DefaultTakerFee: testDefaultTakerFee, + OsmoTakerFeeDistribution: testOsmoTakerFeeDistribution, + NonOsmoTakerFeeDistribution: testNonOsmoTakerFeeDistribution, + AdminAddresses: testAdminAddresses, + CommunityPoolDenomToSwapNonWhitelistedAssetsTo: testCommunityPoolDenomToSwapNonWhitelistedAssetsTo, + }, + AuthorizedQuoteDenoms: testAuthorizedQuoteDenoms, }, NextPoolId: testExpectedPoolId, PoolRoutes: testPoolRoute, @@ -91,5 +127,11 @@ func (s *KeeperTestSuite) TestExportGenesis() { genesis := s.App.PoolManagerKeeper.ExportGenesis(s.Ctx) s.Require().Equal(uint64(testExpectedPoolId), genesis.NextPoolId) s.Require().Equal(testPoolCreationFee, genesis.Params.PoolCreationFee) + s.Require().Equal(testDefaultTakerFee, genesis.Params.TakerFeeParams.DefaultTakerFee) + s.Require().Equal(testOsmoTakerFeeDistribution, genesis.Params.TakerFeeParams.OsmoTakerFeeDistribution) + s.Require().Equal(testNonOsmoTakerFeeDistribution, genesis.Params.TakerFeeParams.NonOsmoTakerFeeDistribution) + s.Require().Equal(testAdminAddresses, genesis.Params.TakerFeeParams.AdminAddresses) + s.Require().Equal(testCommunityPoolDenomToSwapNonWhitelistedAssetsTo, genesis.Params.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) + s.Require().Equal(testAuthorizedQuoteDenoms, genesis.Params.AuthorizedQuoteDenoms) s.Require().Equal(testPoolRoute, genesis.PoolRoutes) } diff --git a/x/poolmanager/msg_server.go b/x/poolmanager/msg_server.go index 20c1ee1624b..8d935d02abc 100644 --- a/x/poolmanager/msg_server.go +++ b/x/poolmanager/msg_server.go @@ -2,7 +2,6 @@ package poolmanager import ( "context" - "errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -122,5 +121,23 @@ func (server msgServer) SplitRouteSwapExactAmountOut(goCtx context.Context, msg } func (server msgServer) SetDenomPairTakerFee(goCtx context.Context, msg *types.MsgSetDenomPairTakerFee) (*types.MsgSetDenomPairTakerFeeResponse, error) { - return nil, errors.New("not implemented") + ctx := sdk.UnwrapSDKContext(goCtx) + + for _, denomPair := range msg.DenomPairTakerFee { + err := server.keeper.SenderValidationSetDenomPairTakerFee(ctx, msg.Sender, denomPair.Denom0, denomPair.Denom1, denomPair.TakerFee) + if err != nil { + return nil, err + } + } + + // Set denom pair taker fee event is handled in each iteration of the loop above + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender), + ), + }) + + return &types.MsgSetDenomPairTakerFeeResponse{Success: true}, nil } diff --git a/x/poolmanager/msg_server_test.go b/x/poolmanager/msg_server_test.go index 726be87f8ad..334b8865df8 100644 --- a/x/poolmanager/msg_server_test.go +++ b/x/poolmanager/msg_server_test.go @@ -48,7 +48,7 @@ func (s *KeeperTestSuite) TestSplitRouteSwapExactAmountIn() { tokenoutMinAmount: min_amount, expectedSplitRouteSwapEvent: 1, - expectedMessageEvents: 9, // 4 pool creation + 5 events in SplitRouteExactAmountIn keeper methods + expectedMessageEvents: 16, // 4 pool creation + 12 events in SplitRouteExactAmountIn keeper methods }, "error: empty route": { routes: []types.SwapAmountInSplitRoute{}, @@ -134,7 +134,7 @@ func (s *KeeperTestSuite) TestSplitRouteSwapExactAmountOut() { tokenoutMaxAmount: max_amount, expectedSplitRouteSwapEvent: 1, - expectedMessageEvents: 9, // 4 pool creation + 5 events in SplitRouteExactAmountOut keeper methods + expectedMessageEvents: 17, // 4 pool creation + 13 events in SplitRouteExactAmountOut keeper methods }, "error: empty route": { routes: []types.SwapAmountOutSplitRoute{}, @@ -196,3 +196,92 @@ func (s *KeeperTestSuite) TestSplitRouteSwapExactAmountOut() { }) } } + +func (s *KeeperTestSuite) TestSetDenomPairTakerFee() { + adminAcc := s.TestAccs[0].String() + nonAdminAcc := s.TestAccs[1].String() + testcases := map[string]struct { + denomPairTakerFeeMessage types.MsgSetDenomPairTakerFee + + expectedSetDenomPairTakerFeeEvent int + expectedMessageEvents int + expectedError bool + }{ + "valid case: two pairs": { + denomPairTakerFeeMessage: types.MsgSetDenomPairTakerFee{ + Sender: adminAcc, + DenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "denom0", + Denom1: "denom1", + TakerFee: sdk.MustNewDecFromStr("0.0013"), + }, + { + Denom0: "denom0", + Denom1: "denom2", + TakerFee: sdk.MustNewDecFromStr("0.0016"), + }, + }, + }, + + expectedSetDenomPairTakerFeeEvent: 2, + }, + "valid case: one pair": { + denomPairTakerFeeMessage: types.MsgSetDenomPairTakerFee{ + Sender: adminAcc, + DenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "denom0", + Denom1: "denom1", + TakerFee: sdk.MustNewDecFromStr("0.0013"), + }, + }, + }, + + expectedSetDenomPairTakerFeeEvent: 1, + }, + "error: not admin account": { + denomPairTakerFeeMessage: types.MsgSetDenomPairTakerFee{ + Sender: nonAdminAcc, + DenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "denom0", + Denom1: "denom1", + TakerFee: sdk.MustNewDecFromStr("0.0013"), + }, + }, + }, + + expectedError: true, + }, + } + + for name, tc := range testcases { + s.Run(name, func() { + s.Setup() + msgServer := poolmanagerKeeper.NewMsgServerImpl(s.App.PoolManagerKeeper) + + // Add the admin address to the pool manager params. + poolManagerParams := s.App.PoolManagerKeeper.GetParams(s.Ctx) + poolManagerParams.TakerFeeParams.AdminAddresses = []string{adminAcc} + s.App.PoolManagerKeeper.SetParams(s.Ctx, poolManagerParams) + + // Reset event counts to 0 by creating a new manager. + s.Ctx = s.Ctx.WithEventManager(sdk.NewEventManager()) + s.Equal(0, len(s.Ctx.EventManager().Events())) + + response, err := msgServer.SetDenomPairTakerFee(sdk.WrapSDKContext(s.Ctx), &types.MsgSetDenomPairTakerFee{ + Sender: tc.denomPairTakerFeeMessage.Sender, + DenomPairTakerFee: tc.denomPairTakerFeeMessage.DenomPairTakerFee, + }) + if tc.expectedError { + s.Require().Error(err) + s.Require().Nil(response) + } else { + s.Require().NoError(err) + s.AssertEventEmitted(s.Ctx, types.TypeMsgSetDenomPairTakerFee, tc.expectedSetDenomPairTakerFeeEvent) + s.AssertEventEmitted(s.Ctx, sdk.EventTypeMessage, 1) + } + }) + } +} diff --git a/x/poolmanager/router.go b/x/poolmanager/router.go index b767a571b36..0e6e9874709 100644 --- a/x/poolmanager/router.go +++ b/x/poolmanager/router.go @@ -7,8 +7,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/osmosis-labs/osmosis/osmoutils" appparams "github.com/osmosis-labs/osmosis/v19/app/params" + + "github.com/osmosis-labs/osmosis/osmoutils" "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" ) @@ -92,7 +93,12 @@ func (k Keeper) RouteExactAmountIn( spreadFactor = routeSpreadFactor.MulRoundUp((spreadFactor.QuoRoundUp(sumOfSpreadFactors))) } - tokenOutAmount, err = swapModule.SwapExactAmountIn(ctx, sender, pool, tokenIn, routeStep.TokenOutDenom, _outMinAmount, spreadFactor) + tokenInAfterSubTakerFee, err := k.chargeTakerFee(ctx, tokenIn, routeStep.TokenOutDenom, sender, true) + if err != nil { + return sdk.Int{}, err + } + + tokenOutAmount, err = swapModule.SwapExactAmountIn(ctx, sender, pool, tokenInAfterSubTakerFee, routeStep.TokenOutDenom, _outMinAmount, spreadFactor) if err != nil { return sdk.Int{}, err } @@ -203,10 +209,54 @@ func (k Keeper) SwapExactAmountIn( return sdk.Int{}, fmt.Errorf("pool %d is not active", pool.GetId()) } - spreadFactor := pool.GetSpreadFactor(ctx) + tokenInAfterSubTakerFee, err := k.chargeTakerFee(ctx, tokenIn, tokenOutDenom, sender, true) + if err != nil { + return sdk.Int{}, err + } + + // routeStep to the pool-specific SwapExactAmountIn implementation. + tokenOutAmount, err = swapModule.SwapExactAmountIn(ctx, sender, pool, tokenInAfterSubTakerFee, tokenOutDenom, tokenOutMinAmount, pool.GetSpreadFactor(ctx)) + if err != nil { + return sdk.Int{}, err + } + + return tokenOutAmount, nil +} + +// SwapExactAmountInNoTakerFee is an API for swapping an exact amount of tokens +// as input to a pool to get a minimum amount of the desired token out. +// This method does NOT charge a taker fee, and should only be used in txfees hooks +// when swapping taker fees. This prevents us from charging taker fees +// on top of taker fees. +func (k Keeper) SwapExactAmountInNoTakerFee( + ctx sdk.Context, + sender sdk.AccAddress, + poolId uint64, + tokenIn sdk.Coin, + tokenOutDenom string, + tokenOutMinAmount sdk.Int, +) (tokenOutAmount sdk.Int, err error) { + // Get the pool-specific module implementation to ensure that + // swaps are routed to the pool type corresponding to pool ID's pool. + swapModule, err := k.GetPoolModule(ctx, poolId) + if err != nil { + return sdk.Int{}, err + } + + // Get pool as a general pool type. Note that the underlying function used + // still varies with the pool type. + pool, poolErr := swapModule.GetPool(ctx, poolId) + if poolErr != nil { + return sdk.Int{}, poolErr + } + + // Check if pool has swaps enabled. + if !pool.IsActive(ctx) { + return sdk.Int{}, fmt.Errorf("pool %d is not active", pool.GetId()) + } // routeStep to the pool-specific SwapExactAmountIn implementation. - tokenOutAmount, err = swapModule.SwapExactAmountIn(ctx, sender, pool, tokenIn, tokenOutDenom, tokenOutMinAmount, spreadFactor) + tokenOutAmount, err = swapModule.SwapExactAmountIn(ctx, sender, pool, tokenIn, tokenOutDenom, tokenOutMinAmount, pool.GetSpreadFactor(ctx)) if err != nil { return sdk.Int{}, err } @@ -266,7 +316,14 @@ func (k Keeper) MultihopEstimateOutGivenExactAmountIn( spreadFactor = routeSpreadFactor.Mul((spreadFactor.Quo(sumOfSpreadFactors))) } - tokenOut, err := swapModule.CalcOutAmtGivenIn(ctx, poolI, tokenIn, routeStep.TokenOutDenom, spreadFactor) + takerFee, err := k.GetTradingPairTakerFee(ctx, routeStep.TokenOutDenom, tokenIn.Denom) + if err != nil { + return sdk.Int{}, err + } + + tokenInAfterSubTakerFee, _ := k.calcTakerFeeExactIn(tokenIn, takerFee) + + tokenOut, err := swapModule.CalcOutAmtGivenIn(ctx, poolI, tokenInAfterSubTakerFee, routeStep.TokenOutDenom, spreadFactor) if err != nil { return sdk.Int{}, err } @@ -382,11 +439,27 @@ func (k Keeper) RouteExactAmountOut(ctx sdk.Context, return sdk.Int{}, swapErr } +<<<<<<< HEAD +======= + tokenIn := sdk.NewCoin(routeStep.TokenInDenom, curTokenInAmount) + tokenInAfterAddTakerFee, err := k.chargeTakerFee(ctx, tokenIn, _tokenOut.Denom, sender, false) + if err != nil { + return sdk.Int{}, err + } + + // Track volume for volume-splitting incentives + k.trackVolume(ctx, pool.GetId(), sdk.NewCoin(routeStep.TokenInDenom, tokenIn.Amount)) + +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) // Sets the final amount of tokens that need to be input into the first pool. Even though this is the final return value for the // whole method and will not change after the first iteration, we still iterate through the rest of the pools to execute their respective // swaps. if i == 0 { +<<<<<<< HEAD tokenInAmount = _tokenInAmount +======= + tokenInAmount = tokenInAfterAddTakerFee.Amount +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) } } @@ -624,9 +697,9 @@ func (k Keeper) getOsmoRoutedMultihopTotalSpreadFactor(ctx sdk.Context, route ty if poolErr != nil { return sdk.Dec{}, sdk.Dec{}, poolErr } - SpreadFactor := pool.GetSpreadFactor(ctx) - additiveSpreadFactor = additiveSpreadFactor.Add(SpreadFactor) - maxSpreadFactor = sdk.MaxDec(maxSpreadFactor, SpreadFactor) + spreadFactor := pool.GetSpreadFactor(ctx) + additiveSpreadFactor = additiveSpreadFactor.Add(spreadFactor) + maxSpreadFactor = sdk.MaxDec(maxSpreadFactor, spreadFactor) } // We divide by 2 to get the average since OSMO-routed multihops always have exactly 2 pools. @@ -663,13 +736,22 @@ func (k Keeper) createMultihopExpectedSwapOuts( return nil, err } - tokenIn, err := swapModule.CalcInAmtGivenOut(ctx, poolI, tokenOut, routeStep.TokenInDenom, poolI.GetSpreadFactor(ctx)) + spreadFactor := poolI.GetSpreadFactor(ctx) + + takerFee, err := k.GetTradingPairTakerFee(ctx, routeStep.TokenInDenom, tokenOut.Denom) if err != nil { return nil, err } - insExpected[i] = tokenIn.Amount - tokenOut = tokenIn + tokenIn, err := swapModule.CalcInAmtGivenOut(ctx, poolI, tokenOut, routeStep.TokenInDenom, spreadFactor) + if err != nil { + return nil, err + } + + tokenInAfterTakerFee, _ := k.calcTakerFeeExactOut(tokenIn, takerFee) + + insExpected[i] = tokenInAfterTakerFee.Amount + tokenOut = tokenInAfterTakerFee } return insExpected, nil @@ -697,13 +779,23 @@ func (k Keeper) createOsmoMultihopExpectedSwapOuts( } spreadFactor := poolI.GetSpreadFactor(ctx) - tokenIn, err := swapModule.CalcInAmtGivenOut(ctx, poolI, tokenOut, routeStep.TokenInDenom, cumulativeRouteSpreadFactor.Mul((spreadFactor.Quo(sumOfSpreadFactors)))) + + takerFee, err := k.GetTradingPairTakerFee(ctx, routeStep.TokenInDenom, tokenOut.Denom) + if err != nil { + return nil, err + } + + osmoDiscountedSpreadFactor := cumulativeRouteSpreadFactor.Mul((spreadFactor.Quo(sumOfSpreadFactors))) + + tokenIn, err := swapModule.CalcInAmtGivenOut(ctx, poolI, tokenOut, routeStep.TokenInDenom, osmoDiscountedSpreadFactor) if err != nil { return nil, err } - insExpected[i] = tokenIn.Amount - tokenOut = tokenIn + tokenInAfterTakerFee, _ := k.calcTakerFeeExactOut(tokenIn, takerFee) + + insExpected[i] = tokenInAfterTakerFee.Amount + tokenOut = tokenInAfterTakerFee } return insExpected, nil @@ -741,3 +833,113 @@ func (k Keeper) TotalLiquidity(ctx sdk.Context) (sdk.Coins, error) { totalLiquidity := totalGammLiquidity.Add(totalConcentratedLiquidity...).Add(totalCosmwasmLiquidity...) return totalLiquidity, nil } +<<<<<<< HEAD +======= + +// isDenomWhitelisted checks if the denom provided exists in the list of authorized quote denoms. +// If it does, it returns true, otherwise false. +func isDenomWhitelisted(denom string, authorizedQuoteDenoms []string) bool { + for _, authorizedQuoteDenom := range authorizedQuoteDenoms { + if denom == authorizedQuoteDenom { + return true + } + } + return false +} + +// nolint: unused +// trackVolume converts the input token into OSMO units and adds it to the global tracked volume for the given pool ID. +// Fails quietly if an OSMO paired pool cannot be found, although this should only happen in rare scenarios where OSMO is +// removed as a base denom from the protorev module (which this function relies on). +// +// CONTRACT: `volumeGenerated` corresponds to one of the denoms in the pool +// CONTRACT: pool with `poolId` exists +func (k Keeper) trackVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) { + // If the denom is already denominated in uosmo, we can just use it directly + OSMO := k.stakingKeeper.BondDenom(ctx) + if volumeGenerated.Denom == OSMO { + k.addVolume(ctx, poolId, volumeGenerated) + return + } + + // Get the most liquid OSMO-paired pool with `volumeGenerated`'s denom using `GetPoolForDenomPair` + osmoPairedPoolId, err := k.protorevKeeper.GetPoolForDenomPair(ctx, OSMO, volumeGenerated.Denom) + + // If no pool is found, fail quietly. + // + // This is a rare scenario that should only happen if OSMO-paired pools are all removed from the protorev module. + // Since this removal scenario is all-or-nothing, this is functionally equiavalent to freezing the tracked volume amounts + // where they were prior to the disabling, which seems an appropriate response. + // + // This branch would also get triggered in the case where there is a token that has no OSMO-paired pool on the entire chain. + // We simply do not track volume in these cases. Importantly, volume splitting gauge logic should prevent a gauge from being + // created for such a pool that includes such a token, although it is okay to no-op in these cases regardless. + if err != nil { + return + } + + // Since we want to ultimately multiply the volume by this spot price, we want to quote OSMO in terms of the input token. + // This is so that once we multiply the volume by the spot price, we get the volume in units of OSMO. + osmoPerInputToken, err := k.RouteCalculateSpotPrice(ctx, osmoPairedPoolId, OSMO, volumeGenerated.Denom) + + // We expect that if a pool is found, there should always be an available spot price as well. + // That being said, if there is an error finding the spot price, we fail quietly and leave tracked volume unchanged. + // This is because we do not want to escalate an issue with finding spot price to locking all swaps involving the given asset. + if err != nil { + return + } + + // Multiply `volumeGenerated.Amount.ToDec()` by this spot price. + // While rounding does not particularly matter here, we round down to ensure that we do not overcount volume. + volumeInOsmo := volumeGenerated.Amount.ToDec().Mul(osmoPerInputToken).TruncateInt() + + // Add this new volume to the global tracked volume for the pool ID + k.addVolume(ctx, poolId, sdk.NewCoin(OSMO, volumeInOsmo)) +} + +// nolint: unused +// addVolume adds the given volume to the global tracked volume for the given pool ID. +func (k Keeper) addVolume(ctx sdk.Context, poolId uint64, volumeGenerated sdk.Coin) { + // Get the current volume for the pool ID + currentTotalVolume := k.GetTotalVolumeForPool(ctx, poolId) + + // Add newly generated volume to existing volume and set updated volume in state + newTotalVolume := currentTotalVolume.Add(volumeGenerated) + k.setVolume(ctx, poolId, newTotalVolume) +} + +// nolint: unused +// setVolume sets the given volume to the global tracked volume for the given pool ID. +func (k Keeper) setVolume(ctx sdk.Context, poolId uint64, totalVolume sdk.Coins) { + storedVolume := types.TrackedVolume{Amount: totalVolume} + osmoutils.MustSet(ctx.KVStore(k.storeKey), types.KeyPoolVolume(poolId), &storedVolume) +} + +// GetTotalVolumeForPool gets the total historical volume in all supported denominations for a given pool ID. +func (k Keeper) GetTotalVolumeForPool(ctx sdk.Context, poolId uint64) sdk.Coins { + var currentTrackedVolume types.TrackedVolume + volumeFound, err := osmoutils.Get(ctx.KVStore(k.storeKey), types.KeyPoolVolume(poolId), ¤tTrackedVolume) + if err != nil { + // We can only encounter an error if a database or serialization errors occurs, so we panic here. + // Normally this would be handled by `osmoutils.MustGet`, but since we want to specifically use `osmoutils.Get`, + // we also have to manually panic here. + panic(err) + } + + // If no volume was found, we treat the existing volume as 0. + // While we can technically require volume to exist, we would need to store empty coins in state for each pool (past and present), + // which is a high storage cost to pay for a weak guardrail. + currentTotalVolume := sdk.NewCoins() + if volumeFound { + currentTotalVolume = currentTrackedVolume.Amount + } + + return currentTotalVolume +} + +// GetOsmoVolumeForPool gets the total OSMO-denominated historical volume for a given pool ID. +func (k Keeper) GetOsmoVolumeForPool(ctx sdk.Context, poolId uint64) sdk.Int { + totalVolume := k.GetTotalVolumeForPool(ctx, poolId) + return totalVolume.AmountOf(k.stakingKeeper.BondDenom(ctx)) +} +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) diff --git a/x/poolmanager/router_test.go b/x/poolmanager/router_test.go index f352ca3b904..e200ad42ec7 100644 --- a/x/poolmanager/router_test.go +++ b/x/poolmanager/router_test.go @@ -675,7 +675,7 @@ func (s *KeeperTestSuite) TestMultihopSwapExactAmountIn() { s.Require().Error(err) } else { // calculate the swap as separate swaps with either the reduced swap fee or normal fee - expectedMultihopTokenOutAmount := s.calcInAmountAsSeparatePoolSwaps(tc.expectReducedFeeApplied, tc.routes, tc.tokenIn) + expectedMultihopTokenOutAmount := s.calcOutGivenInAmountAsSeparatePoolSwaps(tc.expectReducedFeeApplied, tc.routes, tc.tokenIn) // execute the swap multihopTokenOutAmount, err := poolmanagerKeeper.RouteExactAmountIn(s.Ctx, s.TestAccs[0], tc.routes, tc.tokenIn, tc.tokenOutMinAmount) @@ -967,12 +967,12 @@ func (s *KeeperTestSuite) TestMultihopSwapExactAmountOut() { s.Require().Error(err) } else { // calculate the swap as separate swaps with either the reduced swap fee or normal fee - expectedMultihopTokenOutAmount := s.calcOutAmountAsSeparateSwaps(tc.expectReducedFeeApplied, tc.routes, tc.tokenOut) + expectedMultihopTokenInAmount := s.calcInGivenOutAmountAsSeparateSwaps(tc.expectReducedFeeApplied, tc.routes, tc.tokenOut) // execute the swap - multihopTokenOutAmount, err := poolmanagerKeeper.RouteExactAmountOut(s.Ctx, s.TestAccs[0], tc.routes, tc.tokenInMaxAmount, tc.tokenOut) + multihopTokenInAmount, err := poolmanagerKeeper.RouteExactAmountOut(s.Ctx, s.TestAccs[0], tc.routes, tc.tokenInMaxAmount, tc.tokenOut) // compare the expected tokenOut to the actual tokenOut s.Require().NoError(err) - s.Require().Equal(expectedMultihopTokenOutAmount.Amount.String(), multihopTokenOutAmount.String()) + s.Require().Equal(expectedMultihopTokenInAmount.Amount.String(), multihopTokenInAmount.String()) } }) } @@ -1323,7 +1323,7 @@ func (s *KeeperTestSuite) TestEstimateMultihopSwapExactAmountOut() { if test.expectPass { s.Require().NoError(errMultihop, "test: %v", test.name) s.Require().NoError(errEstimate, "test: %v", test.name) - s.Require().Equal(multihopTokenInAmount, estimateMultihopTokenInAmount) + s.Require().Equal(estimateMultihopTokenInAmount.String(), multihopTokenInAmount.String()) } else { s.Require().Error(errMultihop, "test: %v", test.name) s.Require().Error(errEstimate, "test: %v", test.name) @@ -1354,7 +1354,7 @@ func (s *KeeperTestSuite) makeGaugesIncentivized(incentivizedGauges []uint64) { s.App.PoolIncentivesKeeper.SetDistrInfo(s.Ctx, distInfo) } -func (s *KeeperTestSuite) calcOutAmountAsSeparateSwaps(osmoFeeReduced bool, routes []types.SwapAmountOutRoute, tokenOut sdk.Coin) sdk.Coin { +func (s *KeeperTestSuite) calcInGivenOutAmountAsSeparateSwaps(osmoFeeReduced bool, routes []types.SwapAmountOutRoute, tokenOut sdk.Coin) sdk.Coin { cacheCtx, _ := s.Ctx.CacheContext() if osmoFeeReduced { // extract route from swap @@ -1374,13 +1374,20 @@ func (s *KeeperTestSuite) calcOutAmountAsSeparateSwaps(osmoFeeReduced bool, rout // utilize the routeSpreadFactor, sumOfSpreadFactors, and current pool swap fee to calculate the new reduced swap fee spreadFactor := routeSpreadFactor.Mul((currentPoolSpreadFactor.Quo(sumOfSpreadFactors))) + takerFee, err := s.App.PoolManagerKeeper.GetTradingPairTakerFee(cacheCtx, hop.TokenInDenom, nextTokenOut.Denom) + s.Require().NoError(err) + swapModule, err := s.App.PoolManagerKeeper.GetPoolModule(cacheCtx, hop.PoolId) s.Require().NoError(err) // we then do individual swaps until we reach the end of the swap route - tokenOut, err := swapModule.SwapExactAmountOut(cacheCtx, s.TestAccs[0], hopPool, hop.TokenInDenom, sdk.NewInt(100000000), nextTokenOut, spreadFactor) + tokenInAmt, err := swapModule.SwapExactAmountOut(cacheCtx, s.TestAccs[0], hopPool, hop.TokenInDenom, sdk.NewInt(100000000), nextTokenOut, spreadFactor) s.Require().NoError(err) - nextTokenOut = sdk.NewCoin(hop.TokenInDenom, tokenOut) + + tokenInCoin := sdk.NewCoin(hop.TokenInDenom, tokenInAmt) + tokenInCoinAfterAddTakerFee, _ := s.App.PoolManagerKeeper.CalcTakerFeeExactOut(tokenInCoin, takerFee) + + nextTokenOut = tokenInCoinAfterAddTakerFee } return nextTokenOut } else { @@ -1391,21 +1398,28 @@ func (s *KeeperTestSuite) calcOutAmountAsSeparateSwaps(osmoFeeReduced bool, rout s.Require().NoError(err) updatedPoolSpreadFactor := hopPool.GetSpreadFactor(cacheCtx) + takerFee, err := s.App.PoolManagerKeeper.GetTradingPairTakerFee(cacheCtx, hop.TokenInDenom, nextTokenOut.Denom) + s.Require().NoError(err) + swapModule, err := s.App.PoolManagerKeeper.GetPoolModule(cacheCtx, hop.PoolId) s.Require().NoError(err) - tokenOut, err := swapModule.SwapExactAmountOut(cacheCtx, s.TestAccs[0], hopPool, hop.TokenInDenom, sdk.NewInt(100000000), nextTokenOut, updatedPoolSpreadFactor) + tokenInAmt, err := swapModule.SwapExactAmountOut(cacheCtx, s.TestAccs[0], hopPool, hop.TokenInDenom, sdk.NewInt(100000000), nextTokenOut, updatedPoolSpreadFactor) s.Require().NoError(err) - nextTokenOut = sdk.NewCoin(hop.TokenInDenom, tokenOut) + + tokenInCoin := sdk.NewCoin(hop.TokenInDenom, tokenInAmt) + tokenInCoinAfterAddTakerFee, _ := s.App.PoolManagerKeeper.CalcTakerFeeExactOut(tokenInCoin, takerFee) + + nextTokenOut = tokenInCoinAfterAddTakerFee } return nextTokenOut } } -// calcInAmountAsSeparatePoolSwaps calculates the output amount of a series of swaps on PoolManager pools while factoring in reduces swap fee changes. +// calcOutGivenInAmountAsSeparatePoolSwaps calculates the output amount of a series of swaps on PoolManager pools while factoring in reduces swap fee changes. // If its GAMM pool functions directly to ensure the poolmanager functions route to the correct modules. It it's CL pool functions directly to ensure the // poolmanager functions route to the correct modules. -func (s *KeeperTestSuite) calcInAmountAsSeparatePoolSwaps(osmoFeeReduced bool, routes []types.SwapAmountInRoute, tokenIn sdk.Coin) sdk.Coin { +func (s *KeeperTestSuite) calcOutGivenInAmountAsSeparatePoolSwaps(osmoFeeReduced bool, routes []types.SwapAmountInRoute, tokenIn sdk.Coin) sdk.Coin { cacheCtx, _ := s.Ctx.CacheContext() if osmoFeeReduced { // extract route from swap @@ -1427,8 +1441,13 @@ func (s *KeeperTestSuite) calcInAmountAsSeparatePoolSwaps(osmoFeeReduced bool, r // utilize the routeSpreadFactor, sumOfSpreadFactors, and current pool swap fee to calculate the new reduced swap fee spreadFactor := routeSpreadFactor.Mul(pool.GetSpreadFactor(cacheCtx).Quo(sumOfSpreadFactors)) + takerFee, err := s.App.PoolManagerKeeper.GetTradingPairTakerFee(cacheCtx, hop.TokenOutDenom, nextTokenIn.Denom) + s.Require().NoError(err) + + nextTokenInAfterSubTakerFee, _ := s.App.PoolManagerKeeper.CalcTakerFeeExactIn(nextTokenIn, takerFee) + // we then do individual swaps until we reach the end of the swap route - tokenOut, err := swapModule.SwapExactAmountIn(cacheCtx, s.TestAccs[0], pool, nextTokenIn, hop.TokenOutDenom, sdk.OneInt(), spreadFactor) + tokenOut, err := swapModule.SwapExactAmountIn(cacheCtx, s.TestAccs[0], pool, nextTokenInAfterSubTakerFee, hop.TokenOutDenom, sdk.OneInt(), spreadFactor) s.Require().NoError(err) nextTokenIn = sdk.NewCoin(hop.TokenOutDenom, tokenOut) @@ -1446,8 +1465,13 @@ func (s *KeeperTestSuite) calcInAmountAsSeparatePoolSwaps(osmoFeeReduced bool, r // utilize the routeSpreadFactor, sumOfSpreadFactors, and current pool swap fee to calculate the new reduced swap fee spreadFactor := pool.GetSpreadFactor(cacheCtx) + takerFee, err := s.App.PoolManagerKeeper.GetTradingPairTakerFee(cacheCtx, hop.TokenOutDenom, nextTokenIn.Denom) + s.Require().NoError(err) + + nextTokenInAfterSubTakerFee, _ := s.App.PoolManagerKeeper.CalcTakerFeeExactIn(nextTokenIn, takerFee) + // we then do individual swaps until we reach the end of the swap route - tokenOut, err := swapModule.SwapExactAmountIn(cacheCtx, s.TestAccs[0], pool, nextTokenIn, hop.TokenOutDenom, sdk.OneInt(), spreadFactor) + tokenOut, err := swapModule.SwapExactAmountIn(cacheCtx, s.TestAccs[0], pool, nextTokenInAfterSubTakerFee, hop.TokenOutDenom, sdk.OneInt(), spreadFactor) s.Require().NoError(err) nextTokenIn = sdk.NewCoin(hop.TokenOutDenom, tokenOut) @@ -1468,16 +1492,34 @@ func (s *KeeperTestSuite) TestSingleSwapExactAmountIn() { tokenOutDenom string tokenOutMinAmount sdk.Int expectedTokenOutAmount sdk.Int + swapWithNoTakerFee bool expectError bool }{ - // We have: + // Swap with taker fee: + // - foo: 1000000000000 + // - bar: 1000000000000 + // - spreadFactor: 0.1% + // - takerFee: 0.15% + // - foo in: 100000 + // - bar amount out will be calculated according to the formula + // https://www.wolframalpha.com/input?i=solve+%2810%5E12+%2B+10%5E5+x+0.9975%29%2810%5E12+-+x%29+%3D+10%5E24 + { + name: "Swap - [foo -> bar], 0.1 percent fee", + poolId: 1, + poolCoins: sdk.NewCoins(sdk.NewCoin(foo, defaultInitPoolAmount), sdk.NewCoin(bar, defaultInitPoolAmount)), + poolFee: defaultPoolSpreadFactor, + tokenIn: sdk.NewCoin(foo, sdk.NewInt(100000)), + tokenOutMinAmount: sdk.NewInt(1), + tokenOutDenom: bar, + expectedTokenOutAmount: sdk.NewInt(99750), + }, + // Swap with no taker fee: // - foo: 1000000000000 // - bar: 1000000000000 // - spreadFactor: 0.1% // - foo in: 100000 // - bar amount out will be calculated according to the formula // https://www.wolframalpha.com/input?i=solve+%2810%5E12+%2B+10%5E5+x+0.999%29%2810%5E12+-+x%29+%3D+10%5E24 - // We round down the token amount out, get the result is 99899 { name: "Swap - [foo -> bar], 0.1 percent fee", poolId: 1, @@ -1486,6 +1528,7 @@ func (s *KeeperTestSuite) TestSingleSwapExactAmountIn() { tokenIn: sdk.NewCoin(foo, sdk.NewInt(100000)), tokenOutMinAmount: sdk.NewInt(1), tokenOutDenom: bar, + swapWithNoTakerFee: true, expectedTokenOutAmount: sdk.NewInt(99899), }, { @@ -1532,7 +1575,13 @@ func (s *KeeperTestSuite) TestSingleSwapExactAmountIn() { }) // execute the swap - multihopTokenOutAmount, err := poolmanagerKeeper.SwapExactAmountIn(s.Ctx, s.TestAccs[0], tc.poolId, tc.tokenIn, tc.tokenOutDenom, tc.tokenOutMinAmount) + var multihopTokenOutAmount sdk.Int + var err error + if tc.swapWithNoTakerFee { + multihopTokenOutAmount, err = poolmanagerKeeper.SwapExactAmountInNoTakerFee(s.Ctx, s.TestAccs[0], tc.poolId, tc.tokenIn, tc.tokenOutDenom, tc.tokenOutMinAmount) + } else { + multihopTokenOutAmount, err = poolmanagerKeeper.SwapExactAmountIn(s.Ctx, s.TestAccs[0], tc.poolId, tc.tokenIn, tc.tokenOutDenom, tc.tokenOutMinAmount) + } if tc.expectError { s.Require().Error(err) } else { @@ -1847,7 +1896,7 @@ func (s *KeeperTestSuite) TestSplitRouteExactAmountIn() { TokenInAmount: sdk.NewInt(twentyFiveBaseUnitsAmount.Int64() * 3), } - priceImpactThreshold = sdk.NewInt(97866545) + priceImpactThreshold = sdk.NewInt(97469586) ) tests := map[string]struct { @@ -2047,7 +2096,7 @@ func (s *KeeperTestSuite) TestSplitRouteExactAmountOut() { TokenOutAmount: sdk.NewInt(twentyFiveBaseUnitsAmount.Int64() * 3), } - priceImpactThreshold = sdk.NewInt(102239504) + priceImpactThreshold = sdk.NewInt(102666473) ) tests := map[string]struct { diff --git a/x/poolmanager/taker_fee.go b/x/poolmanager/taker_fee.go new file mode 100644 index 00000000000..cfa9222e36d --- /dev/null +++ b/x/poolmanager/taker_fee.go @@ -0,0 +1,185 @@ +package poolmanager + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + appparams "github.com/osmosis-labs/osmosis/v19/app/params" + + "github.com/osmosis-labs/osmosis/osmoutils" + "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" + txfeestypes "github.com/osmosis-labs/osmosis/v19/x/txfees/types" +) + +// SetDenomPairTakerFee sets the taker fee for the given trading pair. +// If the taker fee for this denom pair matches the default taker fee, then +// it is deleted from state. +func (k Keeper) SetDenomPairTakerFee(ctx sdk.Context, denom0, denom1 string, takerFee sdk.Dec) { + store := ctx.KVStore(k.storeKey) + if takerFee.Equal(k.GetParams(ctx).TakerFeeParams.DefaultTakerFee) { + store.Delete(types.FormatDenomTradePairKey(denom0, denom1)) + return + } else { + osmoutils.MustSetDec(store, types.FormatDenomTradePairKey(denom0, denom1), takerFee) + } +} + +// SenderValidationSetDenomPairTakerFee sets the taker fee for the given trading pair iff the sender's address +// also exists in the pool manager taker fee admin address list. +func (k Keeper) SenderValidationSetDenomPairTakerFee(ctx sdk.Context, sender, denom0, denom1 string, takerFee sdk.Dec) error { + adminAddresses := k.GetParams(ctx).TakerFeeParams.AdminAddresses + isAdmin := false + for _, admin := range adminAddresses { + if admin == sender { + isAdmin = true + break + } + } + if !isAdmin { + return fmt.Errorf("%s is not in the pool manager taker fee admin address list", sender) + } + + k.SetDenomPairTakerFee(ctx, denom0, denom1, takerFee) + + ctx.EventManager().EmitEvents(sdk.Events{ + sdk.NewEvent( + types.TypeMsgSetDenomPairTakerFee, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, sender), + sdk.NewAttribute(types.AttributeKeyDenom0, denom0), + sdk.NewAttribute(types.AttributeKeyDenom1, denom1), + sdk.NewAttribute(types.AttributeKeyTakerFee, takerFee.String()), + ), + }) + + return nil +} + +// GetTradingPairTakerFee returns the taker fee for the given trading pair. +// If the trading pair does not exist, it returns the default taker fee. +func (k Keeper) GetTradingPairTakerFee(ctx sdk.Context, denom0, denom1 string) (sdk.Dec, error) { + store := ctx.KVStore(k.storeKey) + key := types.FormatDenomTradePairKey(denom0, denom1) + + takerFee := &sdk.DecProto{} + found, err := osmoutils.Get(store, key, takerFee) + if err != nil { + return sdk.Dec{}, err + } + if !found { + return k.GetParams(ctx).TakerFeeParams.DefaultTakerFee, nil + } + + return takerFee.Dec, nil +} + +// chargeTakerFee extracts the taker fee from the given tokenIn and sends it to the appropriate +// module account. It returns the tokenIn after the taker fee has been extracted. +func (k Keeper) chargeTakerFee(ctx sdk.Context, tokenIn sdk.Coin, tokenOutDenom string, sender sdk.AccAddress, exactIn bool) (sdk.Coin, error) { + feeCollectorForStakingRewardsName := txfeestypes.FeeCollectorForStakingRewardsName + feeCollectorForCommunityPoolName := txfeestypes.FeeCollectorForCommunityPoolName + defaultTakerFeeDenom := appparams.BaseCoinUnit + poolManagerParams := k.GetParams(ctx) + + takerFee, err := k.GetTradingPairTakerFee(ctx, tokenIn.Denom, tokenOutDenom) + if err != nil { + return sdk.Coin{}, err + } + + var tokenInAfterTakerFee sdk.Coin + var takerFeeCoin sdk.Coin + if exactIn { + tokenInAfterTakerFee, takerFeeCoin = k.calcTakerFeeExactIn(tokenIn, takerFee) + } else { + tokenInAfterTakerFee, takerFeeCoin = k.calcTakerFeeExactOut(tokenIn, takerFee) + } + + // N.B. We truncate from the community pool calculation, then remove that from the total, and use the remaining for staking rewards. + // If we truncate both, these can leave tokens in the users wallet when swapping and exact amount in, which is bad UX. + + // We determine the distributution of the taker fee based on its denom + // If the denom is the base denom: + takerFeeAmtRemaining := takerFeeCoin.Amount + if takerFeeCoin.Denom == defaultTakerFeeDenom { + // Community Pool: + if poolManagerParams.TakerFeeParams.OsmoTakerFeeDistribution.CommunityPool.GT(sdk.ZeroDec()) { + // Osmo community pool funds is a direct send + osmoTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToDec().Mul(poolManagerParams.TakerFeeParams.OsmoTakerFeeDistribution.CommunityPool) + osmoTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(defaultTakerFeeDenom, osmoTakerFeeToCommunityPoolDec.TruncateInt())) + err := k.communityPoolKeeper.FundCommunityPool(ctx, osmoTakerFeeToCommunityPoolCoins, sender) + if err != nil { + return sdk.Coin{}, err + } + takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(osmoTakerFeeToCommunityPoolCoins.AmountOf(defaultTakerFeeDenom)) + } + // Staking Rewards: + if poolManagerParams.TakerFeeParams.OsmoTakerFeeDistribution.StakingRewards.GT(sdk.ZeroDec()) { + // Osmo staking rewards funds are sent to the non native fee pool module account (even though its native, we want to distribute at the same time as the non native fee tokens) + // We could stream these rewards via the fee collector account, but this is decision to be made by governance. + osmoTakerFeeToStakingRewardsCoins := sdk.NewCoins(sdk.NewCoin(defaultTakerFeeDenom, takerFeeAmtRemaining)) + err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForStakingRewardsName, osmoTakerFeeToStakingRewardsCoins) + if err != nil { + return sdk.Coin{}, err + } + } + + // If the denom is not the base denom: + } else { + // Community Pool: + if poolManagerParams.TakerFeeParams.NonOsmoTakerFeeDistribution.CommunityPool.GT(sdk.ZeroDec()) { + denomIsWhitelisted := isDenomWhitelisted(takerFeeCoin.Denom, poolManagerParams.AuthorizedQuoteDenoms) + // If the non osmo denom is a whitelisted quote asset, we send to the community pool + if denomIsWhitelisted { + nonOsmoTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToDec().Mul(poolManagerParams.TakerFeeParams.NonOsmoTakerFeeDistribution.CommunityPool) + nonOsmoTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(tokenIn.Denom, nonOsmoTakerFeeToCommunityPoolDec.TruncateInt())) + err := k.communityPoolKeeper.FundCommunityPool(ctx, nonOsmoTakerFeeToCommunityPoolCoins, sender) + if err != nil { + return sdk.Coin{}, err + } + takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(nonOsmoTakerFeeToCommunityPoolCoins.AmountOf(tokenIn.Denom)) + } else { + // If the non osmo denom is not a whitelisted asset, we send to the non native fee pool for community pool module account. + // At epoch, this account swaps the non native, non whitelisted assets for XXX and sends to the community pool. + nonOsmoTakerFeeToCommunityPoolDec := takerFeeAmtRemaining.ToDec().Mul(poolManagerParams.TakerFeeParams.NonOsmoTakerFeeDistribution.CommunityPool) + nonOsmoTakerFeeToCommunityPoolCoins := sdk.NewCoins(sdk.NewCoin(tokenIn.Denom, nonOsmoTakerFeeToCommunityPoolDec.TruncateInt())) + err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForCommunityPoolName, nonOsmoTakerFeeToCommunityPoolCoins) + if err != nil { + return sdk.Coin{}, err + } + takerFeeAmtRemaining = takerFeeAmtRemaining.Sub(nonOsmoTakerFeeToCommunityPoolCoins.AmountOf(tokenIn.Denom)) + } + } + // Staking Rewards: + if poolManagerParams.TakerFeeParams.NonOsmoTakerFeeDistribution.StakingRewards.GT(sdk.ZeroDec()) { + // Non Osmo staking rewards are sent to the non native fee pool module account + nonOsmoTakerFeeToStakingRewardsCoins := sdk.NewCoins(sdk.NewCoin(takerFeeCoin.Denom, takerFeeAmtRemaining)) + err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, feeCollectorForStakingRewardsName, nonOsmoTakerFeeToStakingRewardsCoins) + if err != nil { + return sdk.Coin{}, err + } + } + } + + return tokenInAfterTakerFee, nil +} + +// Returns remaining amount in to swap, and takerFeeCoins. +// returns (1 - takerFee) * tokenIn, takerFee * tokenIn +func (k Keeper) calcTakerFeeExactIn(tokenIn sdk.Coin, takerFee sdk.Dec) (sdk.Coin, sdk.Coin) { + amountInAfterSubTakerFee := tokenIn.Amount.ToDec().MulTruncate(sdk.OneDec().Sub(takerFee)) + tokenInAfterSubTakerFee := sdk.NewCoin(tokenIn.Denom, amountInAfterSubTakerFee.TruncateInt()) + takerFeeCoin := sdk.NewCoin(tokenIn.Denom, tokenIn.Amount.Sub(tokenInAfterSubTakerFee.Amount)) + + return tokenInAfterSubTakerFee, takerFeeCoin +} + +// Returns takerFee adjusted required amountIn, and takerFeeCoins. +// Computed as tokenIn / (1 - takerFee), tokenIn * (1 - / (1 - takerFee)) +func (k Keeper) calcTakerFeeExactOut(tokenIn sdk.Coin, takerFee sdk.Dec) (sdk.Coin, sdk.Coin) { + amountInAfterAddTakerFee := tokenIn.Amount.ToDec().Quo(sdk.OneDec().Sub(takerFee)) + tokenInAfterAddTakerFee := sdk.NewCoin(tokenIn.Denom, amountInAfterAddTakerFee.RoundInt()) + takerFeeCoin := sdk.NewCoin(tokenIn.Denom, tokenInAfterAddTakerFee.Amount.Sub(tokenIn.Amount)) + + return tokenInAfterAddTakerFee, takerFeeCoin +} diff --git a/x/poolmanager/types/events.go b/x/poolmanager/types/events.go index a080865806b..0133711ab60 100644 --- a/x/poolmanager/types/events.go +++ b/x/poolmanager/types/events.go @@ -7,4 +7,7 @@ const ( AttributeKeyTokensIn = "tokens_in" AttributeKeyTokensOut = "tokens_out" AttributeKeyPoolId = "pool_id" + AttributeKeyDenom0 = "denom0" + AttributeKeyDenom1 = "denom1" + AttributeKeyTakerFee = "taker_fee" ) diff --git a/x/poolmanager/types/genesis.pb.go b/x/poolmanager/types/genesis.pb.go index 37acb06c930..5e80d223dbf 100644 --- a/x/poolmanager/types/genesis.pb.go +++ b/x/poolmanager/types/genesis.pb.go @@ -31,6 +31,17 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params holds parameters for the poolmanager module type Params struct { PoolCreationFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=pool_creation_fee,json=poolCreationFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"pool_creation_fee" yaml:"pool_creation_fee"` + // taker_fee_params is the container of taker fee parameters. + TakerFeeParams TakerFeeParams `protobuf:"bytes,2,opt,name=taker_fee_params,json=takerFeeParams,proto3" json:"taker_fee_params" yaml:"taker_fee_params"` + // authorized_quote_denoms is a list of quote denoms that can be used as + // token1 when creating a concentrated pool. We limit the quote assets to a + // small set for the purposes of having convinient price increments stemming + // from tick to price conversion. These increments are in a human readable + // magnitude only for token1 as a quote. For limit orders in the future, this + // will be a desirable property in terms of UX as to allow users to set limit + // orders at prices in terms of token1 (quote asset) that are easy to reason + // about. + AuthorizedQuoteDenoms []string `protobuf:"bytes,3,rep,name=authorized_quote_denoms,json=authorizedQuoteDenoms,proto3" json:"authorized_quote_denoms,omitempty" yaml:"authorized_quote_denoms"` } func (m *Params) Reset() { *m = Params{} } @@ -73,6 +84,20 @@ func (m *Params) GetPoolCreationFee() github_com_cosmos_cosmos_sdk_types.Coins { return nil } +func (m *Params) GetTakerFeeParams() TakerFeeParams { + if m != nil { + return m.TakerFeeParams + } + return TakerFeeParams{} +} + +func (m *Params) GetAuthorizedQuoteDenoms() []string { + if m != nil { + return m.AuthorizedQuoteDenoms + } + return nil +} + // GenesisState defines the poolmanager module's genesis state. type GenesisState struct { // the next_pool_id @@ -137,9 +162,145 @@ func (m *GenesisState) GetPoolRoutes() []ModuleRoute { return nil } +// TakerFeeParams consolidates the taker fee parameters for the poolmanager. +type TakerFeeParams struct { + // default_taker_fee is the fee used when creating a new pool that doesn't + // fall under a custom pool taker fee or stableswap taker fee category. + DefaultTakerFee github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=default_taker_fee,json=defaultTakerFee,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"default_taker_fee"` + // osmo_taker_fee_distribution defines the distribution of taker fees + // generated in OSMO. As of this writing, it has two catagories: + // - staking_rewards: the percent of the taker fee that gets distributed to + // stakers. + // - community_pool: the percent of the taker fee that gets sent to the + // community pool. + OsmoTakerFeeDistribution TakerFeeDistributionPercentage `protobuf:"bytes,2,opt,name=osmo_taker_fee_distribution,json=osmoTakerFeeDistribution,proto3" json:"osmo_taker_fee_distribution"` + // non_osmo_taker_fee_distribution defines the distribution of taker fees + // generated in non-OSMO. As of this writing, it has two categories: + // - staking_rewards: the percent of the taker fee that gets swapped to OSMO + // and then distirbuted to stakers. + // - community_pool: the percent of the taker fee that gets sent to the + // community pool. Note: If the non-OSMO asset is an authorized_quote_denom, + // that denom is sent directly to the community pool. Otherwise, it is + // swapped to the community_pool_denom_to_swap_non_whitelisted_assets_to and + // then sent to the community pool as that denom. + NonOsmoTakerFeeDistribution TakerFeeDistributionPercentage `protobuf:"bytes,3,opt,name=non_osmo_taker_fee_distribution,json=nonOsmoTakerFeeDistribution,proto3" json:"non_osmo_taker_fee_distribution"` + // admin_addresses is a list of addresses that are allowed to set and remove + // custom taker fees for denom pairs. Governance also has the ability to set + // and remove custom taker fees for denom pairs, but with the normal + // governance delay. + AdminAddresses []string `protobuf:"bytes,4,rep,name=admin_addresses,json=adminAddresses,proto3" json:"admin_addresses,omitempty" yaml:"admin_addresses"` + // community_pool_denom_to_swap_non_whitelisted_assets_to is the denom that + // non-whitelisted taker fees will be swapped to before being sent to + // the community pool. + CommunityPoolDenomToSwapNonWhitelistedAssetsTo string `protobuf:"bytes,5,opt,name=community_pool_denom_to_swap_non_whitelisted_assets_to,json=communityPoolDenomToSwapNonWhitelistedAssetsTo,proto3" json:"community_pool_denom_to_swap_non_whitelisted_assets_to,omitempty" yaml:"community_pool_denom_to_swap_non_whitelisted_assets_to"` +} + +func (m *TakerFeeParams) Reset() { *m = TakerFeeParams{} } +func (m *TakerFeeParams) String() string { return proto.CompactTextString(m) } +func (*TakerFeeParams) ProtoMessage() {} +func (*TakerFeeParams) Descriptor() ([]byte, []int) { + return fileDescriptor_aa099d9fbdf68b35, []int{2} +} +func (m *TakerFeeParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TakerFeeParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TakerFeeParams.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 *TakerFeeParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_TakerFeeParams.Merge(m, src) +} +func (m *TakerFeeParams) XXX_Size() int { + return m.Size() +} +func (m *TakerFeeParams) XXX_DiscardUnknown() { + xxx_messageInfo_TakerFeeParams.DiscardUnknown(m) +} + +var xxx_messageInfo_TakerFeeParams proto.InternalMessageInfo + +func (m *TakerFeeParams) GetOsmoTakerFeeDistribution() TakerFeeDistributionPercentage { + if m != nil { + return m.OsmoTakerFeeDistribution + } + return TakerFeeDistributionPercentage{} +} + +func (m *TakerFeeParams) GetNonOsmoTakerFeeDistribution() TakerFeeDistributionPercentage { + if m != nil { + return m.NonOsmoTakerFeeDistribution + } + return TakerFeeDistributionPercentage{} +} + +func (m *TakerFeeParams) GetAdminAddresses() []string { + if m != nil { + return m.AdminAddresses + } + return nil +} + +func (m *TakerFeeParams) GetCommunityPoolDenomToSwapNonWhitelistedAssetsTo() string { + if m != nil { + return m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo + } + return "" +} + +// TakerFeeDistributionPercentage defines what percent of the taker fee category +// gets distributed to the available categories. +type TakerFeeDistributionPercentage struct { + StakingRewards github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=staking_rewards,json=stakingRewards,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"staking_rewards" yaml:"staking_rewards"` + CommunityPool github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=community_pool,json=communityPool,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"community_pool" yaml:"community_pool"` +} + +func (m *TakerFeeDistributionPercentage) Reset() { *m = TakerFeeDistributionPercentage{} } +func (m *TakerFeeDistributionPercentage) String() string { return proto.CompactTextString(m) } +func (*TakerFeeDistributionPercentage) ProtoMessage() {} +func (*TakerFeeDistributionPercentage) Descriptor() ([]byte, []int) { + return fileDescriptor_aa099d9fbdf68b35, []int{3} +} +func (m *TakerFeeDistributionPercentage) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TakerFeeDistributionPercentage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TakerFeeDistributionPercentage.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 *TakerFeeDistributionPercentage) XXX_Merge(src proto.Message) { + xxx_messageInfo_TakerFeeDistributionPercentage.Merge(m, src) +} +func (m *TakerFeeDistributionPercentage) XXX_Size() int { + return m.Size() +} +func (m *TakerFeeDistributionPercentage) XXX_DiscardUnknown() { + xxx_messageInfo_TakerFeeDistributionPercentage.DiscardUnknown(m) +} + +var xxx_messageInfo_TakerFeeDistributionPercentage proto.InternalMessageInfo + func init() { proto.RegisterType((*Params)(nil), "osmosis.poolmanager.v1beta1.Params") proto.RegisterType((*GenesisState)(nil), "osmosis.poolmanager.v1beta1.GenesisState") + proto.RegisterType((*TakerFeeParams)(nil), "osmosis.poolmanager.v1beta1.TakerFeeParams") + proto.RegisterType((*TakerFeeDistributionPercentage)(nil), "osmosis.poolmanager.v1beta1.TakerFeeDistributionPercentage") } func init() { @@ -147,34 +308,58 @@ func init() { } var fileDescriptor_aa099d9fbdf68b35 = []byte{ - // 421 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x51, 0xb1, 0x6e, 0xd4, 0x40, - 0x10, 0xf5, 0x92, 0xe8, 0x8a, 0xbd, 0x48, 0x08, 0x8b, 0xc2, 0x09, 0x92, 0xcf, 0x32, 0x8d, 0x29, - 0xb2, 0xab, 0x0b, 0x05, 0x82, 0x8e, 0x8b, 0x04, 0x42, 0x02, 0x11, 0x4c, 0x47, 0x63, 0xad, 0xed, - 0x89, 0xb1, 0xb0, 0x3d, 0x96, 0x77, 0x1d, 0xe5, 0xfe, 0x02, 0x89, 0x9e, 0x0f, 0xe0, 0x3b, 0x28, - 0x52, 0x5e, 0x49, 0x75, 0xa0, 0xbb, 0x3f, 0xe0, 0x0b, 0xd0, 0xae, 0xd7, 0xe8, 0x20, 0xd2, 0x55, - 0xf6, 0xec, 0xbc, 0xf7, 0x66, 0xde, 0x1b, 0xfa, 0x08, 0x65, 0x8d, 0xb2, 0x94, 0xbc, 0x45, 0xac, - 0x6a, 0xd1, 0x88, 0x02, 0x3a, 0x7e, 0x35, 0x4f, 0x41, 0x89, 0x39, 0x2f, 0xa0, 0x01, 0x59, 0x4a, - 0xd6, 0x76, 0xa8, 0xd0, 0x7d, 0x60, 0xa1, 0x6c, 0x07, 0xca, 0x2c, 0xf4, 0xe4, 0x7e, 0x81, 0x05, - 0x1a, 0x1c, 0xd7, 0x7f, 0x03, 0xe5, 0xe4, 0xb8, 0x40, 0x2c, 0x2a, 0xe0, 0xa6, 0x4a, 0xfb, 0x4b, - 0x2e, 0x9a, 0xe5, 0xd8, 0xca, 0x8c, 0x5c, 0x32, 0x70, 0x86, 0xc2, 0xb6, 0xfc, 0xff, 0x59, 0x79, - 0xdf, 0x09, 0x55, 0x62, 0x33, 0xf6, 0x07, 0x34, 0x4f, 0x85, 0x84, 0xbf, 0xbb, 0x66, 0x58, 0x8e, - 0x7d, 0xb6, 0xcf, 0x53, 0x8d, 0x79, 0x5f, 0x41, 0xd2, 0x61, 0xaf, 0x60, 0xc0, 0x87, 0x5f, 0x09, - 0x9d, 0x5c, 0x88, 0x4e, 0xd4, 0xd2, 0xfd, 0x42, 0xe8, 0x3d, 0xcd, 0x4a, 0xb2, 0x0e, 0xcc, 0xc8, - 0xe4, 0x12, 0xc0, 0x23, 0xc1, 0x41, 0x34, 0x3d, 0x3b, 0x66, 0x76, 0x4b, 0x3d, 0x77, 0x34, 0xce, - 0xce, 0xb1, 0x6c, 0x16, 0xaf, 0x6f, 0xd6, 0x33, 0xe7, 0xf7, 0x7a, 0xe6, 0x2d, 0x45, 0x5d, 0x3d, - 0x0b, 0x6f, 0x29, 0x84, 0xdf, 0x7e, 0xce, 0xa2, 0xa2, 0x54, 0x1f, 0xfb, 0x94, 0x65, 0x58, 0x5b, - 0xbb, 0xf6, 0x73, 0x2a, 0xf3, 0x4f, 0x5c, 0x2d, 0x5b, 0x90, 0x46, 0x4c, 0xc6, 0x77, 0x35, 0xff, - 0xdc, 0xd2, 0x5f, 0x00, 0x84, 0xdf, 0x09, 0x3d, 0x7a, 0x39, 0xdc, 0xe2, 0xbd, 0x12, 0x0a, 0xdc, - 0x80, 0x1e, 0x35, 0x70, 0xad, 0x12, 0x33, 0xa8, 0xcc, 0x3d, 0x12, 0x90, 0xe8, 0x30, 0xa6, 0xfa, - 0xed, 0x02, 0xb1, 0x7a, 0x95, 0xbb, 0xcf, 0xe9, 0xa4, 0x35, 0x96, 0xbc, 0x3b, 0x01, 0x89, 0xa6, - 0x67, 0x0f, 0xd9, 0x9e, 0xeb, 0xb1, 0xc1, 0xfd, 0xe2, 0x50, 0xdb, 0x88, 0x2d, 0xd1, 0x7d, 0x4b, - 0xa7, 0x46, 0xdf, 0x44, 0x25, 0xbd, 0x03, 0x13, 0x42, 0xb4, 0x57, 0xe7, 0x8d, 0x09, 0x37, 0xd6, - 0x04, 0x2b, 0x46, 0x35, 0xcc, 0x3c, 0xc8, 0xc5, 0xbb, 0x9b, 0x8d, 0x4f, 0x56, 0x1b, 0x9f, 0xfc, - 0xda, 0xf8, 0xe4, 0xf3, 0xd6, 0x77, 0x56, 0x5b, 0xdf, 0xf9, 0xb1, 0xf5, 0x9d, 0x0f, 0x4f, 0x76, - 0xb2, 0xb1, 0xfa, 0xa7, 0x95, 0x48, 0xe5, 0x58, 0xf0, 0xab, 0xf9, 0x53, 0x7e, 0xfd, 0xcf, 0x3d, - 0x4d, 0x60, 0xe9, 0xc4, 0x5c, 0xf0, 0xf1, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x16, 0x38, 0x17, - 0x2f, 0xc7, 0x02, 0x00, 0x00, + // 810 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0x26, 0x69, 0xa4, 0x8c, 0x8b, 0x4d, 0x57, 0x94, 0x6e, 0x13, 0x69, 0xd7, 0xda, 0x4a, + 0xc8, 0x08, 0x75, 0x57, 0x09, 0x12, 0x08, 0x10, 0x07, 0x3b, 0x56, 0x0b, 0x12, 0xb4, 0xe9, 0x36, + 0x12, 0x52, 0x2f, 0xa3, 0xb1, 0xf7, 0x65, 0xb3, 0xca, 0xee, 0x8c, 0xbb, 0x33, 0x1b, 0xd7, 0x5c, + 0xf9, 0x02, 0x48, 0x5c, 0x39, 0x73, 0xe0, 0xc6, 0x37, 0xe0, 0xc0, 0xa1, 0xc7, 0x1e, 0x11, 0x87, + 0x05, 0x39, 0xdf, 0xc0, 0x9f, 0x00, 0xcd, 0x1f, 0xc7, 0xd9, 0x50, 0x5b, 0x0d, 0x3d, 0xd9, 0xf3, + 0xde, 0xfb, 0xfd, 0xde, 0x7b, 0xbf, 0xfd, 0x8d, 0x06, 0x7d, 0xc8, 0x78, 0xce, 0x78, 0xca, 0xc3, + 0x11, 0x63, 0x59, 0x4e, 0x28, 0x49, 0xa0, 0x08, 0xcf, 0xf6, 0x06, 0x20, 0xc8, 0x5e, 0x98, 0x00, + 0x05, 0x9e, 0xf2, 0x60, 0x54, 0x30, 0xc1, 0xec, 0x5d, 0x53, 0x1a, 0x5c, 0x2a, 0x0d, 0x4c, 0xe9, + 0xce, 0x7b, 0x09, 0x4b, 0x98, 0xaa, 0x0b, 0xe5, 0x3f, 0x0d, 0xd9, 0xb9, 0x9b, 0x30, 0x96, 0x64, + 0x10, 0xaa, 0xd3, 0xa0, 0x3c, 0x0e, 0x09, 0x9d, 0xcc, 0x53, 0x43, 0x45, 0x87, 0x35, 0x46, 0x1f, + 0x4c, 0xca, 0xbd, 0x8a, 0x8a, 0xcb, 0x82, 0x88, 0x94, 0xd1, 0x79, 0x5e, 0x57, 0x87, 0x03, 0xc2, + 0xe1, 0x62, 0xd6, 0x21, 0x4b, 0xe7, 0xf9, 0x60, 0xd5, 0x4e, 0x39, 0x8b, 0xcb, 0x0c, 0x70, 0xc1, + 0x4a, 0x01, 0xba, 0xde, 0x9f, 0xad, 0xa3, 0xad, 0x43, 0x52, 0x90, 0x9c, 0xdb, 0x3f, 0x59, 0xe8, + 0x96, 0x44, 0xe1, 0x61, 0x01, 0xaa, 0x25, 0x3e, 0x06, 0x70, 0xac, 0xf6, 0x46, 0xa7, 0xb1, 0x7f, + 0x37, 0x30, 0x53, 0xca, 0xbe, 0xf3, 0xc5, 0x83, 0x03, 0x96, 0xd2, 0xde, 0x37, 0x2f, 0x2b, 0x6f, + 0x6d, 0x56, 0x79, 0xce, 0x84, 0xe4, 0xd9, 0xe7, 0xfe, 0x7f, 0x18, 0xfc, 0x5f, 0xff, 0xf6, 0x3a, + 0x49, 0x2a, 0x4e, 0xca, 0x41, 0x30, 0x64, 0xb9, 0x59, 0xd7, 0xfc, 0xdc, 0xe7, 0xf1, 0x69, 0x28, + 0x26, 0x23, 0xe0, 0x8a, 0x8c, 0x47, 0x2d, 0x89, 0x3f, 0x30, 0xf0, 0x07, 0x00, 0xf6, 0x19, 0x7a, + 0x57, 0x90, 0x53, 0x28, 0x24, 0x15, 0x1e, 0xa9, 0x49, 0x9d, 0xf5, 0xb6, 0xd5, 0x69, 0xec, 0x7f, + 0x14, 0xac, 0xf8, 0x28, 0xc1, 0x91, 0x04, 0x3d, 0x00, 0xd0, 0xcb, 0xf5, 0x3c, 0x33, 0xe5, 0x1d, + 0x3d, 0xe5, 0x55, 0x4a, 0x3f, 0x6a, 0x8a, 0x1a, 0xc0, 0x7e, 0x86, 0xee, 0x90, 0x52, 0x9c, 0xb0, + 0x22, 0xfd, 0x1e, 0x62, 0xfc, 0xbc, 0x64, 0x02, 0x70, 0x0c, 0x94, 0xe5, 0xdc, 0xd9, 0x68, 0x6f, + 0x74, 0xb6, 0x7b, 0xfe, 0xac, 0xf2, 0x5c, 0xcd, 0xb6, 0xa4, 0xd0, 0x8f, 0x6e, 0x2f, 0x32, 0x4f, + 0x64, 0xa2, 0xaf, 0xe3, 0x7f, 0x58, 0xe8, 0xe6, 0x43, 0xed, 0xaf, 0xa7, 0x82, 0x08, 0xb0, 0xdb, + 0xe8, 0x26, 0x85, 0x17, 0x02, 0x2b, 0xf1, 0xd2, 0xd8, 0xb1, 0xda, 0x56, 0x67, 0x33, 0x42, 0x32, + 0x76, 0xc8, 0x58, 0xf6, 0x75, 0x6c, 0x77, 0xd1, 0x56, 0x6d, 0xf9, 0x7b, 0x2b, 0x97, 0x37, 0x4b, + 0x6f, 0xca, 0xa5, 0x23, 0x03, 0xb4, 0x1f, 0xa3, 0x86, 0xe2, 0x57, 0x9f, 0x5f, 0x6f, 0xd1, 0xd8, + 0xef, 0xac, 0xe4, 0xf9, 0x56, 0x19, 0x26, 0x92, 0x00, 0x43, 0x86, 0x64, 0x99, 0x0a, 0x70, 0xff, + 0xf7, 0x1b, 0xa8, 0x59, 0x97, 0xd9, 0xce, 0xd1, 0xad, 0x18, 0x8e, 0x49, 0x99, 0x09, 0x7c, 0x21, + 0xb1, 0xda, 0x66, 0xbb, 0xd7, 0x95, 0xf8, 0xbf, 0x2a, 0xef, 0x83, 0x37, 0xf0, 0x42, 0x1f, 0x86, + 0xd3, 0xca, 0x6b, 0xf5, 0x35, 0xd5, 0xbc, 0x47, 0xd4, 0x8a, 0xeb, 0x01, 0xfb, 0x67, 0x0b, 0xa9, + 0x9b, 0xb9, 0x68, 0x86, 0xe3, 0x94, 0x8b, 0x22, 0x1d, 0x94, 0xd2, 0x3f, 0x46, 0xab, 0x2f, 0xde, + 0xc8, 0x28, 0xfd, 0x4b, 0xc0, 0x43, 0x28, 0x86, 0x40, 0x05, 0x49, 0xa0, 0xd7, 0x96, 0x63, 0x4f, + 0x2b, 0xcf, 0x79, 0xcc, 0x73, 0xf6, 0xba, 0xda, 0xc8, 0x61, 0x4b, 0x32, 0xf6, 0x2f, 0x16, 0xf2, + 0x28, 0xa3, 0x78, 0xd5, 0x88, 0x1b, 0x6f, 0x3f, 0xe2, 0x3d, 0x33, 0xe2, 0xee, 0x23, 0x46, 0x97, + 0x4e, 0xb9, 0x4b, 0x97, 0x27, 0xed, 0x03, 0xd4, 0x22, 0x71, 0x9e, 0x52, 0x4c, 0xe2, 0xb8, 0x00, + 0xce, 0x81, 0x3b, 0x9b, 0xca, 0xe4, 0x3b, 0xb3, 0xca, 0x7b, 0xdf, 0x98, 0xbc, 0x5e, 0xe0, 0x47, + 0x4d, 0x15, 0xe9, 0xce, 0x03, 0xf6, 0x6f, 0x16, 0xfa, 0x64, 0xc8, 0xf2, 0xbc, 0xa4, 0xa9, 0x98, + 0x68, 0x2b, 0xab, 0x7b, 0x80, 0x05, 0xc3, 0x7c, 0x4c, 0x46, 0x58, 0x4a, 0x31, 0x3e, 0x49, 0x05, + 0x64, 0x29, 0x17, 0x10, 0x63, 0xc2, 0x39, 0x08, 0x8e, 0x05, 0x73, 0x6e, 0x68, 0x87, 0xcc, 0x2a, + 0xef, 0x4b, 0xdd, 0xec, 0xff, 0xf1, 0xf8, 0x51, 0x70, 0x01, 0x94, 0xf7, 0x46, 0x5d, 0xb7, 0x23, + 0xf6, 0x74, 0x4c, 0x46, 0x8f, 0x18, 0xfd, 0x6e, 0x01, 0xe9, 0x2a, 0xc4, 0x11, 0xf3, 0x7f, 0x58, + 0x47, 0xee, 0x6a, 0x75, 0xed, 0xe7, 0xa8, 0xc5, 0x05, 0x39, 0x4d, 0x69, 0x82, 0x0b, 0x18, 0x93, + 0x22, 0xe6, 0xc6, 0xd0, 0x5f, 0x5d, 0xcf, 0xd0, 0x0b, 0x25, 0xaf, 0xd0, 0xf9, 0x51, 0xd3, 0x44, + 0x22, 0x1d, 0xb0, 0x29, 0x6a, 0xd6, 0x05, 0x50, 0x46, 0xde, 0xee, 0x3d, 0xbc, 0x76, 0xc7, 0xdb, + 0xaf, 0x93, 0xd3, 0x8f, 0xde, 0xa9, 0xc9, 0xd4, 0x7b, 0xf2, 0x72, 0xea, 0x5a, 0xaf, 0xa6, 0xae, + 0xf5, 0xcf, 0xd4, 0xb5, 0x7e, 0x3c, 0x77, 0xd7, 0x5e, 0x9d, 0xbb, 0x6b, 0x7f, 0x9e, 0xbb, 0x6b, + 0xcf, 0x3e, 0xbd, 0xd4, 0xc9, 0x38, 0xf4, 0x7e, 0x46, 0x06, 0x7c, 0x7e, 0x08, 0xcf, 0xf6, 0x3e, + 0x0b, 0x5f, 0xd4, 0x1e, 0x1b, 0xd5, 0x7e, 0xb0, 0xa5, 0x9e, 0x97, 0x8f, 0xff, 0x0d, 0x00, 0x00, + 0xff, 0xff, 0xb2, 0xd3, 0xe1, 0xd1, 0x64, 0x07, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -197,6 +382,25 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.AuthorizedQuoteDenoms) > 0 { + for iNdEx := len(m.AuthorizedQuoteDenoms) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AuthorizedQuoteDenoms[iNdEx]) + copy(dAtA[i:], m.AuthorizedQuoteDenoms[iNdEx]) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.AuthorizedQuoteDenoms[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + { + size, err := m.TakerFeeParams.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 if len(m.PoolCreationFee) > 0 { for iNdEx := len(m.PoolCreationFee) - 1; iNdEx >= 0; iNdEx-- { { @@ -266,6 +470,118 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *TakerFeeParams) 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 *TakerFeeParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TakerFeeParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) > 0 { + i -= len(m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) + copy(dAtA[i:], m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo))) + i-- + dAtA[i] = 0x2a + } + if len(m.AdminAddresses) > 0 { + for iNdEx := len(m.AdminAddresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AdminAddresses[iNdEx]) + copy(dAtA[i:], m.AdminAddresses[iNdEx]) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.AdminAddresses[iNdEx]))) + i-- + dAtA[i] = 0x22 + } + } + { + size, err := m.NonOsmoTakerFeeDistribution.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size, err := m.OsmoTakerFeeDistribution.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.DefaultTakerFee.Size() + i -= size + if _, err := m.DefaultTakerFee.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *TakerFeeDistributionPercentage) 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 *TakerFeeDistributionPercentage) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TakerFeeDistributionPercentage) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.CommunityPool.Size() + i -= size + if _, err := m.CommunityPool.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.StakingRewards.Size() + i -= size + if _, err := m.StakingRewards.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + 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 @@ -289,6 +605,14 @@ func (m *Params) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + l = m.TakerFeeParams.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.AuthorizedQuoteDenoms) > 0 { + for _, s := range m.AuthorizedQuoteDenoms { + l = len(s) + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -312,6 +636,44 @@ func (m *GenesisState) Size() (n int) { return n } +func (m *TakerFeeParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.DefaultTakerFee.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.OsmoTakerFeeDistribution.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.NonOsmoTakerFeeDistribution.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.AdminAddresses) > 0 { + for _, s := range m.AdminAddresses { + l = len(s) + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = len(m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + return n +} + +func (m *TakerFeeDistributionPercentage) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.StakingRewards.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.CommunityPool.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + func sovGenesis(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -381,6 +743,71 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TakerFeeParams", 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.TakerFeeParams.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorizedQuoteDenoms", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AuthorizedQuoteDenoms = append(m.AuthorizedQuoteDenoms, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) @@ -538,6 +965,338 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } return nil } +func (m *TakerFeeParams) 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: TakerFeeParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TakerFeeParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DefaultTakerFee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DefaultTakerFee.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OsmoTakerFeeDistribution", 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.OsmoTakerFeeDistribution.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NonOsmoTakerFeeDistribution", 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.NonOsmoTakerFeeDistribution.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AdminAddresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AdminAddresses = append(m.AdminAddresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CommunityPoolDenomToSwapNonWhitelistedAssetsTo", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CommunityPoolDenomToSwapNonWhitelistedAssetsTo = string(dAtA[iNdEx:postIndex]) + 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 (m *TakerFeeDistributionPercentage) 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: TakerFeeDistributionPercentage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TakerFeeDistributionPercentage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingRewards", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StakingRewards.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CommunityPool", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CommunityPool.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 diff --git a/x/poolmanager/types/gov.go b/x/poolmanager/types/gov.go index 7730c1bffeb..db161a3335c 100644 --- a/x/poolmanager/types/gov.go +++ b/x/poolmanager/types/gov.go @@ -1,7 +1,6 @@ package types import ( - "errors" "fmt" "strings" @@ -45,7 +44,12 @@ func (p *DenomPairTakerFeeProposal) ProposalType() string { // ValidateBasic validates a governance proposal's abstract and basic contents func (p *DenomPairTakerFeeProposal) ValidateBasic() error { - return errors.New("TODO: unimplemented") + err := govtypes.ValidateAbstract(p) + if err != nil { + return err + } + + return validateDenomPairTakerFees(p.DenomPairTakerFee) } // String returns a string containing the denom pair taker fee proposal. diff --git a/x/poolmanager/types/gov_test.go b/x/poolmanager/types/gov_test.go new file mode 100644 index 00000000000..87dedc2ece7 --- /dev/null +++ b/x/poolmanager/types/gov_test.go @@ -0,0 +1,146 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + proto "github.com/gogo/protobuf/proto" + "github.com/stretchr/testify/require" + + "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" +) + +func TestDenomPairTakerFeeProposalMarshalUnmarshal(t *testing.T) { + records := []types.DenomPairTakerFee{ + { + Denom0: "uion", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0013"), + }, + { + Denom0: "stake", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0016"), + }, + { + Denom0: "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0017"), + }, + } + + tests := []struct { + proposal *types.DenomPairTakerFeeProposal + }{ + { // empty title + proposal: &types.DenomPairTakerFeeProposal{ + Title: "", + Description: "proposal to add denom pair taker fee records", + }, + }, + { // empty description + proposal: &types.DenomPairTakerFeeProposal{ + Title: "title", + Description: "", + }, + }, + { // happy path + proposal: &types.DenomPairTakerFeeProposal{ + Title: "title", + Description: "proposal to add denom pair taker fee records", + DenomPairTakerFee: records, + }, + }, + } + + for _, test := range tests { + bz, err := proto.Marshal(test.proposal) + require.NoError(t, err) + decoded := types.DenomPairTakerFeeProposal{} + err = proto.Unmarshal(bz, &decoded) + require.NoError(t, err) + require.Equal(t, *test.proposal, decoded) + } +} + +func TestDenomPairTakerFeeProposal_ValidateBasic(t *testing.T) { + baseRecord := types.DenomPairTakerFee{ + Denom0: "uion", + Denom1: "uosmo", + TakerFee: sdk.MustNewDecFromStr("0.0013"), + } + + withSameDenom := func(record types.DenomPairTakerFee) types.DenomPairTakerFee { + record.Denom1 = record.Denom0 + return record + } + + withInvalidDenom0 := func(record types.DenomPairTakerFee) types.DenomPairTakerFee { + record.Denom0 = "0" + return record + } + + withInvalidDenom1 := func(record types.DenomPairTakerFee) types.DenomPairTakerFee { + record.Denom1 = "1" + return record + } + + withInvalidTakerFee := func(record types.DenomPairTakerFee) types.DenomPairTakerFee { + record.TakerFee = sdk.MustNewDecFromStr("1.01") + return record + } + + withInvalidRecord := func(record types.DenomPairTakerFee) types.DenomPairTakerFee { + record = types.DenomPairTakerFee{} + return record + } + + tests := []struct { + name string + modifyFunc func(types.DenomPairTakerFee) types.DenomPairTakerFee + expectPass bool + }{ + { + name: "proper msg", + modifyFunc: func(record types.DenomPairTakerFee) types.DenomPairTakerFee { return record }, + expectPass: true, + }, + { + name: "invalid denom pair", + modifyFunc: withSameDenom, + expectPass: false, + }, + { + name: "invalid denom0", + modifyFunc: withInvalidDenom0, + expectPass: false, + }, + { + name: "invalid denom1", + modifyFunc: withInvalidDenom1, + expectPass: false, + }, + { + name: "invalid taker fee", + modifyFunc: withInvalidTakerFee, + expectPass: false, + }, + { + name: "invalid record", + modifyFunc: withInvalidRecord, + expectPass: false, + }, + } + + for _, test := range tests { + records := []types.DenomPairTakerFee{test.modifyFunc(baseRecord)} + + denomPairTakerFeeProposal := types.NewDenomPairTakerFeeProposal("title", "description", records) + + if test.expectPass { + require.NoError(t, denomPairTakerFeeProposal.ValidateBasic(), "test: %v", test.name) + } else { + require.Error(t, denomPairTakerFeeProposal.ValidateBasic(), "test: %v", test.name) + } + } +} diff --git a/x/poolmanager/types/keys.go b/x/poolmanager/types/keys.go index 5f470da6af8..d26fb27d693 100644 --- a/x/poolmanager/types/keys.go +++ b/x/poolmanager/types/keys.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "sort" "github.com/gogo/protobuf/proto" ) @@ -20,6 +21,15 @@ var ( // SwapModuleRouterPrefix defines prefix to store pool id to swap module mappings. SwapModuleRouterPrefix = []byte{0x02} +<<<<<<< HEAD +======= + + // KeyPoolVolumePrefix defines prefix to store pool volume. + KeyPoolVolumePrefix = []byte{0x03} + + // DenomTradePairPrefix defines prefix to store denom trade pair for taker fee. + DenomTradePairPrefix = []byte{0x04} +>>>>>>> 5c8fd80f (feat(spike): taker fee (#6034)) ) // ModuleRouteToBytes serializes moduleRoute to bytes. @@ -27,6 +37,14 @@ func FormatModuleRouteKey(poolId uint64) []byte { return []byte(fmt.Sprintf("%s%d", SwapModuleRouterPrefix, poolId)) } +// FormatDenomTradePairKey serializes denom trade pair to bytes. +// Denom trade pair is automatically sorted lexicographically. +func FormatDenomTradePairKey(denom0, denom1 string) []byte { + denoms := []string{denom0, denom1} + sort.Strings(denoms) + return []byte(fmt.Sprintf("%s%s%s%s%s", DenomTradePairPrefix, KeySeparator, denoms[0], KeySeparator, denoms[1])) +} + // ParseModuleRouteFromBz parses the raw bytes into ModuleRoute. // Returns error if fails to parse or if the bytes are empty. func ParseModuleRouteFromBz(bz []byte) (ModuleRoute, error) { diff --git a/x/poolmanager/types/keys_test.go b/x/poolmanager/types/keys_test.go new file mode 100644 index 00000000000..07a68121a0a --- /dev/null +++ b/x/poolmanager/types/keys_test.go @@ -0,0 +1,36 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" +) + +func TestFormatDenomTradePairKey(t *testing.T) { + tests := map[string]struct { + denom0 string + denom1 string + expectedKey string + }{ + "happy path": { + denom0: "uosmo", + denom1: "uion", + expectedKey: "\x04|uion|uosmo", + }, + "reversed denoms get reordered": { + denom0: "uion", + denom1: "uosmo", + expectedKey: "\x04|uion|uosmo", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + formatDenomTradePairKey := types.FormatDenomTradePairKey(tc.denom0, tc.denom1) + stringFormatDenomTradePairKeyString := string(formatDenomTradePairKey) + require.Equal(t, tc.expectedKey, stringFormatDenomTradePairKeyString) + }) + } +} diff --git a/x/poolmanager/types/msgs.go b/x/poolmanager/types/msgs.go index 9b8b7ea49af..ae1df8ed7b2 100644 --- a/x/poolmanager/types/msgs.go +++ b/x/poolmanager/types/msgs.go @@ -1,8 +1,6 @@ package types import ( - "errors" - errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -176,8 +174,12 @@ func (msg MsgSetDenomPairTakerFee) Route() string { return RouterKey } func (msg MsgSetDenomPairTakerFee) Type() string { return TypeMsgSetDenomPairTakerFee } func (msg MsgSetDenomPairTakerFee) ValidateBasic() error { - // TODO: - return errors.New("not implemented") + _, err := sdk.AccAddressFromBech32(msg.Sender) + if err != nil { + return InvalidSenderError{Sender: msg.Sender} + } + + return validateDenomPairTakerFees(msg.DenomPairTakerFee) } func (msg MsgSetDenomPairTakerFee) GetSignBytes() []byte { diff --git a/x/poolmanager/types/msgs_test.go b/x/poolmanager/types/msgs_test.go index a64931e6fbf..da5b8f9335c 100644 --- a/x/poolmanager/types/msgs_test.go +++ b/x/poolmanager/types/msgs_test.go @@ -567,3 +567,89 @@ func TestMsgSplitRouteSwapExactAmountOut(t *testing.T) { }) } } + +func TestMsgSetDenomPairTakerFee(t *testing.T) { + createMsg := func(after func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + properMsg := types.MsgSetDenomPairTakerFee{ + Sender: addr1, + DenomPairTakerFee: []types.DenomPairTakerFee{ + { + Denom0: "uosmo", + Denom1: "uatom", + TakerFee: sdk.MustNewDecFromStr("0.003"), + }, + { + Denom0: "uosmo", + Denom1: "uion", + TakerFee: sdk.MustNewDecFromStr("0.006"), + }, + }, + } + + return after(properMsg) + } + + msg := createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + // Do nothing + return msg + }) + + require.Equal(t, msg.Route(), types.RouterKey) + require.Equal(t, msg.Type(), types.TypeMsgSetDenomPairTakerFee) + signers := msg.GetSigners() + require.Equal(t, len(signers), 1) + require.Equal(t, signers[0].String(), addr1) + + tests := map[string]struct { + msg types.MsgSetDenomPairTakerFee + expectError bool + }{ + "valid": { + msg: createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + // Do nothing + return msg + }), + }, + "invalid sender": { + msg: createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + msg.Sender = "" + return msg + }), + expectError: true, + }, + "invalid denom0": { + msg: createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + msg.DenomPairTakerFee[0].Denom0 = "" + return msg + }), + expectError: true, + }, + "invalid denom1": { + msg: createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + msg.DenomPairTakerFee[0].Denom1 = "" + return msg + }), + expectError: true, + }, + "invalid denom0 = denom1": { + msg: createMsg(func(msg types.MsgSetDenomPairTakerFee) types.MsgSetDenomPairTakerFee { + msg.DenomPairTakerFee[0].Denom0 = msg.DenomPairTakerFee[0].Denom1 + return msg + }), + expectError: true, + }, + } + + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + + if tc.expectError { + require.Error(t, err) + return + } + require.NoError(t, err) + }) + } +} diff --git a/x/poolmanager/types/params.go b/x/poolmanager/types/params.go index 433105e9cbe..dcd5806ba48 100644 --- a/x/poolmanager/types/params.go +++ b/x/poolmanager/types/params.go @@ -11,7 +11,13 @@ import ( // Parameter store keys. var ( - KeyPoolCreationFee = []byte("PoolCreationFee") + KeyPoolCreationFee = []byte("PoolCreationFee") + KeyDefaultTakerFee = []byte("DefaultTakerFee") + KeyOsmoTakerFeeDistribution = []byte("OsmoTakerFeeDistribution") + KeyNonOsmoTakerFeeDistribution = []byte("NonOsmoTakerFeeDistribution") + KeyAdminAddresses = []byte("AdminAddresses") + KeyCommunityPoolDenomToSwapNonWhitelistedAssetsTo = []byte("CommunityPoolDenomToSwapNonWhitelistedAssetsTo") + KeyAuthorizedQuoteDenoms = []byte("AuthorizedQuoteDenoms") ) // ParamTable for gamm module. @@ -19,9 +25,21 @@ func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -func NewParams(poolCreationFee sdk.Coins) Params { +func NewParams(poolCreationFee sdk.Coins, + defaultTakerFee sdk.Dec, + osmoTakerFeeDistribution, nonOsmoTakerFeeDistribution TakerFeeDistributionPercentage, + adminAddresses, authorizedQuoteDenoms []string, + communityPoolDenomToSwapNonWhitelistedAssetsTo string) Params { return Params{ PoolCreationFee: poolCreationFee, + TakerFeeParams: TakerFeeParams{ + DefaultTakerFee: defaultTakerFee, + OsmoTakerFeeDistribution: osmoTakerFeeDistribution, + NonOsmoTakerFeeDistribution: nonOsmoTakerFeeDistribution, + AdminAddresses: adminAddresses, + CommunityPoolDenomToSwapNonWhitelistedAssetsTo: communityPoolDenomToSwapNonWhitelistedAssetsTo, + }, + AuthorizedQuoteDenoms: authorizedQuoteDenoms, } } @@ -29,6 +47,25 @@ func NewParams(poolCreationFee sdk.Coins) Params { func DefaultParams() Params { return Params{ PoolCreationFee: sdk.Coins{sdk.NewInt64Coin(appparams.BaseCoinUnit, 1000_000_000)}, // 1000 OSMO + TakerFeeParams: TakerFeeParams{ + DefaultTakerFee: sdk.MustNewDecFromStr("0.0015"), // 0.15% + OsmoTakerFeeDistribution: TakerFeeDistributionPercentage{ + StakingRewards: sdk.MustNewDecFromStr("1"), // 100% + CommunityPool: sdk.MustNewDecFromStr("0"), // 0% + }, + NonOsmoTakerFeeDistribution: TakerFeeDistributionPercentage{ + StakingRewards: sdk.MustNewDecFromStr("0.67"), // 67% + CommunityPool: sdk.MustNewDecFromStr("0.33"), // 33% + }, + AdminAddresses: []string{}, + CommunityPoolDenomToSwapNonWhitelistedAssetsTo: "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", // USDC + }, + AuthorizedQuoteDenoms: []string{ + "uosmo", + "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", // ATOM + "ibc/0CD3A0285E1341859B5E86B6AB7682F023D03E97607CCC1DC95706411D866DF7", // DAI + "ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858", // USDC + }, } } @@ -37,6 +74,24 @@ func (p Params) Validate() error { if err := validatePoolCreationFee(p.PoolCreationFee); err != nil { return err } + if err := validateDefaultTakerFee(p.TakerFeeParams.DefaultTakerFee); err != nil { + return err + } + if err := validateTakerFeeDistribution(p.TakerFeeParams.OsmoTakerFeeDistribution); err != nil { + return err + } + if err := validateTakerFeeDistribution(p.TakerFeeParams.NonOsmoTakerFeeDistribution); err != nil { + return err + } + if err := validateAdminAddresses(p.TakerFeeParams.AdminAddresses); err != nil { + return err + } + if err := validateCommunityPoolDenomToSwapNonWhitelistedAssetsTo(p.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo); err != nil { + return err + } + if err := validateAuthorizedQuoteDenoms(p.AuthorizedQuoteDenoms); err != nil { + return err + } return nil } @@ -45,6 +100,12 @@ func (p Params) Validate() error { func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ paramtypes.NewParamSetPair(KeyPoolCreationFee, &p.PoolCreationFee, validatePoolCreationFee), + paramtypes.NewParamSetPair(KeyDefaultTakerFee, &p.TakerFeeParams.DefaultTakerFee, validateDefaultTakerFee), + paramtypes.NewParamSetPair(KeyOsmoTakerFeeDistribution, &p.TakerFeeParams.OsmoTakerFeeDistribution, validateTakerFeeDistribution), + paramtypes.NewParamSetPair(KeyNonOsmoTakerFeeDistribution, &p.TakerFeeParams.NonOsmoTakerFeeDistribution, validateTakerFeeDistribution), + paramtypes.NewParamSetPair(KeyAdminAddresses, &p.TakerFeeParams.AdminAddresses, validateAdminAddresses), + paramtypes.NewParamSetPair(KeyCommunityPoolDenomToSwapNonWhitelistedAssetsTo, &p.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo, validateCommunityPoolDenomToSwapNonWhitelistedAssetsTo), + paramtypes.NewParamSetPair(KeyAuthorizedQuoteDenoms, &p.AuthorizedQuoteDenoms, validateAuthorizedQuoteDenoms), } } @@ -60,3 +121,123 @@ func validatePoolCreationFee(i interface{}) error { return nil } + +func validateDefaultTakerFee(i interface{}) error { + // Convert the given parameter to sdk.Dec. + defaultTakerFee, ok := i.(sdk.Dec) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + // Ensure that the passed in discount rate is between 0 and 1. + if defaultTakerFee.IsNegative() || defaultTakerFee.GT(sdk.OneDec()) { + return fmt.Errorf("invalid default taker fee: %s", defaultTakerFee) + } + + return nil +} + +func validateTakerFeeDistribution(i interface{}) error { + // Convert the given parameter to sdk.Dec. + takerFeeDistribution, ok := i.(TakerFeeDistributionPercentage) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if takerFeeDistribution.StakingRewards.IsNegative() || takerFeeDistribution.StakingRewards.GT(sdk.OneDec()) { + return fmt.Errorf("invalid staking rewards distribution: %s", takerFeeDistribution.StakingRewards) + } + if takerFeeDistribution.CommunityPool.IsNegative() || takerFeeDistribution.CommunityPool.GT(sdk.OneDec()) { + return fmt.Errorf("invalid community pool distribution: %s", takerFeeDistribution.CommunityPool) + } + + return nil +} + +func validateAdminAddresses(i interface{}) error { + adminAddresses, ok := i.([]string) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if len(adminAddresses) > 0 { + for _, adminAddress := range adminAddresses { + if _, err := sdk.AccAddressFromBech32(adminAddress); err != nil { + return fmt.Errorf("invalid account address: %s", adminAddress) + } + } + } + + return nil +} + +// validateAuthorizedQuoteDenoms validates a slice of authorized quote denoms. +// +// Parameters: +// - i: The parameter to validate. +// +// Returns: +// - An error if given type is not string slice. +// - An error if given slice is empty. +// - An error if any of the denoms are invalid. +func validateAuthorizedQuoteDenoms(i interface{}) error { + authorizedQuoteDenoms, ok := i.([]string) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if len(authorizedQuoteDenoms) == 0 { + return fmt.Errorf("authorized quote denoms cannot be empty") + } + + for _, denom := range authorizedQuoteDenoms { + if err := sdk.ValidateDenom(denom); err != nil { + return err + } + } + + return nil +} + +func validateCommunityPoolDenomToSwapNonWhitelistedAssetsTo(i interface{}) error { + communityPoolDenomToSwapNonWhitelistedAssetsTo, ok := i.(string) + + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if err := sdk.ValidateDenom(communityPoolDenomToSwapNonWhitelistedAssetsTo); err != nil { + return err + } + + return nil +} + +func validateDenomPairTakerFees(pairs []DenomPairTakerFee) error { + if len(pairs) == 0 { + return fmt.Errorf("Empty denom pair taker fee") + } + + for _, record := range pairs { + if record.Denom0 == record.Denom1 { + return fmt.Errorf("denom0 and denom1 must be different") + } + + if sdk.ValidateDenom(record.Denom0) != nil { + return fmt.Errorf("denom0 is invalid: %s", sdk.ValidateDenom(record.Denom0)) + } + + if sdk.ValidateDenom(record.Denom1) != nil { + return fmt.Errorf("denom1 is invalid: %s", sdk.ValidateDenom(record.Denom1)) + } + + takerFee := record.TakerFee + if takerFee.IsNegative() || takerFee.GTE(sdk.OneDec()) { + return fmt.Errorf("taker fee must be between 0 and 1: %s", takerFee.String()) + } + } + return nil +} diff --git a/x/poolmanager/types/tx.pb.go b/x/poolmanager/types/tx.pb.go index dfc5a7fc6f3..1d9e03c3197 100644 --- a/x/poolmanager/types/tx.pb.go +++ b/x/poolmanager/types/tx.pb.go @@ -529,6 +529,8 @@ func (m *MsgSetDenomPairTakerFeeResponse) GetSuccess() bool { } type DenomPairTakerFee struct { + // denom0 and denom1 get automatically lexigographically sorted + // when being stored, so the order of input here does not matter. Denom0 string `protobuf:"bytes,1,opt,name=denom0,proto3" json:"denom0,omitempty" yaml:"denom0"` Denom1 string `protobuf:"bytes,2,opt,name=denom1,proto3" json:"denom1,omitempty" yaml:"denom1"` TakerFee github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=taker_fee,json=takerFee,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"taker_fee" yaml:"taker_fee"` diff --git a/x/protorev/keeper/keeper_test.go b/x/protorev/keeper/keeper_test.go index 2711b63c33f..1a564a0d89d 100644 --- a/x/protorev/keeper/keeper_test.go +++ b/x/protorev/keeper/keeper_test.go @@ -57,6 +57,10 @@ func TestKeeperTestSuite(t *testing.T) { func (s *KeeperTestSuite) SetupTest() { s.Setup() + poolManagerParams := s.App.PoolManagerKeeper.GetParams(s.Ctx) + poolManagerParams.TakerFeeParams.DefaultTakerFee = sdk.ZeroDec() + s.App.PoolManagerKeeper.SetParams(s.Ctx, poolManagerParams) + // Genesis on init should be the same as the default genesis exportDefaultGenesis := s.App.ProtoRevKeeper.ExportGenesis(s.Ctx) s.Require().Equal(exportDefaultGenesis, types.DefaultGenesis()) diff --git a/x/protorev/keeper/rebalance_test.go b/x/protorev/keeper/rebalance_test.go index 6a7328c13ef..9df1fd56051 100644 --- a/x/protorev/keeper/rebalance_test.go +++ b/x/protorev/keeper/rebalance_test.go @@ -737,7 +737,6 @@ func (s *KeeperTestSuite) TestRemainingPoolPointsForTx() { } func (s *KeeperTestSuite) TestUpdateSearchRangeIfNeeded() { - s.SetupTest() s.Run("Extended search on stable pools", func() { route := keeper.RouteMetaData{ Route: extendedRangeRoute, diff --git a/x/superfluid/keeper/stake_test.go b/x/superfluid/keeper/stake_test.go index fd22bc9d0f6..3daa7483fc5 100644 --- a/x/superfluid/keeper/stake_test.go +++ b/x/superfluid/keeper/stake_test.go @@ -1160,7 +1160,7 @@ func (s *KeeperTestSuite) TestConvertLockToStake() { "error: min amount to stake greater than actual amount": { useMinAmountToStake: true, expectedError: types.TokenConvertedLessThenDesiredStakeError{ - ActualTotalAmtToStake: sdk.NewInt(8309), + ActualTotalAmtToStake: sdk.NewInt(8306), ExpectedTotalAmtToStake: sdk.NewInt(999999999), }, }, @@ -1359,7 +1359,7 @@ func (s *KeeperTestSuite) TestConvertGammSharesToOsmoAndStake() { }, "error: min amount to stake exceeds actual amount staking": { useMinAmtToStake: true, - expectedError: "actual amount converted to stake (8309) is less then minimum amount expected to be staked (999999999)", + expectedError: "actual amount converted to stake (8306) is less then minimum amount expected to be staked (999999999)", }, } diff --git a/x/twap/keeper_test.go b/x/twap/keeper_test.go index 47c58f16590..f510aa6ef80 100644 --- a/x/twap/keeper_test.go +++ b/x/twap/keeper_test.go @@ -43,9 +43,9 @@ func (s *TestSuite) SetupTest() { s.twapkeeper = s.App.TwapKeeper s.Ctx = s.Ctx.WithBlockTime(baseTime) // add x/twap test specific denoms - concentratedLiquidityParams := s.App.ConcentratedLiquidityKeeper.GetParams(s.Ctx) - concentratedLiquidityParams.AuthorizedQuoteDenoms = append(concentratedLiquidityParams.AuthorizedQuoteDenoms, denom0, denom1, denom2) - s.App.ConcentratedLiquidityKeeper.SetParams(s.Ctx, concentratedLiquidityParams) + poolManagerParams := s.App.PoolManagerKeeper.GetParams(s.Ctx) + poolManagerParams.AuthorizedQuoteDenoms = append(poolManagerParams.AuthorizedQuoteDenoms, denom0, denom1, denom2) + s.App.PoolManagerKeeper.SetParams(s.Ctx, poolManagerParams) } var ( diff --git a/x/twap/store.go b/x/twap/store.go index 27187b03ee8..2ac0367eab8 100644 --- a/x/twap/store.go +++ b/x/twap/store.go @@ -157,6 +157,7 @@ func (k Keeper) GetAllHistoricalTimeIndexedTWAPs(ctx sdk.Context) ([]types.TwapR } // getAllHistoricalPoolIndexedTWAPs returns all historical TWAPs indexed by pool id. +// nolint:unused func (k Keeper) getAllHistoricalPoolIndexedTWAPs(ctx sdk.Context) ([]types.TwapRecord, error) { return osmoutils.GatherValuesFromStorePrefix(ctx.KVStore(k.storeKey), []byte(types.HistoricalTWAPPoolIndexPrefix), types.ParseTwapFromBz) } diff --git a/x/txfees/README.md b/x/txfees/README.md index 3ca2f947bf2..c9a352720d6 100644 --- a/x/txfees/README.md +++ b/x/txfees/README.md @@ -15,6 +15,22 @@ Currently the only supported metadata & spot price calculator is using a GAMM po of each epoch. * Adds a new SDK message for creating governance proposals for adding new TxFee denoms. +## Epoch Hooks + +The txfees module includes hooks that trigger actions at the end of each epoch. + +The `AfterEpochEnd` hook performs several actions: + +1. It swaps all non-OSMO denominated fees in the non-native fee collector for staking rewards module account into OSMO. This is done by checking the balance of the non-native fee collector for staking rewards module account, and swapping each non-OSMO denominated fee into OSMO. If a pool does not exist for a particular denomination pair, the swap is silently skipped. See the `swapNonNativeFeeToDenom` function description below for more details. + +2. After the swap, it transfers all OSMO from the non-native fee collector for staking rewards to the primary txfees fee collector module account. This indirectly distributes the fees to stakers. + +3. It also swaps non-whitelisted assets in the non-native community pool collector into the denomination specified in the pool manager parameters (currently USDC). + +4. Finally, it funds the community pool with the swapped denomination. + +The `swapNonNativeFeeToDenom` function is used to perform the swaps. It iterates over each coin in the balance of the specified fee collector account, and swaps it into the specified denomination. This function assumes that a pool route exists in the protorev route store for each denomination pair. If a pool route does not exist or is disabled, the swap is silently skipped. + ## Local Mempool Filters Added * If you specify a min-tx-fee in the $BASEDENOM then diff --git a/x/txfees/keeper/export_test.go b/x/txfees/keeper/export_test.go new file mode 100644 index 00000000000..b432816b3df --- /dev/null +++ b/x/txfees/keeper/export_test.go @@ -0,0 +1,7 @@ +package keeper + +import sdk "github.com/cosmos/cosmos-sdk/types" + +func (k Keeper) SwapNonNativeFeeToDenom(ctx sdk.Context, denomToSwapTo string, feeCollectorAddress sdk.AccAddress) { + k.swapNonNativeFeeToDenom(ctx, denomToSwapTo, feeCollectorAddress) +} diff --git a/x/txfees/keeper/feedecorator.go b/x/txfees/keeper/feedecorator.go index ce66f2b8f3d..7ac13d7ef69 100644 --- a/x/txfees/keeper/feedecorator.go +++ b/x/txfees/keeper/feedecorator.go @@ -180,8 +180,8 @@ func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo } // checks to make sure a separate module account has been set to collect fees not in base token - if addrNonNativeFee := dfd.ak.GetModuleAddress(types.NonNativeFeeCollectorForStakingRewardsName); addrNonNativeFee == nil { - return ctx, fmt.Errorf("non native fee collector module account (%s) has not been set", types.NonNativeFeeCollectorForStakingRewardsName) + if addrNonNativeFee := dfd.ak.GetModuleAddress(types.FeeCollectorForStakingRewardsName); addrNonNativeFee == nil { + return ctx, fmt.Errorf("non native fee collector module account (%s) has not been set", types.FeeCollectorForStakingRewardsName) } // fee can be in any denom (checked for validity later) @@ -248,8 +248,8 @@ func DeductFees(txFeesKeeper types.TxFeesKeeper, bankKeeper types.BankKeeper, ct return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) } } else { - // sends to NonNativeFeeCollectorForStakingRewardsName module account - err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.NonNativeFeeCollectorForStakingRewardsName, fees) + // sends to FeeCollectorForStakingRewardsName module account + err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorForStakingRewardsName, fees) if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) } diff --git a/x/txfees/keeper/feedecorator_test.go b/x/txfees/keeper/feedecorator_test.go index 7cb93ca5e3b..489cf6f67b0 100644 --- a/x/txfees/keeper/feedecorator_test.go +++ b/x/txfees/keeper/feedecorator_test.go @@ -208,7 +208,7 @@ func (s *KeeperTestSuite) TestFeeDecorator() { if !tc.txFee.IsZero() { moduleName := types.FeeCollectorName if tc.txFee[0].Denom != baseDenom { - moduleName = types.NonNativeFeeCollectorForStakingRewardsName + moduleName = types.FeeCollectorForStakingRewardsName } moduleAddr := s.App.AccountKeeper.GetModuleAddress(moduleName) s.Require().Equal(tc.txFee[0], s.App.BankKeeper.GetBalance(s.Ctx, moduleAddr, tc.txFee[0].Denom), tc.name) diff --git a/x/txfees/keeper/hooks.go b/x/txfees/keeper/hooks.go index eb595cc45d7..e6ed1204435 100644 --- a/x/txfees/keeper/hooks.go +++ b/x/txfees/keeper/hooks.go @@ -14,36 +14,32 @@ func (k Keeper) BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochN // at the end of each epoch, swap all non-OSMO fees into OSMO and transfer to fee module account func (k Keeper) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) error { - nonNativeFeeAddr := k.accountKeeper.GetModuleAddress(txfeestypes.NonNativeFeeCollectorForStakingRewardsName) - baseDenom, _ := k.GetBaseDenom(ctx) - feeTokens := k.GetFeeTokens(ctx) + defaultFeesDenom, _ := k.GetBaseDenom(ctx) - for _, feetoken := range feeTokens { - if feetoken.Denom == baseDenom { - continue - } - coinBalance := k.bankKeeper.GetBalance(ctx, nonNativeFeeAddr, feetoken.Denom) - if coinBalance.Amount.IsZero() { - continue - } + nonNativeStakingCollectorAddress := k.accountKeeper.GetModuleAddress(txfeestypes.FeeCollectorForStakingRewardsName) - // Do the swap of this fee token denom to base denom. - _ = osmoutils.ApplyFuncIfNoError(ctx, func(cacheCtx sdk.Context) error { - // We allow full slippage. Theres not really an effective way to bound slippage until TWAP's land, - // but even then the point is a bit moot. - // The only thing that could be done is a costly griefing attack to reduce the amount of osmo given as tx fees. - // However the idea of the txfees FeeToken gating is that the pool is sufficiently liquid for that base token. - minAmountOut := sdk.ZeroInt() - _, err := k.poolManager.SwapExactAmountIn(cacheCtx, nonNativeFeeAddr, feetoken.PoolID, coinBalance, baseDenom, minAmountOut) - return err - }) - } + // Non-native fee collector for staking rewards get swapped entirely into base denom. + k.swapNonNativeFeeToDenom(ctx, defaultFeesDenom, nonNativeStakingCollectorAddress) - // Get all of the txfee payout denom in the module account - baseDenomCoins := sdk.NewCoins(k.bankKeeper.GetBalance(ctx, nonNativeFeeAddr, baseDenom)) + // Now that the rewards have been swapped, transfer any base denom existing in the non-native fee collector to the fee collector (indirectly distributing to stakers) + baseDenomCoins := sdk.NewCoins(k.bankKeeper.GetBalance(ctx, nonNativeStakingCollectorAddress, defaultFeesDenom)) + _ = osmoutils.ApplyFuncIfNoError(ctx, func(cacheCtx sdk.Context) error { + err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, txfeestypes.FeeCollectorForStakingRewardsName, txfeestypes.FeeCollectorName, baseDenomCoins) + return err + }) + // Non-native fee collector for community pool get swapped entirely into denom specified in the pool manager params. + poolManagerParams := k.poolManager.GetParams(ctx) + denomToSwapTo := poolManagerParams.TakerFeeParams.CommunityPoolDenomToSwapNonWhitelistedAssetsTo + // Only non-whitelisted assets should exist here since we do direct community pool funds when calculating the taker fee if + // the input is a whitelisted asset. + nonNativeCommunityPoolCollectorAddress := k.accountKeeper.GetModuleAddress(txfeestypes.FeeCollectorForCommunityPoolName) + k.swapNonNativeFeeToDenom(ctx, denomToSwapTo, nonNativeCommunityPoolCollectorAddress) + + // Now that the non whitelisted assets have been swapped, fund the community pool with the denom we swapped to. + denomToSwapToCoins := sdk.NewCoins(k.bankKeeper.GetBalance(ctx, nonNativeCommunityPoolCollectorAddress, denomToSwapTo)) _ = osmoutils.ApplyFuncIfNoError(ctx, func(cacheCtx sdk.Context) error { - err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, txfeestypes.NonNativeFeeCollectorForStakingRewardsName, txfeestypes.FeeCollectorName, baseDenomCoins) + err := k.distributionKeeper.FundCommunityPool(ctx, denomToSwapToCoins, nonNativeCommunityPoolCollectorAddress) return err }) @@ -69,3 +65,47 @@ func (h Hooks) BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNu func (h Hooks) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) error { return h.k.AfterEpochEnd(ctx, epochIdentifier, epochNumber) } + +// swapNonNativeFeeToDenom swaps the given non-native fees into the given denom from the given fee collector address. +// If an error in swap occurs for a given denom, it will be silently skipped. +// CONTRACT: a pool must exist between each denom in the balance and denomToSwapTo. If doesn't exist. Silently skip swap. +// CONTRACT: protorev must be configured to have a pool for the given denom pair. Otherwise, the denom will be skipped. +func (k Keeper) swapNonNativeFeeToDenom(ctx sdk.Context, denomToSwapTo string, feeCollectorAddress sdk.AccAddress) { + feeCollectorBalance := k.bankKeeper.GetAllBalances(ctx, feeCollectorAddress) + + for _, coin := range feeCollectorBalance { + if coin.Denom == denomToSwapTo { + continue + } + + // Search for the denom pair route via the protorev store. + // Since OSMO is one of the protorev denoms, many of the routes will exist in this store. + // There will be times when this store does not know about a route, but this is acceptable + // since this will likely be a very small value of a relatively unknown token. If this begins + // to accrue more value, we can always manually register the route and it will get swapped in + // the next epoch. + poolId, err := k.protorevKeeper.GetPoolForDenomPairNoOrder(ctx, denomToSwapTo, coin.Denom) + if err != nil { + if err != nil { + // The pool route either doesn't exist or is disabled in protorev. + // It will just accrue in the non-native fee collector account. + // Skip this denom and move on to the next one. + continue + } + } + + // Do the swap of this fee token denom to base denom. + _ = osmoutils.ApplyFuncIfNoError(ctx, func(cacheCtx sdk.Context) error { + // We allow full slippage. Theres not really an effective way to bound slippage until TWAP's land, + // but even then the point is a bit moot. + // The only thing that could be done is a costly griefing attack to reduce the amount of osmo given as tx fees. + // However the idea of the txfees FeeToken gating is that the pool is sufficiently liquid for that base token. + minAmountOut := sdk.ZeroInt() + + // We swap without charging a taker fee / sending to the non native fee collector, since these are funds that + // are accruing from the taker fee itself. + _, err := k.poolManager.SwapExactAmountInNoTakerFee(cacheCtx, feeCollectorAddress, poolId, coin, denomToSwapTo, minAmountOut) + return err + }) + } +} diff --git a/x/txfees/keeper/hooks_test.go b/x/txfees/keeper/hooks_test.go index 54c5974adaa..5028921b218 100644 --- a/x/txfees/keeper/hooks_test.go +++ b/x/txfees/keeper/hooks_test.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/v19/app/apptesting" gammtypes "github.com/osmosis-labs/osmosis/v19/x/gamm/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v19/x/poolmanager/types" "github.com/osmosis-labs/osmosis/v19/x/txfees/types" @@ -91,12 +92,12 @@ func (s *KeeperTestSuite) TestTxFeesAfterEpochEnd() { _, _, addr0 := testdata.KeyTestPubAddr() err = simapp.FundAccount(s.App.BankKeeper, s.Ctx, addr0, sdk.Coins{coin}) s.NoError(err) - err = s.App.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, addr0, types.NonNativeFeeCollectorForStakingRewardsName, sdk.Coins{coin}) + err = s.App.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, addr0, types.FeeCollectorForStakingRewardsName, sdk.Coins{coin}) s.NoError(err) } // checks the balance of the non-native denom in module account - moduleAddrNonNativeFee := s.App.AccountKeeper.GetModuleAddress(types.NonNativeFeeCollectorForStakingRewardsName) + moduleAddrNonNativeFee := s.App.AccountKeeper.GetModuleAddress(types.FeeCollectorForStakingRewardsName) s.Equal(s.App.BankKeeper.GetAllBalances(s.Ctx, moduleAddrNonNativeFee), tc.coins) // End of epoch, so all the non-osmo fee amount should be swapped to osmo and transfer to fee module account @@ -112,7 +113,74 @@ func (s *KeeperTestSuite) TestTxFeesAfterEpochEnd() { // non-osmos module account should be empty as all the funds should be transferred to osmo module s.Empty(s.App.BankKeeper.GetAllBalances(s.Ctx, moduleAddrNonNativeFee)) // check that the total osmo amount has been transferred to module account - s.Equal(moduleBaseDenomBalance.Amount, finalOutputAmount) + s.Equal(moduleBaseDenomBalance.Amount.String(), finalOutputAmount.String()) + }) + } +} + +func (s *KeeperTestSuite) TestSwapNonNativeFeeToDenom() { + s.Setup() + + var ( + defaultTxFeesDenom, _ = s.App.TxFeesKeeper.GetBaseDenom(s.Ctx) + defaultPoolCoins = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)), sdk.NewCoin(defaultTxFeesDenom, sdk.NewInt(100))) + defaultBalanceToSwap = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100))) + ) + + tests := []struct { + name string + denomToSwapTo string + poolCoins sdk.Coins + preFundCoins sdk.Coins + feeCollectorAddress sdk.AccAddress + expectPass bool + }{ + { + name: "happy path", + denomToSwapTo: defaultTxFeesDenom, + poolCoins: defaultPoolCoins, + preFundCoins: defaultBalanceToSwap, + }, + + // TODO: add more test cases + // - pool does not exist for denom pair but protorev has it set for a pair + // - error in swap due to no liquidity + // - same denom in balance as denomToSwapTo + // - no pool exists for denom pair in protorev + // - many tokens in balance, some get swapped, others don't + // - different order of denoms in SetPoolForDenomPair() + } + + for _, tc := range tests { + tc := tc + + s.Run(tc.name, func() { + s.Setup() + + // Sets up account with no balance + testAccount := apptesting.CreateRandomAccounts(1)[0] + + // Create a pool to be swapped against + poolId := s.PrepareConcentratedPoolWithCoins(tc.poolCoins[0].Denom, tc.poolCoins[1].Denom).GetId() + + s.FundAcc(s.TestAccs[0], tc.poolCoins) + _, err := s.App.ConcentratedLiquidityKeeper.CreateFullRangePosition(s.Ctx, poolId, s.TestAccs[0], tc.poolCoins) + s.Require().NoError(err) + + // Set the pool for the denom pair + s.App.ProtoRevKeeper.SetPoolForDenomPair(s.Ctx, tc.poolCoins[0].Denom, tc.poolCoins[1].Denom, poolId) + + // Fund the account with the preFundCoins + s.FundAcc(testAccount, tc.preFundCoins) + + s.App.TxFeesKeeper.SwapNonNativeFeeToDenom(s.Ctx, tc.denomToSwapTo, testAccount) + + // Check balance + balances := s.App.BankKeeper.GetAllBalances(s.Ctx, testAccount) + s.Require().Len(balances, 1) + + // Check that the denomToSwapTo is the denom of the balance + s.Require().Equal(balances[0].Denom, tc.denomToSwapTo) }) } } diff --git a/x/txfees/keeper/keeper_test.go b/x/txfees/keeper/keeper_test.go index 067ec643600..7eee526f7cf 100644 --- a/x/txfees/keeper/keeper_test.go +++ b/x/txfees/keeper/keeper_test.go @@ -11,6 +11,7 @@ import ( osmosisapp "github.com/osmosis-labs/osmosis/v19/app" "github.com/osmosis-labs/osmosis/v19/app/apptesting" + protorevtypes "github.com/osmosis-labs/osmosis/v19/x/protorev/types" "github.com/osmosis-labs/osmosis/v19/x/txfees/types" ) @@ -36,6 +37,21 @@ func (s *KeeperTestSuite) SetupTest(isCheckTx bool) { WithLegacyAmino(encodingConfig.Amino). WithCodec(encodingConfig.Marshaler) + // We set the base denom here in order for highest liquidity routes to get generated. + // This is used in the tx fees epoch hook to swap the non OSMO to other tokens. + baseDenom, err := s.App.TxFeesKeeper.GetBaseDenom(s.Ctx) + s.Require().NoError(err) + + // Configure protorev base denoms + baseDenomPriorities := []protorevtypes.BaseDenom{ + { + Denom: baseDenom, + StepSize: sdk.NewInt(1_000_000), + }, + } + err = s.App.ProtoRevKeeper.SetBaseDenoms(s.Ctx, baseDenomPriorities) + s.Require().NoError(err) + // Mint some assets to the accounts. for _, acc := range s.TestAccs { s.FundAcc(acc, diff --git a/x/txfees/types/expected_keepers.go b/x/txfees/types/expected_keepers.go index 9c5668461c9..cd0de930a52 100644 --- a/x/txfees/types/expected_keepers.go +++ b/x/txfees/types/expected_keepers.go @@ -31,14 +31,14 @@ type PoolManager interface { tokenOutMinAmount sdk.Int, ) (sdk.Int, error) - // SwapExactAmountInNoTakerFee( - // ctx sdk.Context, - // sender sdk.AccAddress, - // poolId uint64, - // tokenIn sdk.Coin, - // tokenOutDenom string, - // tokenOutMinAmount sdk.Int, - // ) (sdk.Int, error) + SwapExactAmountInNoTakerFee( + ctx sdk.Context, + sender sdk.AccAddress, + poolId uint64, + tokenIn sdk.Coin, + tokenOutDenom string, + tokenOutMinAmount sdk.Int, + ) (sdk.Int, error) GetParams(ctx sdk.Context) (params poolmanagertypes.Params) } diff --git a/x/txfees/types/keys.go b/x/txfees/types/keys.go index 2ab6ff1e76f..13a39b957bc 100644 --- a/x/txfees/types/keys.go +++ b/x/txfees/types/keys.go @@ -13,13 +13,13 @@ const ( // FeeCollectorName the module account name for the fee collector account address. FeeCollectorName = "fee_collector" - // NonNativeFeeCollectorForStakingRewardsName the module account name for the alt fee collector account address (used for auto-swapping non-OSMO tx fees). + // FeeCollectorForStakingRewardsName the module account name for the alt fee collector account address (used for auto-swapping non-OSMO tx fees). // These fees go to the staking rewards pool. - NonNativeFeeCollectorForStakingRewardsName = "non_native_fee_collector" + FeeCollectorForStakingRewardsName = "non_native_fee_collector" - // NonNativeFeeCollectorForCommunityPoolName the module account name for the alt fee collector account address (used for auto-swapping non-OSMO tx fees). + // FeeCollectorForCommunityPoolName the module account name for the alt fee collector account address (used for auto-swapping non-OSMO tx fees). // These fees go to the community pool. - NonNativeFeeCollectorForCommunityPoolName = "non_native_fee_collector_community_pool" + FeeCollectorForCommunityPoolName = "non_native_fee_collector_community_pool" // QuerierRoute defines the module's query routing key QuerierRoute = ModuleName