From ba0c337f6e07ec044b2467f4f518d7a81a344c88 Mon Sep 17 00:00:00 2001 From: Niccolo Raspa Date: Fri, 20 May 2022 15:51:55 +0200 Subject: [PATCH 01/26] Add arm64 support to Docker images (#1541) Closes: #XXX ## What is the purpose of the change This PR extends the work done in #1535 to introduce support for `arm64` architecture making osmosis docker image multi-architecture (`amd64` and `arm64`). I have also updated the CI to build and push the image for multiple architectures. Please note that it takes ~20 minutes for the build complete but it would run only on every new tag so I think it's acceptable. ## Brief Changelog - Modify Dockerfile to add `arm64` support - Update docker CI to build and push for `arm64` ## Testing and Verifying You can build for arm64 with: ```bash docker buildx build --platform linux/arm64 --tag osmosis:arm64 . ``` I tested the CI in my fork: https://github.com/nikever/osmosis/runs/6505857265?check_suite_focus=true ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? yes (`arm64` support!) - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? yes - How is the feature or change documented? not applicable --- .github/workflows/docker.yml | 3 ++- CHANGELOG.md | 1 + Dockerfile | 29 ++++++++++++++++++----------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8f51f8a5d0e..955bd5fe19d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -44,8 +44,9 @@ jobs: file: Dockerfile context: . push: true - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} + - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e7cf561f2aa..b815fadd4c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#1253] Add lockup duration edit method * [#1312] Stableswap: Createpool logic * [#1230] Stableswap CFMM equations +* [#1541] Add arm64 support to Docker ## [v8.0.0 - Emergency proposals upgrade](https://github.com/osmosis-labs/osmosis/releases/tag/v8.0.0) diff --git a/Dockerfile b/Dockerfile index 5fc92efd277..7bfa136b2b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,36 @@ # syntax=docker/dockerfile:1 -ARG BASE_IMG_TAG=nonroot +ARG BASE_IMG_TAG=nonroot + +# -------------------------------------------------------- +# Build +# -------------------------------------------------------- -## Build Image FROM golang:1.18.2-alpine3.15 as build RUN set -eux; apk add --no-cache ca-certificates build-base; - RUN apk add git - -# needed by github.com/zondax/hid +# Needed by github.com/zondax/hid RUN apk add linux-headers WORKDIR /osmosis COPY . /osmosis -# From https://github.com/CosmWasm/wasmd/blob/master/Dockerfile -# For more details see https://github.com/CosmWasm/wasmvm#builds-of-libwasmvm -ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.0.0/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.a -RUN sha256sum /lib/libwasmvm_muslc.a | grep f6282df732a13dec836cda1f399dd874b1e3163504dbd9607c6af915b2740479 +# CosmWasm: see https://github.com/CosmWasm/wasmvm/releases +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.0.0/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.0.0/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a +RUN sha256sum /lib/libwasmvm_muslc.aarch64.a | grep 7d2239e9f25e96d0d4daba982ce92367aacf0cbd95d2facb8442268f2b1cc1fc +RUN sha256sum /lib/libwasmvm_muslc.x86_64.a | grep f6282df732a13dec836cda1f399dd874b1e3163504dbd9607c6af915b2740479 + +# CosmWasm: copy the right library according to architecture. The final location will be found by the linker flag `-lwasmvm_muslc` +RUN cp /lib/libwasmvm_muslc.$(uname -m).a /lib/libwasmvm_muslc.a RUN BUILD_TAGS=muslc LINK_STATICALLY=true make build -## Deploy image +# -------------------------------------------------------- +# Runner +# -------------------------------------------------------- + FROM gcr.io/distroless/base-debian11:${BASE_IMG_TAG} COPY --from=build /osmosis/build/osmosisd /bin/osmosisd @@ -35,4 +43,3 @@ EXPOSE 26657 EXPOSE 1317 ENTRYPOINT ["osmosisd"] -CMD [ "start" ] \ No newline at end of file From d610ff3ded655c44fdb2f88ec228ad38fa63ba65 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 20 May 2022 20:25:00 +0200 Subject: [PATCH 02/26] Make token factory handle empty genesis configurations better (#1551) * Make token factory handle empty genesis configurations better * add start back to dockerfile Co-authored-by: Adam Tucker --- Dockerfile | 1 + x/tokenfactory/genesis.go | 3 +++ x/tokenfactory/keeper/createdenom.go | 6 ++++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7bfa136b2b5..8171358b5ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,3 +43,4 @@ EXPOSE 26657 EXPOSE 1317 ENTRYPOINT ["osmosisd"] +CMD [ "start" ] diff --git a/x/tokenfactory/genesis.go b/x/tokenfactory/genesis.go index b2bf22eb090..1d6a7d8fab7 100644 --- a/x/tokenfactory/genesis.go +++ b/x/tokenfactory/genesis.go @@ -12,6 +12,9 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { k.CreateModuleAccount(ctx) + if genState.Params.DenomCreationFee == nil { + genState.Params.DenomCreationFee = sdk.NewCoins() + } k.SetParams(ctx, genState.Params) for _, genDenom := range genState.GetFactoryDenoms() { diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go index 4889b8e2efd..1411b8c7849 100644 --- a/x/tokenfactory/keeper/createdenom.go +++ b/x/tokenfactory/keeper/createdenom.go @@ -15,8 +15,10 @@ func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, denomNonce stri if err != nil { return "", err } - if err := k.distrKeeper.FundCommunityPool(ctx, creationFee, accAddr); err != nil { - return "", err + if len(creationFee) > 0 { + if err := k.distrKeeper.FundCommunityPool(ctx, creationFee, accAddr); err != nil { + return "", err + } } denom, err := types.GetTokenDenom(creatorAddr, denomNonce) From 16007fb5004544d3f4168d36ac74a559dfe78074 Mon Sep 17 00:00:00 2001 From: Xiangan He <76530366+xBalbinus@users.noreply.github.com> Date: Fri, 20 May 2022 18:50:51 -0400 Subject: [PATCH 03/26] Docbuild automation (#1492) * docbuild automation * tokenmodule spec docs * sidebar fixes * formatting fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes Co-authored-by: Xiangan He --- x/epochs/spec/README.md | 57 +- .../05_queries_transactions.md} | 128 ++-- x/gamm/spec/README.md | 492 ++++++++++++++ x/incentives/spec/README.md | 432 +++++++++++- x/lockup/spec/README.md | 634 +++++++++++++++++- x/mint/spec/README.md | 245 ++++++- x/pool-incentives/spec/README.md | 303 ++++++++- x/tokenfactory/{ => spec}/README.md | 0 x/txfees/spec/README.md | 49 ++ 9 files changed, 2207 insertions(+), 133 deletions(-) rename x/gamm/{README.md => spec/05_queries_transactions.md} (88%) create mode 100644 x/gamm/spec/README.md rename x/tokenfactory/{ => spec}/README.md (100%) create mode 100644 x/txfees/spec/README.md diff --git a/x/epochs/spec/README.md b/x/epochs/spec/README.md index 0fb24986dd9..8e32d19e5fa 100644 --- a/x/epochs/spec/README.md +++ b/x/epochs/spec/README.md @@ -1,4 +1,4 @@ -# `epochs` +# Epochs ## Abstract @@ -18,3 +18,58 @@ they can easily be signalled upon such events. 5. **[Hooks](05_hooks.md)**\ 6. **[Queries](06_queries.md)**\ 7. **[Future improvements](07_future_improvements.md)** + +## Queries + +### epoch-infos + +Query the currently running epochInfos + +```sh +osmosisd query epochs epoch-infos +``` +::: details Example + +An example output: + +```sh +epochs: +- current_epoch: "183" + current_epoch_start_height: "2438409" + current_epoch_start_time: "2021-12-18T17:16:09.898160996Z" + duration: 86400s + epoch_counting_started: true + identifier: day + start_time: "2021-06-18T17:00:00Z" +- current_epoch: "26" + current_epoch_start_height: "2424854" + current_epoch_start_time: "2021-12-17T17:02:07.229632445Z" + duration: 604800s + epoch_counting_started: true + identifier: week + start_time: "2021-06-18T17:00:00Z" +``` +::: + +### current-epoch + +Query the current epoch by the specified identifier + +```sh +osmosisd query epochs current-epoch [identifier] +``` + +::: details Example + +Query the current `day` epoch: + +```sh +osmosisd query epochs current-epoch day +``` + +Which in this example outputs: + +```sh +current_epoch: "183" +``` +::: \ No newline at end of file diff --git a/x/gamm/README.md b/x/gamm/spec/05_queries_transactions.md similarity index 88% rename from x/gamm/README.md rename to x/gamm/spec/05_queries_transactions.md index 925e7165bae..4138b2a07f8 100644 --- a/x/gamm/README.md +++ b/x/gamm/spec/05_queries_transactions.md @@ -17,55 +17,44 @@ The **Query** submodule of the GAMM module provides the logic to request informa - [Total Liquidity](#total-liquidity) - [Total Share](#total-share) -## Estimate Swap Exact Amount In - +### Estimate Swap Exact Amount In Query the estimated result of the [Swap Exact Amount In](#swap-exact-amount-in) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. - -### Usage - +#### Usage ```sh osmosisd query gamm estimate-swap-exact-amount-in [flags] ``` - -### Example - +#### Example Query the amount of ATOM the sender would receive for swapping 1 OSMO in pool 1. ```sh osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 ``` -## Estimate Swap Exact Amount Out +### Estimate Swap Exact Amount Out Query the estimated result of the [Swap Exact Amount Out](#swap-exact-amount-out) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. - -### Usage - +#### Usage ```sh osmosisd query gamm estimate-swap-exact-amount-out [flags] ``` - -### Example - +#### Example Query the amount of OSMO the sender would require to swap 1 ATOM out of pool 1. ```sh osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --swap-route-pool-ids 1 --swap-route-denoms uosmo ``` -## Num Pools +### Num Pools Query the number of active pools. - -### Usage - +#### Usage ```sh osmosisd query gamm num-pools ``` ## Pool - -Query the parameter and assets of a specific pool. +<<<<<<< HEAD:x/gamm/spec/05_queries_transactions.md +Query the parameter and assets of a specific pool. ### Usage @@ -75,106 +64,79 @@ osmosisd query gamm pool [flags] ### Example +>>>>>>> main:x/gamm/README.md Query parameters and assets from pool 1. ```sh osmosisd query gamm pool 1 ``` -## Pool Assets +### Pool Assets Query the assets of a specific pool. This query is a reduced form of the [Pool](#pool) query. - -### Usage - +#### Usage ```sh osmosisd query gamm pool-assets [flags] ``` Query the assets from pool 1. - -### Example - +#### Example ```sh osmosisd query gamm pool-assets 1 ``` -## Pool Params +### Pool Params Query the parameters of a specific pool. This query is a reduced form of the [Pool](#pool) query. - -### Usage - +#### Usage ```sh osmosisd query gamm pool-params [flags] ``` Query the parameters from pool 1. - -### Example - +#### Example ```sh osmosisd query gamm pool-params 1 ``` -## Pools +### Pools Query parameters and assets of all active pools. -### Usage - -```sh -osmosisd query gamm pools -``` - -## Spot Price - -Query the spot price of a pool asset based on a specific pool it is in. - -### Usage - -```sh -osmosisd query gamm spot-price [flags] -``` - -### Example +#### Usage Query the price of OSMO based on the price of ATOM in pool 1. ```sh osmosisd query gamm spot-price 1 uosmo ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 ``` -## Total Liquidity +### Total Liquidity Query the total liquidity of all active pools. - -### Usage - +#### Usage ```sh osmosisd query gamm total-liquidity ``` -## Total Share +### Total Share Query the total amount of GAMM shares of a specific pool. - -### Usage - +#### Usage ```sh osmosisd query gamm total-share [flags] ``` - -### Example - +#### Example Query the total amount of GAMM shares of pool 1. ```sh osmosisd query gamm total-share 1 ``` -# Transactions + + +### Transactions The **Transaction** submodule of the GAMM module provides the logic to create and interact with the liquidity pools. It contains the following functions: - [Create Pool](#create-pool) @@ -187,8 +149,8 @@ The **Transaction** submodule of the GAMM module provides the logic to create an - [Swap Exact Amount In](#swap-exact-amount-in) - [Swap Exact Amount Out](#swap-exact-amount-out) -## Create Pool +### Create Pool Create a new liquidity pool and provide the initial liquidity to it. Pool initialization parameters must be provided through a JSON file using the flag *pool-file*. #### Usage @@ -199,7 +161,7 @@ osmosisd tx gamm create-pool [flags] The configuration file *config.json* must specify the following parameters. -```sh +```json { "weights": [list weighted denoms], "initial-deposit": [list of denoms with initial deposit amount], @@ -208,9 +170,7 @@ The configuration file *config.json* must specify the following parameters. "future-governor": [number of hours] } ``` - -### Example - +#### Example Create a new ATOM-OSMO liquidity pool with a swap and exit fee of 1%. ```sh @@ -219,7 +179,7 @@ tx gamm create-pool --pool-file ../public/config.json --from myKeyringWallet The configuration file contains the following parameters. -```sh +```json { "weights": "5uatom,5uosmo", "initial-deposit": "100uatom,100uosmo", @@ -229,8 +189,9 @@ The configuration file contains the following parameters. } ``` -## Join Pool + +### Join Pool Join a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *max-amounts-in* and *share-amount-out* are required. #### Usage @@ -247,8 +208,8 @@ Join pool 1 with 1 OSMO and the respective amount of ATOM, using myKeyringWallet osmosisd tx gamm join-pool --pool-id 2 --max-amounts-in 1000000uosmo --max-amounts-in 1000000uion --share-amount-out 1000000 --from myKeyringWallet ``` -## Exit Pool +### Exit Pool Exit a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *min-amounts-out* and *share-amount-in* are required. #### Usage @@ -265,8 +226,8 @@ Exit pool one with 1 OSMO and the respective amount of ATOM using myKeyringWalle osmosisd tx gamm exit-pool --pool-id 1 --min-amounts-out 1000000uosmo --share-amount-in 1000000 --from myKeyringWallet ``` -## Join Swap Extern Amount In +### Join Swap Extern Amount In Note that the flags *pool-id* is required. #### Usage @@ -281,12 +242,10 @@ osmosisd tx gamm join-swap-extern-amount-in [token-in] [share-out-min-amount] [f osmosisd tx gamm join-swap-extern-amount-in 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet ``` -## Exit Swap Extern Amount Out +### Exit Swap Extern Amount Out Note that the flag *pool-id* is required. -#### Usage - ```sh osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] [flags] ``` @@ -295,11 +254,10 @@ osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] [ ```sh osmosisd tx gamm exit-swap-extern-amount-out 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet - ``` -## Join Swap Share Amount Out +### Join Swap Share Amount Out Note that the flag *pool-id* is required. #### Usage @@ -314,8 +272,8 @@ osmosisd tx gamm join-swap-share-amount-out [token-in-denom] [token-in-max-amoun osmosisd tx gamm join-swap-share-amount-out uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet ``` -## Exit Swap Share Amount In +### Exit Swap Share Amount In Note that the flag *pool-id* is required. #### Usage @@ -330,8 +288,7 @@ osmosisd tx gamm exit-swap-share-amount-in [token-out-denom] [share-in-amount] [ osmosisd tx gamm exit-swap-share-amount-in uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet ``` -## Swap Exact Amount In - +### Swap Exact Amount In Swap an exact amount of tokens into a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. #### Usage @@ -348,8 +305,8 @@ Swap 1 OSMO through pool 1 into at least 0.3 ATOM using MyKeyringWallet. osmosisd tx gamm swap-exact-amount-in 1000000uosmo 300000 --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from MyKeyringWallet ``` -## Swap Exact Amount Out +### Swap Exact Amount Out Swap an exact amount of tokens out of a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. ### Usage @@ -367,6 +324,5 @@ osmosisd tx gamm swap-exact-amount-out 1000000ibc/27394FB092D2ECCD56123C74F36E4C ``` ## Other resources - -- [Creating a liquidity bootstrapping pool](./client/docs/create-lbp-pool.md) -- [Creating a pool with a pool file](./client/docs/create-pool.md) +* [Creating a liquidity bootstrapping pool](./client/docs/create-lbp-pool.md) +* [Creating a pool with a pool file](./client/docs/create-pool.md) diff --git a/x/gamm/spec/README.md b/x/gamm/spec/README.md new file mode 100644 index 00000000000..ac06df03feb --- /dev/null +++ b/x/gamm/spec/README.md @@ -0,0 +1,492 @@ +# GAMM + +The ``GAMM`` module (**G**eneralized **A**utomated **M**arket **M**aker) provides the logic to create and interact with liquidity pools on the Osmosis DEX. + +## Contents + +1. **[Weights](0x_weights.md)** +2. **[Concepts](01_concepts.md)** +3. **[Pool Params](02_pool_params.md)** +4. **[Messages](03_msgs.md)** +5. **[Params](04_params.md)** +5. **[Queries and Transactions](05_queries_transactions.md)** + +## Overview + +### Network Parameters + +Pools have the following parameters: + +- SwapFee +- ExitFee +- FutureGovernor +- Weights +- SmoothWeightChangeParams + +We will go through these in sequence. + +1. **SwapFee** - + The swap fee is the cut of all swaps that goes to the Liquidity Providers (LPs) for a pool. Suppose a pool has a swap fee `s`. Then if a user wants to swap `T` tokens in the pool, `sT` tokens go to the LP's, and then `(1 - s)T` tokens are swapped according to the AMM swap function. +2. **ExitFee** - + The exit fee is a fee that is applied to LP's that want to remove their liquidity from the pool. Suppose a pool has an exit fee `e`. If they currently have `S` LP shares, then when they remove their liquidity they get tokens worth `(1 - e)S` shares back. The remaining `eS` shares are then burned, and the tokens corresponding to these shares are kept as liquidity. +3. **FutureGovernor** - + Osmosis plans to allow every pool to act as a DAO, with its own governance in a future upgrade. To facilitate this transition, we allow pools to specify who the governor should be as a string. There are currently 3 options for the future governor. + - No one will govern it. This is done by leaving the future governor string as blank. + - Allow a given address to govern it. This is done by setting the future governor as a bech32 address. + - Lockups to a token. This is the full DAO scenario. The future governor specifies a token denomination `denom`, and a lockup duration `duration`. This says that "all tokens of denomination `denom` that are locked up for `duration` or longer, have equal say in governance of this pool". +4. **Weights** - + This defines the weights of the pool - [https://balancer.fi/whitepaper.pdf](https://balancer.fi/whitepaper.pdf) +5. **SmoothWeightChangeParams** - + This allows pool governance to smoothly change the weights of the assets it holds in the pool. So it can slowly move from a 2:1 ratio, to a 1:1 ratio. + Currently, smooth weight changes are implemented as a linear change in weight ratios over a given duration of time. So weights changed from 4:1 to 2:2 over 2 days, then at day 1 of the change, the weights would be 3:1.5, and at day 2 its 2:2, and will remain at these weight ratios. + +The GAMM module also has a **PoolCreationFee** parameter, which currently is set to `100000000 uosmo` or `100 OSMO`. + +[comment]: <> (TODO Add better description of how the weights affect things) + + + +
+
+ +## Transactions + + +### create-pool +Create a new liquidity pool and provide initial liquidity to it. + +```sh +osmosisd tx gamm create-pool [config-file] --from --chain-id +``` + +::: details Example +The JSON [config-file] must specify the following parameters: + +```json +{ + "weights": [list weighted denoms], + "initial-deposit": [list of denoms with initial deposit amount], + "swap-fee": [swap fee in percentage], + "exit-fee": [exit fee in percentage], + "future-governor": [see options in pool parameters section above] +} +``` + +Create a new 50/50 AKT-OSMO liquidity pool with a swap and exit fee of 1%. + +```sh +osmosisd tx gamm create-pool --pool-file [config-file] --from WALLET_NAME --chain-id osmosis-1 +``` + +The configuration json file contains the following parameters: + +```json +{ + "weights": "5ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4,5uosmo", + "initial-deposit": "499404ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4,500000uosmo", + "swap-fee": "0.01", + "exit-fee": "0.01", + "future-governor": "" +} +``` +::: + +::: warning +There is now a 100 OSMO fee for creating pools. +::: + + + +### join-pool +Add liquidity to a specified pool to get an **exact** amount of LP shares while specifying a **maximum** number tokens willing to swap to receive said LP shares. + +```sh +osmosisd tx gamm join-pool --pool-id --max-amounts-in --share-amount-out --from --chain-id +``` + +::: details Example + +Join `pool 3` with a **maximum** of `.037753 AKT` and the corresponding amount of `OSMO` to get an **exact** share amount of `1.227549469722224220 gamm/pool/3` using `WALLET_NAME` on the osmosis mainnet: + +```sh +osmosisd tx gamm join-pool --pool-id 3 --max-amounts-in 37753ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --share-amount-out 1227549469722224220 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + + +### exit-pool +Remove liquidity from a specified pool with an **exact** amount of LP shares while specifying the **minimum** number of tokens willing to receive for said LP shares. + +```sh +osmosisd tx gamm exit-pool --pool-id --min-amounts-out --share-amount-in --from --chain-id +``` + +::: details Example + +Exit `pool 3` with for **exactly** `1.136326462628731195 gamm/pool/3` in order to receive a **minimum** of `.033358 AKT` and the corresponding amount of `OSMO` using `WALLET_NAME` on the osmosis mainnet: + +```sh +osmosisd tx gamm exit-pool --pool-id 3 --min-amounts-out 33358ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --share-amount-in 1136326462628731195 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + + +### join-swap-extern-amount-in + +Add liquidity to a specified pool with only one of the required assets (i.e. Join pool 1 (50/50 ATOM-OSMO) with just ATOM). + +This command essentially swaps an **exact** amount of an asset for the required pairing and then converts the pair to a **minimum** of the requested LP shares in a single step (i.e. combines the `swap-exact-amount-in` and `join-pool` commands) + +```sh +osmosisd tx gamm join-swap-extern-amount-in [token-in] [share-out-min-amount] --from --pool-id --chain-id +``` + +::: details Example + +Join `pool 3` with **exactly** `.200000 AKT` (and `0 OSMO`) to get a **minimum** of `3.234812471272883046 gamm/pool/3` using `WALLET_NAME` on the osmosis mainnet: + +```sh +osmosisd tx gamm join-swap-extern-amount-in 200000ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 3234812471272883046 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + + +### exit-swap-extern-amount-out + +Remove liquidity from a specified pool with a **maximum** amount of LP shares and swap to an **exact** amount of one of the token pairs (i.e. Leave pool 1 (50/50 ATOM-OSMO) and receive 100% ATOM instead of 50% OSMO and 50% ATOM). + +This command essentially converts an LP share into the corresponding share of tokens and then swaps to the specified `token-out` in a single step (i.e. combines the `swap-exact-amount-out` and `exit-pool` commands) + +```sh +osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] --pool-id --from --chain-id +``` + +::: details Example + +Exit `pool 3` by removing a **maximum** of `3.408979387886193586 gamm/pool/3` and swap the `OSMO` portion of the LP share to receive 100% AKT in the **exact** amount of `0.199430 AKT`: + +```sh +osmosisd tx gamm exit-swap-extern-amount-out 199430ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 3408979387886193586 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + + +### join-swap-share-amount-out + +Swap a **maximum** amount of a specified token for another token, similar to swapping a token on the trade screen GUI (i.e. takes the specified asset and swaps it to the other asset needed to join the specified pool) and then adds an **exact** amount of LP shares to the specified pool. + +```sh +osmosisd tx gamm join-swap-share-amount-out [token-in-denom] [token-in-max-amount] [share-out-amount] --pool-id --from --chain-id +``` + +::: details Example + +Swap a **maximum** of `0.312466 OSMO` for the corresponding amount of `AKT`, then join `pool 3` and receive **exactly** `1.4481270389710236872 gamm/pool/3`: + +```sh +osmosisd tx gamm join-swap-share-amount-out uosmo 312466 14481270389710236872 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + + +### exit-swap-share-amount-in + +Remove an **exact** amount of LP shares from a specified pool, swap the LP shares to one of the token pairs to receive a **minimum** of the specified token amount. + +```sh +osmosisd tx gamm exit-swap-share-amount-in [token-out-denom] [share-in-amount] [token-out-min-amount] --pool-id --from --chain-id +``` + +::: details Example + +Exit `pool 3` by removing **exactly** `14.563185400026723131 gamm/pool/3` and swap the `AKT` portion of the LP share to receive 100% OSMO in the **minimum** amount of `.298548 OSMO`: + +```sh +osmosisd tx gamm exit-swap-share-amount-in uosmo 14563185400026723131 298548 --pool-id 3 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + +### swap-exact-amount-in + +Swap an **exact** amount of tokens for a **minimum** of another token, similar to swapping a token on the trade screen GUI. + + +```sh +osmosisd tx gamm swap-exact-amount-in [token-in] [token-out-min-amount] --pool-id --from --chain-id +``` + +::: details Example + +Swap **exactly** `.407239 AKT` through `pool 3` into a **minimum** of `.140530 OSMO` using `WALLET_NAME` on the osmosis mainnet: + +```sh +osmosisd tx gamm swap-exact-amount-in 407239ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 140530 --swap-route-pool-ids 3 --swap-route-denoms uosmo --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + + +### swap-exact-amount-out + +Swap a **maximum** amount of tokens for an **exact** amount of another token, similar to swapping a token on the trade screen GUI. + +```sh +osmosisd tx gamm swap-exact-amount-out [token-out] [token-out-max-amount] --swap-route-pool-ids --from --chain-id +``` + +::: details Example + +Swap a **maximum** of `.407239 AKT` through `pool 3` into **exactly** `.140530 OSMO` using `WALLET_NAME` on the osmosis mainnet: + +```sh +osmosisd tx gamm swap-exact-amount-out 140530uosmo 407239 --swap-route-pool-ids 3 --swap-route-denoms ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 --from WALLET_NAME --chain-id osmosis-1 +``` + + +[comment]: <> (Other resources Creating a liquidity bootstrapping pool and Creating a pool with a pool file) +::: + + + + +## Queries + +### estimate-swap-exact-amount-in + +Query the estimated result of the [swap-exact-amount-in](#swap-exact-amount-in) transaction. + +```sh +osmosisd query gamm estimate-swap-exact-amount-in [poolID] [sender] [tokenIn] --swap-route-pool-ids --swap-route-denoms +``` + +::: details Example + +Query the amount of ATOM (or `ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2`) the `sender` would receive for swapping `1 OSMO` in `pool 1`. + +```sh +osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +``` +::: + + + +### estimate-swap-exact-amount-out + +Query the estimated result of the [swap-exact-amount-out](#swap-exact-amount-out) transaction. + +```sh +osmosisd query gamm estimate-swap-exact-amount-out [poolID] [sender] [tokenOut] --swap-route-pool-ids --swap-route-denoms +``` + +::: details Example + +Query the amount of `OSMO` the `sender` would require to swap 1 ATOM (or `1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2`) our of `pool 1`: + +```sh +osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --swap-route-pool-ids 1 --swap-route-denoms uosmo +``` +::: + + + +### num-pools + +Query the number of active pools. + +```sh +osmosisd query gamm num-pools +``` + + + +### pool + +Query the parameter and assets of a specific pool. + +```sh +osmosisd query gamm pool [poolID] [flags] +``` + +::: details Example + +Query parameters and assets from `pool 1`. + +```sh +osmosisd query gamm pool 1 +``` + +Which outputs: + +```sh + address: osmo1mw0ac6rwlp5r8wapwk3zs6g29h8fcscxqakdzw9emkne6c8wjp9q0t3v8t + id: 1 + pool_params: + swap_fee: "0.003000000000000000" + exit_fee: "0.000000000000000000" + smooth_weight_change_params: null + future-governor: 24h + total_weight: "1000000.000000000000000000" + total_shares: + denom: gamm/pool/1 + amount: "252329392916236134754561337" + pool_assets: + - | + token: + denom: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 + amount: "4024633105693" + weight: "500000.000000000000000000" + - | + token: + denom: uosmo + amount: "21388879300450" + weight: "500000.000000000000000000" +``` +::: + + +### pool-assets + +Query the assets of a specific pool. This query is a reduced form of the [pool](#pool) query. + +```sh +osmosisd query gamm pool-assets [poolID] [flags] +``` + +::: details Example + +Query the assets from `pool 1`. + +```sh +osmosisd query gamm pool-assets 1 +``` + +Which outputs: + +```sh +poolAssets: +- token: + amount: "4024839695885" + denom: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 + weight: "536870912000000" +- token: + amount: "21387918414792" + denom: uosmo + weight: "536870912000000" +``` +::: + + +### Pool Params + +Query the parameters of a specific pool. This query is a reduced form of the [pool](#pool) query. + +```sh +osmosisd query gamm pool-params [poolID] [flags] +``` + +::: details Example + +Query the parameters from pool 1. + +```sh +osmosisd query gamm pool-params 1 +``` + +Which outputs: + +```sh +swap_fee: "0.003000000000000000" +exit_fee: "0.000000000000000000" +smooth_weight_change_params: null +``` +::: + + +### pools + +Query parameters and assets of all active pools. + +```sh +osmosisd query gamm pools +``` + + + + +### spot-price + +Query the spot price of a pool asset based on a specific pool it is in. + +```sh +osmosisd query gamm spot-price [poolID] [tokenInDenom] [tokenOutDenom] [flags] +``` + +::: details Example + +Query the price of OSMO based on the price of ATOM in pool 1: + +```sh +osmosisd query gamm spot-price 1 uosmo ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +``` + +Which outputs: + +```sh +spotPrice: "5.314387014412388547" +``` + +In other words, at the time of this writing, ~5.314 OSMO is equivalent to 1 ATOM. +::: + + + + +### total-liquidity + +Query the total liquidity of all active pools. + +```sh +osmosisd query gamm total-liquidity +``` + + + + +### total-share + +Query the total amount of GAMM shares of a specific pool. + +```sh +osmosisd query gamm total-share [poolID] [flags] +``` + +::: details Example + +Query the total amount of GAMM shares of pool 1. + +```sh +osmosisd query gamm total-share 1 +``` + +Which outputs: + +```sh +totalShares: + amount: "252328895834096787303097071" + denom: gamm/pool/1 +``` + +Indicating there are a total of `252328895.834096787303097071 gamm/pool/1` at the time of this writing +::: \ No newline at end of file diff --git a/x/incentives/spec/README.md b/x/incentives/spec/README.md index 9096d6d26c7..637a530e257 100644 --- a/x/incentives/spec/README.md +++ b/x/incentives/spec/README.md @@ -4,22 +4,15 @@ Incentives module provides general interface to give yield to stakers. -The yield to be given to stakers are stored in `gauge` and it is -distributed on epoch basis to the stakers who meet specific conditions. +The yield to be given to stakers are stored in `gauge` and it is distributed on epoch basis to the stakers who meet specific conditions. -Anyone can create gauge and add rewards to the gauge, there is no way to -take it out other than distribution. +Anyone can create gauge and add rewards to the gauge, there is no way to take it out other than distribution. There are two kinds of `gauges`, perpetual and non-perpetual ones. -- Non perpetual ones get removed from active queue after the the - distribution period finish but perpetual ones persist. -- For non perpetual ones, they distribute the tokens equally per epoch - during the `gauge` is in the active period. -- For perpetual ones, it distribute all the tokens at a single time - and somewhere else put the tokens regularly to distribute the - tokens, it's mainly used to distribute minted OSMO tokens to LP - token stakers. +- Non perpetual ones get removed from active queue after the the distribution period finish but perpetual ones persist. +- For non perpetual ones, they distribute the tokens equally per epoch during the `gauge` is in the active period. +- For perpetual ones, it distribute all the tokens at a single time and somewhere else put the tokens regularly to distribute the tokens, it's mainly used to distribute minted OSMO tokens to LP token stakers. ## Contents @@ -27,6 +20,417 @@ There are two kinds of `gauges`, perpetual and non-perpetual ones. 2. **[State](02_state.md)** 3. **[Messages](03_messages.md)** 4. **[Events](04_events.md)** -5. **[Hooks](05_hooks.md)**\ -6. **[Queries](06_queries.md)**\ +5. **[Hooks](05_hooks.md)** +6. **[Queries](06_queries.md)** 7. **[Params](07_params.md)** + +## Overview + +The purpose of incentives module is to provide incentives to users who lock certain tokens for specified periods of time. + +Locked tokens can be of any denomination, including LP tokens (gamm/pool/x), IBC tokens (tokens sent through IBC such as ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2), and native tokens (such as ATOM or LUNA). + +The incentive amount is entered by the gauge creator. Rewards for a given pool of locked up tokens are pooled into a gauge until the disbursement time. At the disbursement time, they are distributed pro-rata (proportionally) to members of the pool. + +Anyone can create a gauge and add rewards to the gauge. There is no way to withdraw gauge rewards other than distribution. Governance proposals can be raised to match the external incentive tokens with equivalent Osmo incentives (see for example: [proposal 47](https://www.mintscan.io/osmosis/proposals/47)). + +There are two kinds of gauges: **`perpetual`** and **`non-perpetual`**: + +- **`Non-perpetual`** gauges distribute their tokens equally per epoch while the gauge is in the active period. These gauges get removed from the active queue after the distribution period finishes + +- **`Perpetual gauges`** distribute all their tokens at a single time and only distribute their tokens again once the gauge is refilled (this is mainly used to distribute minted OSMO tokens to LP token stakers). Perpetual gauges persist and will re-disburse tokens when refilled (there is no "active" period) + + +
+
+ +## Transactions + +### create-gauge + +Create a gauge to distribute rewards to users + +```sh +osmosisd tx incentives create-gauge [lockup_denom] [reward] [flags] +``` + +::: details Example 1 + + +I want to make incentives for LP tokens of pool 3, namely gamm/pool/3 that have been locked up for at least 1 day. +I want to reward 100 AKT to this pool over 2 days (2 epochs). (50 rewarded on each day) +I want the rewards to start dispersing on 21 December 2021 (1640081402 UNIX time) + +```bash +osmosisd tx incentives create-gauge gamm/pool/3 10000ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 \ +--duration 24h --start-time 1640081402 --epochs 2 --from WALLET_NAME --chain-id osmosis-1 +``` +::: + +::: details Example 2 + +I want to make incentives for ATOM (ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2) that have been locked up for at least 1 week (164h). +I want to reward 1000 JUNO (ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED) to ATOM holders perpetually (perpetually meaning I must add more tokens to this gauge myself every epoch). I want the reward to start dispersing immediately. + +```bash +osmosisd tx incentives create-gauge ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 \ +1000000000ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED --perpetual --duration 168h \ +--from WALLET_NAME --chain-id osmosis-1 +``` +::: + + +### add-to-gauge + +Add coins to a gauge previously created to distribute more rewards to users + +```sh +osmosisd tx incentives add-to-gauge [gauge_id] [rewards] [flags] +``` + +::: details Example + +I want to refill the gauge with 500 JUNO to a previously created gauge (gauge ID 1914) after the distribution. + +```bash +osmosisd tx incentives add-to-gauge 1914 500000000ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED \ +--from WALLET_NAME --chain-id osmosis-1 +``` +::: + + +## Queries + +### active-gauges + +Query active gauges + +```sh +osmosisd query incentives active-gauges [flags] +``` + +::: details Example + +```bash +osmosisd query incentives active-gauges +``` + +An example output + +```sh +- coins: [] + distribute_to: + denom: gamm/pool/99 + duration: 604800s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "297" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-07-03T12:27:09.323840990Z" +- coins: [] + distribute_to: + denom: gamm/pool/99 + duration: 1209600s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "298" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-07-03T12:27:09.323840990Z" +pagination: + next_key: BwEAAAAAAAAAHTIwMjEtMDctMDNUMTI6Mjc6MDkuMzIzODQwOTkw + total: "0" +... +``` +::: + + + +### active-gauges-per-denom + +Query active gauges per denom + +```sh +osmosisd query incentives active-gauges-per-denom [denom] [flags] +``` + +::: details Example + +Query all active gauges distributing incentives to holders of gamm/pool/341 + +```bash +osmosisd query incentives active-gauges-per-denom gamm/pool/341 +``` + +An example output: + +```sh +- coins: [] + distribute_to: + denom: gamm/pool/341 + duration: 604800s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "1033" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-09-06T22:42:52.139465318Z" +- coins: [] + distribute_to: + denom: gamm/pool/341 + duration: 1209600s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "1034" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-09-06T22:42:52.139465318Z" +pagination: + next_key: BwEAAAAAAAAAHTIwMjEtMDctMDNUMTI6Mjc6MDkuMzIzODQwOTkw + total: "0" +... +``` +::: + + + +### distributed-coins + +Query coins distributed so far + +```sh +osmosisd query incentives distributed-coins [flags] +``` + +::: details Example + +```bash +osmosisd query incentives distributed-coins +``` + +An example output: + +```sh +coins: +- amount: "27632051924" + denom: ibc/0954E1C28EB7AF5B72D24F3BC2B47BBB2FDF91BDDFD57B74B99E133AED40972A +- amount: "3975960654" + denom: ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0 +- amount: "125999980901" + denom: ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 +- amount: "434999992789" + denom: ibc/1DC495FCEFDA068A3820F903EDBD78B942FBD204D7E93D3BA2B432E9669D1A59 +- amount: "3001296" + denom: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +- amount: "1493887986685" + denom: ibc/3BCCC93AD5DF58D11A6F8A05FA8BC801CBA0BA61A981F57E91B8B598BF8061CB +- amount: "372218215714" + denom: ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED +- amount: "1049999973206" + denom: ibc/4E5444C35610CC76FC94E7F7886B93121175C28262DDFDDE6F84E82BF2425452 +- amount: "11666666665116" + denom: ibc/7A08C6F11EF0F59EB841B9F788A87EC9F2361C7D9703157EC13D940DC53031FA +- amount: "13199999715662" + denom: ibc/9712DBB13B9631EDFA9BF61B55F1B2D290B2ADB67E3A4EB3A875F3B6081B3B84 +- amount: "1177777428443" + denom: ibc/D805F1DA50D31B96E4282C1D4181EDDFB1A44A598BFF5666F4B43E4B8BEA95A5 +- amount: "466666567747" + denom: ibc/EA3E1640F9B1532AB129A571203A0B9F789A7F14BB66E350DCBFA18E1A1931F0 +- amount: "79999999178" + denom: ibc/F3FF7A84A73B62921538642F9797C423D2B4C4ACB3C7FCFFCE7F12AA69909C4B +- amount: "65873607694598" + denom: uosmo +``` +::: + + +### gauge-by-id + +Query gauge by id + +```sh +osmosisd query incentives gauge-by-id [id] [flags] +``` + +::: details Example + +Query the incentive distribution for gauge ID 1: + +```sh +osmosisd query incentives gauge-by-id 1 +``` + +```bash +gauge: + coins: + - amount: "16654747773959" + denom: uosmo + distribute_to: + denom: gamm/pool/1 + duration: 86400s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: + - amount: "16589795315655" + denom: uosmo + filled_epochs: "182" + id: "1" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-06-19T04:30:19.082462364Z" +``` +::: + + + + +### gauges + +Query available gauges + +```sh +osmosisd query incentives gauges [flags] +``` + +::: details Example + +Query ALL gauges (by default the limit is 100, so here I will define a much larger number to output all gauges) + +```bash +osmosisd query incentives gauges --limit 2000 +``` + +An example output: + +```sh +- coins: + - amount: "1924196414964" + denom: uosmo + distribute_to: + denom: gamm/pool/348 + duration: 604800s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "8" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-10-04T13:59:02.142175968Z" +- coins: + - amount: "641398804181" + denom: uosmo + distribute_to: + denom: gamm/pool/348 + duration: 1209600s + lock_query_type: ByDuration + timestamp: "0001-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "9" + is_perpetual: true + num_epochs_paid_over: "1" + start_time: "2021-10-04T13:59:02.142175968Z" +pagination: + next_key: null + total: "0" +... +``` +::: + +### rewards-estimation + +Query rewards estimation + +// Error: strconv.ParseUint: parsing "": invalid syntax + + +### to-distribute-coins + +Query coins that is going to be distributed + +```sh +osmosisd query incentives to-distribute-coins [flags] +``` + +::: details Example + +```bash +osmosisd query incentives to-distribute-coins +``` + +An example output: + +```sh +coins: +- amount: "20000000" + denom: gamm/pool/87 +- amount: "90791948076" + denom: ibc/0954E1C28EB7AF5B72D24F3BC2B47BBB2FDF91BDDFD57B74B99E133AED40972A +- amount: "10000" + denom: ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 +- amount: "1000" + denom: ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +- amount: "10728832013315" + denom: ibc/3BCCC93AD5DF58D11A6F8A05FA8BC801CBA0BA61A981F57E91B8B598BF8061CB +- amount: "627782783496" + denom: ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED +- amount: "450000026794" + denom: ibc/4E5444C35610CC76FC94E7F7886B93121175C28262DDFDDE6F84E82BF2425452 +- amount: "38333333334884" + denom: ibc/7A08C6F11EF0F59EB841B9F788A87EC9F2361C7D9703157EC13D940DC53031FA +- amount: "46800000284338" + denom: ibc/9712DBB13B9631EDFA9BF61B55F1B2D290B2ADB67E3A4EB3A875F3B6081B3B84 +- amount: "2822222571557" + denom: ibc/D805F1DA50D31B96E4282C1D4181EDDFB1A44A598BFF5666F4B43E4B8BEA95A5 +- amount: "2533333432253" + denom: ibc/EA3E1640F9B1532AB129A571203A0B9F789A7F14BB66E350DCBFA18E1A1931F0 +- amount: "366164843847" + denom: uosmo +``` +::: + + +### upcoming-gauges + +Query scheduled gauges (gauges whose `start_time` has not yet occurred) + +```sh +osmosisd query incentives upcoming-gauges [flags] +``` + +::: details Example + +```bash +osmosisd query incentives upcoming-gauges +``` + +Using this command, we will see the gauge we created earlier, among all other upcoming gauges: + +```sh +- coins: + - amount: "10000" + denom: ibc/1480B8FD20AD5FCAE81EA87584D269547DD4D436843C1D20F15E00EB64743EF4 + distribute_to: + denom: gamm/pool/3 + duration: 86400s + lock_query_type: ByDuration + timestamp: "1970-01-01T00:00:00Z" + distributed_coins: [] + filled_epochs: "0" + id: "1914" + is_perpetual: false + num_epochs_paid_over: "2" + start_time: "2021-12-21T10:10:02Z" +... +``` +::: \ No newline at end of file diff --git a/x/lockup/spec/README.md b/x/lockup/spec/README.md index 18a6266e9fe..9d4a9821b56 100644 --- a/x/lockup/spec/README.md +++ b/x/lockup/spec/README.md @@ -1,16 +1,14 @@ -# `lockup` +# Lockup ## Abstract -Lockup module provides an interface for users to lock tokens into the -module to get incentives. +Lockup module provides an interface for users to lock tokens (also known as bonding) into the module to get incentives. -Users can lock tokens with specific duration and to unlock users should -start unlock and wait for the unlock period that's set initially. After -unlock period finish, users can claim tokens from the module. +After tokens have been added to a specific pool and turned into LP shares through the GAMM module, users can then lock these LP shares with a specific duration in order to begin earing rewards. -This module provides interfaces for other modules to iterate the locks -efficiently and grpc query to check the status of locked coins. +To unlock these LP shares, users must trigger the unlock timer and wait for the unlock period that was set initially to be completed. After the unlock period is over, users can turn LP shares back into their respective share of tokens. + +This module provides interfaces for other modules to iterate the locks efficiently and grpc query to check the status of locked coins. ## Contents @@ -23,3 +21,623 @@ efficiently and grpc query to check the status of locked coins. 7. **[Queries](07_queries.md)**\ 8. **[Params](08_params.md)** 9. **[Endblocker](09_endblocker.md)** + +## Overview + +There are currently three incentivize lockup periods; `1 day` (24h), `1 week` (168h), and `2 weeks` (336h). When locking tokens in the 2 week period, the liquidity provider is effectively earning rewards for a combination of the 1 day, 1 week, and 2 week bonding periods. + +The 2 week period refers to how long it takes to unbond the LP shares. The liquidity provider can keep their LP shares bonded to the 2 week lockup period indefinitely. Unbonding is only required when the liquidity provider desires access to the underlying assets. + +If the liquidity provider begins the unbonding process for their 2 week bonded LP shares, they will earn rewards for all three bonding periods during the first day of unbonding. + +After the first day passes, they will only receive rewards for the 1 day and 1 week lockup periods. After seven days pass, they will only receive the 1 day rewards until the 2 weeks is complete and their LP shares are unlocked. The below chart is a visual example of what was just explained. + +
+

+ +

+ +
+
+ +## Transactions + +### lock-tokens + +Bond tokens in a LP for a set duration + +```sh +osmosisd tx lockup lock-tokens [tokens] --duration --from --chain-id +``` + +::: details Example + +To lockup `15.527546134174465309gamm/pool/3` tokens for a `one day` bonding period from `WALLET_NAME` on the osmosis mainnet: + +```bash +osmosisd tx lockup lock-tokens 15527546134174465309gamm/pool/3 --duration="24h" --from WALLET_NAME --chain-id osmosis-1 +``` + +To lockup `25.527546134174465309gamm/pool/13` tokens for a `one week` bonding period from `WALLET_NAME` on the osmosis testnet: + +```bash +osmosisd tx lockup lock-tokens 25527546134174465309gamm/pool/13 --duration="168h" --from WALLET_NAME --chain-id osmo-test-4 +``` + +To lockup `35.527546134174465309 gamm/pool/197` tokens for a `two week` bonding period from `WALLET_NAME` on the osmosis mainnet: + +```bash +osmosisd tx lockup lock-tokens 35527546134174465309gamm/pool/197 --duration="336h" --from WALLET_NAME --chain-id osmosis-1 +``` +::: + + +### begin-unlock-by-id + +Begin the unbonding process for tokens given their unique lock ID + +```sh +osmosisd tx lockup begin-unlock-by-id [id] --from --chain-id +``` + +::: details Example + +To begin the unbonding time for all bonded tokens under id `75` from `WALLET_NAME` on the osmosis mainnet: + +```bash +osmosisd tx lockup begin-unlock-by-id 75 --from WALLET_NAME --chain-id osmosis-1 +``` +::: +::: warning Note +The ID corresponds to the unique ID given to your lockup transaction (explained more in lock-by-id section) +::: + +### begin-unlock-tokens + +Begin unbonding process for all bonded tokens in a wallet + +```sh +osmosisd tx lockup begin-unlock-tokens --from --chain-id +``` + +::: details Example + +To begin unbonding time for ALL pools and ALL bonded tokens in `WALLET_NAME` on the osmosis mainnet: + + +```bash +osmosisd tx lockup begin-unlock-tokens --from=WALLET_NAME --chain-id=osmosis-1 --yes +``` +::: + +## Queries + +### account-locked-beforetime + +Query an account's unlocked records after a specified time (UNIX) has passed + +In other words, if an account unlocked all their bonded tokens the moment the query was executed, only the locks that would have completed their bond time requirement by the time the `TIMESTAMP` is reached will be returned. + +::: details Example + +In this example, the current UNIX time is `1639776682`, 2 days from now is approx `1639971082`, and 15 days from now is approx `1641094282`. + +An account's `ADDRESS` is locked in both the `1 day` and `1 week` gamm/pool/3. To query the `ADDRESS` with a timestamp 2 days from now `1639971082`: + +```bash +osmosisd query lockup account-locked-beforetime ADDRESS 1639971082 +``` + +In this example will output the `1 day` lock but not the `1 week` lock: + +```bash +locks: +- ID: "571839" + coins: + - amount: "15527546134174465309" + denom: gamm/pool/3 + duration: 24h + end_time: "2021-12-18T23:32:58.900715388Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +If querying the same `ADDRESS` with a timestamp 15 days from now `1641094282`: + +```bash +osmosisd query lockup account-locked-beforetime ADDRESS 1641094282 +``` + +In this example will output both the `1 day` and `1 week` lock: + +```bash +locks: +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +- ID: "571839" + coins: + - amount: "15527546134174465309" + denom: gamm/pool/3 + duration: 24h + end_time: "2021-12-18T23:32:58.900715388Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` +::: + + +### account-locked-coins + +Query an account's locked (bonded) LP tokens + +```sh +osmosisd query lockup account-locked-coins [address] +``` + +:::: details Example + +```bash +osmosisd query lockup account-locked-coins osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +An example output: + +```bash +coins: +- amount: "413553955105681228583" + denom: gamm/pool/1 +- amount: "32155370994266157441309" + denom: gamm/pool/10 +- amount: "220957857520769912023" + denom: gamm/pool/3 +- amount: "31648237936933949577" + denom: gamm/pool/42 +- amount: "14162624050980051053569" + denom: gamm/pool/5 +- amount: "1023186951315714985896914" + denom: gamm/pool/9 +``` +::: warning Note +All GAMM amounts listed are 10^18. Move the decimal place to the left 18 places to get the GAMM amount listed in the GUI. + +You may also specify a --height flag to see bonded LP tokens at a specified height (note: if running a pruned node, this may result in an error) +::: +:::: + +### account-locked-longer-duration + +Query an account's locked records that are greater than or equal to a specified lock duration + +```sh +osmosisd query lockup account-locked-longer-duration [address] [duration] +``` + +::: details Example + +Here is an example of querying an `ADDRESS` for all `1 day` or greater bonding periods: + +```bash +osmosisd query lockup account-locked-longer-duration osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 24h +``` + +An example output: + +```bash +locks: +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +- ID: "571839" + coins: + - amount: "15527546134174465309" + denom: gamm/pool/3 + duration: 24h + end_time: "2021-12-18T23:32:58.900715388Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` +::: + + +### account-locked-longer-duration-denom + +Query an account's locked records for a denom that is locked equal to or greater than the specified duration AND match a specified denom + +```sh +osmosisd query lockup account-locked-longer-duration-denom [address] [duration] [denom] +``` + +::: details Example + +Here is an example of an `ADDRESS` that is locked in both the `1 day` and `1 week` for both the gamm/pool/3 and gamm/pool/1, then queries the `ADDRESS` for all bonding periods equal to or greater than `1 day` for just the gamm/pool/3: + +```bash +osmosisd query lockup account-locked-longer-duration-denom osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 24h gamm/pool/3 +``` + +An example output: + +```bash +locks: +- ID: "571839" + coins: + - amount: "15527546134174465309" + denom: gamm/pool/3 + duration: 24h + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +As shown, the gamm/pool/3 is returned but not the gamm/pool/1 due to the denom filter. +::: + + +### account-locked-longer-duration-not-unlocking + +Query an account's locked records for a denom that is locked equal to or greater than the specified duration AND is not in the process of being unlocked + +```sh +osmosisd query lockup account-locked-longer-duration-not-unlocking [address] [duration] +``` + +::: details Example + +Here is an example of an `ADDRESS` that is locked in both the `1 day` and `1 week` gamm/pool/3, begins unlocking process for the `1 day` bond, and queries the `ADDRESS` for all bonding periods equal to or greater than `1 day` that are not unbonding: + +```bash +osmosisd query lockup account-locked-longer-duration-not-unlocking osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 24h +``` + +An example output: + +```bash +locks: +- ID: "571839" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +The `1 day` bond does not show since it is in the process of unbonding. +::: + + +### account-locked-pasttime + +Query the locked records of an account with the unlock time beyond timestamp (UNIX) + +```bash +osmosisd query lockup account-locked-pasttime [address] [timestamp] +``` + +::: details Example + +Here is an example of an account that is locked in both the `1 day` and `1 week` gamm/pool/3. In this example, the UNIX time is currently `1639776682` and queries an `ADDRESS` for UNIX time two days later from the current time (which in this example would be `1639971082`) + +```bash +osmosisd query lockup account-locked-pasttime osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 1639971082 +``` + +The example output: + +```bash +locks: +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +Note that the `1 day` lock ID did not display because, if the unbonding time began counting down from the time the command was executed, the bonding period would be complete before the two day window given by the UNIX timestamp input. +::: + + +### account-locked-pasttime-denom + +Query the locked records of an account with the unlock time beyond timestamp (unix) and filter by a specific denom + +```bash +osmosisd query lockup account-locked-pasttime-denom osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 [timestamp] [denom] +``` + +::: details Example + +Here is an example of an account that is locked in both the `1 day` and `1 week` gamm/pool/3 and `1 day` and `1 week` gamm/pool/1. In this example, the UNIX time is currently `1639776682` and queries an `ADDRESS` for UNIX time two days later from the current time (which in this example would be `1639971082`) and filters for gamm/pool/3 + +```bash +osmosisd query lockup account-locked-pasttime-denom osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 1639971082 gamm/pool/3 +``` + +The example output: + +```bash +locks: +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +Note that the `1 day` lock ID did not display because, if the unbonding time began counting down from the time the command was executed, the bonding period would be complete before the two day window given by the UNIX timestamp input. Additionally, neither of the `1 day` or `1 week` lock IDs for gamm/pool/1 showed due to the denom filter. +::: + + +### account-locked-pasttime-not-unlocking + +Query the locked records of an account with the unlock time beyond timestamp (unix) AND is not in the process of unlocking + +```sh +osmosisd query lockup account-locked-pasttime [address] [timestamp] +``` + +::: details Example + +Here is an example of an account that is locked in both the `1 day` and `1 week` gamm/pool/3. In this example, the UNIX time is currently `1639776682` and queries an `ADDRESS` for UNIX time two days later from the current time (which in this example would be `1639971082`) AND is not unlocking: + +```bash +osmosisd query lockup account-locked-pasttime osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 1639971082 +``` + +The example output: + +```bash +locks: +- ID: "572027" + coins: + - amount: "16120691802759484268" + denom: gamm/pool/3 + duration: 604800.000006193s + end_time: "0001-01-01T00:00:00Z" + owner: osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +Note that the `1 day` lock ID did not display because, if the unbonding time began counting down from the time the command was executed, the bonding period would be complete before the two day window given by the UNIX timestamp input. Additionally, if ID 572027 were to begin the unlocking process, the query would have returned blank. +::: + + +### account-unlockable-coins + +Query an address's LP shares that have completed the unlocking period and are ready to be withdrawn + +```bash +osmosisd query lockup account-unlockable-coins ADDRESS +``` + + + +### account-unlocking-coins + +Query an address's LP shares that are currently unlocking + +```sh +osmosisd query lockup account-unlocking-coins [address] +``` + +::: details Example + +```bash +osmosisd query lockup account-unlocking-coins osmo1xqhlshlhs5g0acqgrkafdemvf5kz4pp4c2x259 +``` + +Example output: + +```bash +coins: +- amount: "15527546134174465309" + denom: gamm/pool/3 +``` +::: + + +### lock-by-id + +Query a lock record by its ID + +```sh +osmosisd query lockup lock-by-id [id] +``` + +::: details Example + +Every time a user bonds tokens to an LP, a unique lock ID is created for that transaction. + +Here is an example viewing the lock record for ID 9: + +```bash +osmosisd query lockup lock-by-id 9 +``` + +And its output: + +```bash +lock: + ID: "9" + coins: + - amount: "2449472670508255020346507" + denom: gamm/pool/2 + duration: 336h + end_time: "0001-01-01T00:00:00Z" + owner: osmo16r39ghhwqjcwxa8q3yswlz8jhzldygy66vlm82 +``` + +In summary, this shows wallet `osmo16r39ghhwqjcwxa8q3yswlz8jhzldygy66vlm82` bonded `2449472.670 gamm/pool/2` LP shares for a `2 week` locking period. +::: + + +### module-balance + +Query the balance of all LP shares (bonded and unbonded) + +```sh +osmosisd query lockup module-balance +``` + +::: details Example + +```bash +osmosisd query lockup module-balance +``` + +An example output: + +```bash +coins: +- amount: "118851922644152734549498647" + denom: gamm/pool/1 +- amount: "2165392672114512349039263626" + denom: gamm/pool/10 +- amount: "9346769826591025900804" + denom: gamm/pool/13 +- amount: "229347389639275840044722315" + denom: gamm/pool/15 +- amount: "81217698776012800247869" + denom: gamm/pool/183 +- amount: "284253336860259874753775" + denom: gamm/pool/197 +- amount: "664300804648059580124426710" + denom: gamm/pool/2 +- amount: "5087102794776326441530430" + denom: gamm/pool/22 +- amount: "178900843925960029029567880" + denom: gamm/pool/3 +- amount: "64845148811263846652326124" + denom: gamm/pool/4 +- amount: "177831279847453210600513" + denom: gamm/pool/42 +- amount: "18685913727862493301261661338" + denom: gamm/pool/5 +- amount: "23579028640963777558149250419" + denom: gamm/pool/6 +- amount: "1273329284855460149381904976" + denom: gamm/pool/7 +- amount: "625252103927082207683116933" + denom: gamm/pool/8 +- amount: "1148475247281090606949382402" + denom: gamm/pool/9 +``` +::: + + +### module-locked-amount + +Query the balance of all bonded LP shares + +```sh +osmosisd query lockup module-locked-amount +``` + +::: details Example + +```bash +osmosisd query lockup module-locked-amount +``` + +An example output: + +```bash + + "coins": + { + "denom": "gamm/pool/1", + "amount": "247321084020868094262821308" + }, + { + "denom": "gamm/pool/10", + "amount": "2866946821820635047398966697" + }, + { + "denom": "gamm/pool/13", + "amount": "9366580741745176812984" + }, + { + "denom": "gamm/pool/15", + "amount": "193294911294343602187680438" + }, + { + "denom": "gamm/pool/183", + "amount": "196722012808526595790871" + }, + { + "denom": "gamm/pool/197", + "amount": "1157025085661167198918241" + }, + { + "denom": "gamm/pool/2", + "amount": "633051132033131378888258047" + }, + { + "denom": "gamm/pool/22", + "amount": "3622601406125950733194696" + }, +... + +``` + +NOTE: This command seems to only work on gRPC and on CLI returns an EOF error. +::: + + + +### output-all-locks + +Output all locks into a json file + +```sh +osmosisd query lockup output-all-locks [max lock ID] +``` + +:::: details Example + +This example command outputs locks 1-1000 and saves to a json file: + +```bash +osmosisd query lockup output-all-locks 1000 +``` +::: warning Note +If a lockup has been completed, the lockup status will show as "0" (or successful) and no further information will be available. To get further information on a completed lock, run the lock-by-id query. +::: +:::: + + +### total-locked-of-denom + +Query locked amount for a specific denom in the duration provided + +```sh +osmosisd query lockup total-locked-of-denom [denom] --min-duration +``` + +:::: details Example + +This example command outputs the amount of `gamm/pool/2` LP shares that locked in the `2 week` bonding period: + +```bash +osmosisd query lockup total-locked-of-denom gamm/pool/2 --min-duration "336h" +``` + +Which, at the time of this writing outputs `14106985399822075248947045` which is equivalent to `14106985.3998 gamm/pool/2` + +NOTE: As of this writing, there is a bug that defaults the min duration to days instead of seconds. Ensure you specify the time in seconds to get the correct response. +::: diff --git a/x/mint/spec/README.md b/x/mint/spec/README.md index f08b1ab65ec..4637aa61c22 100644 --- a/x/mint/spec/README.md +++ b/x/mint/spec/README.md @@ -1,19 +1,11 @@ -```html - -``` +# Mint -# `mint` +The ```mint``` module is responsible for creating tokens in a flexible way to reward +validators, incentivize providing pool liquidity, provide funds for Osmosis governance, and pay developers to maintain and improve Osmosis. -Mint module mint OSMO tokens at the end of epochs. On the other hand, -module allocate tokens to OSMO stakers, pool incentives, developer -rewards, and community pool. +The module is also responsible for reducing the token creation and distribution by a set amount and a set period of time until it reaches its maximum supply (see ```reduction_factor``` and ```reduction_period_in_epochs```) -Module uses time basis epochs supported by `epochs` module. +Module uses time basis epochs supported by ```epochs``` module. ## Contents @@ -22,3 +14,230 @@ Module uses time basis epochs supported by `epochs` module. 3. **[End Epoch](03_end_epoch.md)** 4. **[Parameters](04_params.md)** 5. **[Events](05_events.md)** + +## Overview + +### Network Parameters + +Below are all the network parameters for the ```mint``` module: + +- **```mint_denom```** - Token type being minted +- **```genesis_epoch_provisions```** - Amount of tokens generated at epoch to the distribution categories (see distribution_proportions) +- **```epoch_identifier```** - Type of epoch that triggers token issuance (day, week, etc.) +- **```reduction_period_in_epochs```** - How many epochs must occur before implementing the reduction factor +- **```reduction_factor```** - What the total token issuance factor will reduce by after reduction period passes (if set to 66.66%, token issuance will reduce by 1/3) +- **```distribution_proportions```** - Categories in which the specified proportion of newly released tokens are distributed to + - **```staking```** - Proportion of minted funds to incentivize staking OSMO + - **```pool_incentives```** - Proportion of minted funds to incentivize pools on Osmosis + - **```developer_rewards```** - Proportion of minted funds to pay developers for their past and future work + - **```community_pool```** - Proportion of minted funds to be set aside for the community pool +- **```weighted_developer_rewards_receivers```** - Addresses that developer rewards will go to. The weight attached to an address is the percent of the developer rewards that the specific address will receive +- **```minting_rewards_distribution_start_epoch```** - What epoch will start the rewards distribution to the aforementioned distribution categories + +
+
+ +## Queries + +### params + +Query all the current mint parameter values + +```sh +query mint params +``` + +::: details Example + +List all current min parameters in json format by: + +```bash +osmosisd query mint params -o json | jq +``` + +An example of the output: + +```json +{ + "mint_denom": "uosmo", + "genesis_epoch_provisions": "821917808219.178082191780821917", + "epoch_identifier": "day", + "reduction_period_in_epochs": "365", + "reduction_factor": "0.666666666666666666", + "distribution_proportions": { + "staking": "0.250000000000000000", + "pool_incentives": "0.450000000000000000", + "developer_rewards": "0.250000000000000000", + "community_pool": "0.050000000000000000" + }, + "weighted_developer_rewards_receivers": [ + { + "address": "osmo14kjcwdwcqsujkdt8n5qwpd8x8ty2rys5rjrdjj", + "weight": "0.288700000000000000" + }, + { + "address": "osmo1gw445ta0aqn26suz2rg3tkqfpxnq2hs224d7gq", + "weight": "0.229000000000000000" + }, + { + "address": "osmo13lt0hzc6u3htsk7z5rs6vuurmgg4hh2ecgxqkf", + "weight": "0.162500000000000000" + }, + { + "address": "osmo1kvc3he93ygc0us3ycslwlv2gdqry4ta73vk9hu", + "weight": "0.109000000000000000" + }, + { + "address": "osmo19qgldlsk7hdv3ddtwwpvzff30pxqe9phq9evxf", + "weight": "0.099500000000000000" + }, + { + "address": "osmo19fs55cx4594een7qr8tglrjtt5h9jrxg458htd", + "weight": "0.060000000000000000" + }, + { + "address": "osmo1ssp6px3fs3kwreles3ft6c07mfvj89a544yj9k", + "weight": "0.015000000000000000" + }, + { + "address": "osmo1c5yu8498yzqte9cmfv5zcgtl07lhpjrj0skqdx", + "weight": "0.010000000000000000" + }, + { + "address": "osmo1yhj3r9t9vw7qgeg22cehfzj7enwgklw5k5v7lj", + "weight": "0.007500000000000000" + }, + { + "address": "osmo18nzmtyn5vy5y45dmcdnta8askldyvehx66lqgm", + "weight": "0.007000000000000000" + }, + { + "address": "osmo1z2x9z58cg96ujvhvu6ga07yv9edq2mvkxpgwmc", + "weight": "0.005000000000000000" + }, + { + "address": "osmo1tvf3373skua8e6480eyy38avv8mw3hnt8jcxg9", + "weight": "0.002500000000000000" + }, + { + "address": "osmo1zs0txy03pv5crj2rvty8wemd3zhrka2ne8u05n", + "weight": "0.002500000000000000" + }, + { + "address": "osmo1djgf9p53n7m5a55hcn6gg0cm5mue4r5g3fadee", + "weight": "0.001000000000000000" + }, + { + "address": "osmo1488zldkrn8xcjh3z40v2mexq7d088qkna8ceze", + "weight": "0.000800000000000000" + } + ], + "minting_rewards_distribution_start_epoch": "1" +} +``` +::: + + +### epoch-provisions + +Query the current epoch provisions + +```sh +query mint epoch-provisions +``` + +::: details Example + +List the current epoch provisions: + +```bash +osmosisd query mint epoch-provisions +``` +As of this writing, this number will be equal to the ```genesis-epoch-provisions```. Once the ```reduction_period_in_epochs``` is reached, the ```reduction_factor``` will be initiated and reduce the amount of OSMO minted per epoch. +::: + +## Appendix + +### Current Configuration + +```mint``` **module: Network Parameter effects and current configuration** + +The following tables show overall effects on different configurations of the ```mint``` related network parameters: + + + + + + + + + + + + + + + + + + + + + + + + + +
mint_denomepoch_provisionsepoch_identifier
Typestringstring (dec)string
HigherN/AHigher inflation rateIncreases time to reduction_period
LowerN/ALower inflation rateDecreases time to reduction_period
ConstraintsN/AValue has to be a positive integerString must be day, week, month, or year
Current configurationuosmo821917808219.178 (821,9178 OSMO)day
+ + + + + + + + + + + + + + + + + + + + + + + + + +
reduction_period_in_epochsreduction_factorstaking
Typestringstring (dec)string (dec)
HigherLonger period of time until reduction_factor implementedReduces time until maximum supply is reachedMore epoch provisions go to staking rewards than other categories
LowerShorter period of time until reduction_factor implementedIncreases time until maximum supply is reachedLess epoch provisions go to staking rewards than other categories
ConstraintsValue has to be a whole number greater than or equal to 1Value has to be less or equal to 1Value has to be less or equal to 1 and all distribution categories combined must equal 1
Current configuration365 (epochs)0.666666666666666666 (66.66%)0.250000000000000000 (25%)
+ + + + + + + + + + + + + + + + + + + + + + + + + +
pool_incentivesdeveloper_rewardscommunity_pool
Typestring (dec)string (dec)string (dec)
HigherMore epoch provisions go to pool incentives than other categoriesMore epoch provisions go to developer rewards than other categoriesMore epoch provisions go to community pool than other categories
LowerLess epoch provisions go to pool incentives than other categoriesLess epoch provisions go to developer rewards than other categoriesLess epoch provisions go to community pool than other categories
ConstraintsValue has to be less or equal to 1 and all distribution categories combined must equal 1Value has to be less or equal to 1 and all distribution categories combined must equal 1Value has to be less or equal to 1 and all distribution categories combined must equal 1
Current configuration0.450000000000000000 (45%)0.250000000000000000 (25%)0.050000000000000000 (5%)
diff --git a/x/pool-incentives/spec/README.md b/x/pool-incentives/spec/README.md index 2281f90eb18..75a5ce0a5b0 100644 --- a/x/pool-incentives/spec/README.md +++ b/x/pool-incentives/spec/README.md @@ -1,15 +1,296 @@ -```html - -``` +# Pool Incentives -# `pool incentives` +The `pool-incentives` module is separate but related to the `incentives` module. When a pool is created using the `GAMM` module, the `pool-incentives` module automatically creates individual gauges in the `incentives` module for every lock duration that exists in that pool. The `pool-incentives` module also takes takes the `pool_incentives` distributed from the `gov` module and distributes it to the various incentivized gauges. ## Abstract +The `pool-incentives` module is separate but related to the `incentives` module. When a pool is created using the `GAMM` module, the `pool-incentives` module automatically creates individual gauges in the `incentives` module for every lock duration that exists in that pool. The `pool-incentives` module also takes takes the `pool_incentives` distributed from the `gov` module and distributes it to the various incentivized gauges. + +## Contents + +1. **[Concept](01_concepts.md)** +2. **[State](02_state.md)** +3. **[Governance](03_gov.md)** + +## Transactions + +### replace-pool-incentives + +```sh +osmosisd tx poolincentives replace-pool-incentives [gaugeIds] [weights] [flags] +``` + +::: details Example + +Fully replace records for pool incentives: + +```bash +osmosisd tx poolincentives replace-pool-incentives proposal.json --from --chain-id +``` + +The proposal.json would look as follows: + +```json +{ + "title": "Pool Incentive Adjustment", + "description": "Adjust pool incentives", + "records": [ + { + "gauge_id": "0", + "weight": "100000" + }, + { + "gauge_id": "1", + "weight": "1766249" + }, + { + "gauge_id": "XXX", + "weight": "XXXXXXXX" + }, + ... + ] +} +``` +::: + + + + +### update-pool-incentives + +Update the weight of specified pool gauges in regards to their share of incentives (by creating a proposal) + +```sh +osmosisd tx poolincentives update-pool-incentives [gaugeIds] [weights] [flags] --from --chain-id +``` + +::: details Example + +Update the pool incentives for `gauge_id` 0 and 1: + +```bash +osmosisd tx gov submit-proposal update-pool-incentives proposal.json --from WALLET_NAME --chain-id CHAIN_ID +``` + +The proposal.json would look as follows: + +```json +{ + "title": "Pool Incentive Adjustment", + "description": "Adjust pool incentives", + "records": [ + { + "gauge_id": "0", + "weight": "100000" + }, + { + "gauge_id": "1", + "weight": "1766249" + }, + ] +} +``` +::: + + +## Queries + +### distr-info + +Query distribution info for all pool gauges + +```sh +osmosisd query poolincentives distr-info +``` + +::: details Example + +```bash +osmosisd query poolincentives distr-info +``` + +An example output: + +``` + - gauge_id: "1877" + weight: "60707" + - gauge_id: "1878" + weight: "40471" + - gauge_id: "1897" + weight: "1448" + - gauge_id: "1898" + weight: "869" + - gauge_id: "1899" + weight: "579" +... +``` +::: + + +### external-incentivized-gauges + +Query externally incentivized gauges (gauges distributing rewards on top of the normal OSMO rewards) + +```sh +osmosisd query pool-incentives external-incentivized-gauges +``` + +::: details Example + +```bash +osmosisd query pool-incentives external-incentivized-gauges +``` + +An example output: + +``` +- coins: + - amount: "596400000" + denom: ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0 + distribute_to: + denom: gamm/pool/562 + duration: 604800s + lock_query_type: ByDuration + timestamp: "1970-01-01T00:00:00Z" + distributed_coins: + - amount: "596398318" + denom: ibc/0EF15DF2F02480ADE0BB6E85D9EBB5DAEA2836D3860E9F97F9AADE4F57A31AA0 + filled_epochs: "28" + id: "1791" + is_perpetual: false + num_epochs_paid_over: "28" + start_time: "1970-01-01T00:00:00Z" +- coins: + - amount: "1000000" + denom: ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED + distribute_to: + denom: gamm/pool/498 + duration: 86400s + lock_query_type: ByDuration + timestamp: "1970-01-01T00:00:00Z" + distributed_coins: + - amount: "999210" + denom: ibc/46B44899322F3CD854D2D46DEEF881958467CDD4B3B10086DA49296BBED94BED + filled_epochs: "2" + id: "1660" + is_perpetual: false + num_epochs_paid_over: "2" + start_time: "2021-10-14T16:00:00Z" +... +``` +::: + -The purpose of the `pool incentives` module is to distribute incentives -to the pool LPs within the `Gamm` module. + +### gauge-ids + +Query the gauge ids (by duration) by pool id + +```sh +osmosisd query poolincentives gauge-ids [pool-id] [flags] +``` + +::: details Example + +Find out what the gauge IDs are for pool 1: + +```bash +osmosisd query poolincentives gauge-ids 1 +``` + +An example output: + +``` +gauge_ids_with_duration: +- duration: 86400s + gauge_id: "1" +- duration: 604800s + gauge_id: "2" +- duration: 1209600s + gauge_id: "3" +``` + +In this example, we see that gauge IDs 1,2, and 3 are for the one day, one week, and two week lockup periods respectively for the OSMO/ATOM pool. +::: + + + +### incentivized-pools + +Query all incentivized pools with their respective gauge IDs and lockup durations + +```sh +osmosisd query poolincentives incentivized-pools [flags] +``` + +::: details Example + +```bash +osmosisd query poolincentives incentivized-pools +``` + +An example output: + +``` +- gauge_id: "1897" + lockable_duration: 86400s + pool_id: "602" +- gauge_id: "1898" + lockable_duration: 604800s + pool_id: "602" +- gauge_id: "1899" + lockable_duration: 1209600s + pool_id: "602" +... +``` +::: + + + + +### lockable-durations + +Query incentivized lockup durations + +```sh +osmosisd query poolincentives lockable-durations [flags] +``` + +::: details Example + +```bash +osmosisd query poolincentives lockable-durations +``` + +An example output: + +``` +lockable_durations: +- 86400s +- 604800s +- 1209600s +``` +::: + + + +### params + +Query pool-incentives module parameters + +```sh +osmosisd query poolincentives params [flags] +``` + +::: details Example + +```bash +osmosisd query poolincentives params +``` + +An example output: + +``` +params: + minted_denom: uosmo +``` +::: diff --git a/x/tokenfactory/README.md b/x/tokenfactory/spec/README.md similarity index 100% rename from x/tokenfactory/README.md rename to x/tokenfactory/spec/README.md diff --git a/x/txfees/spec/README.md b/x/txfees/spec/README.md new file mode 100644 index 00000000000..b88b4b30924 --- /dev/null +++ b/x/txfees/spec/README.md @@ -0,0 +1,49 @@ +# Txfees + +The txfees modules allows nodes to easily support many tokens for usage as txfees, while letting node operators only specify their tx fee parameters for a single "base" asset. +This is done by having this module maintain an allow-list of token denoms which can be used as tx fees, each with some associated metadata. +Then this metadata is used in tandem with a "Spot Price Calculator" provided to the module, to convert the provided tx fees into their equivalent value in the base denomination. +Currently the only supported metadata & spot price calculator is using a GAMM pool ID & the GAMM keeper. + +## State Changes + +* Adds a whitelist of tokens that can be used as fees on the chain. + * Any token not on this list cannot be provided as a tx fee. +* Adds a new SDK message for creating governance proposals for adding new TxFee denoms. + +## Local Mempool Filters Added + +* If you specify a min-tx-fee in the $BASEDENOM then + * Your node will allow any tx w/ tx fee in the whitelist of fees, and a sufficient osmo-equivalent price to enter your mempool + * The osmo-equivalent price for determining sufficiency is rechecked after every block. (During the mempools RecheckTx) + * TODO: further consider if we want to take this tradeoff. Allows someone who manipulates price for one block to flush txs using that asset as fee from most of the networks' mempools. + * The simple alternative is only check fee equivalency at a txs entry into the mempool, which allows someone to manipulate price down to have many txs enter the chain at low cost. + * Another alternative is to use TWAP instead of Spot Price once it is available on-chain + * The former concern isn't very worrisome as long as some nodes have 0 min tx fees. +* A separate min-gas-fee can be set on every node for arbitrage txs. Methods of detecting an arb tx atm + * does start token of a swap = final token of swap (definitionally correct) + * does it have multiple swap messages, with different tx ins. If so, we assume its an arb. + * This has false positives, but is intended to avoid the obvious solution of splitting an arb into multiple messages. + * We record all denoms seen across all swaps, and see if any duplicates. (TODO) + * Contains both JoinPool and ExitPool messages in one tx. + * Has some false positives. + * These false positives seem like they primarily will get hit during batching of many distinct operations, not really in one atomic action. +* A max wanted gas per any tx can be set to filter out attack txes. +* If tx wanted gas > than predefined threshold of 1M, then separate 'min-gas-price-for-high-gas-tx' option used to calculate min gas price. + +## Queries + +base-denom +- Query the base fee denom + +denom-pool-id +- Query the pool id associated with a specific whitelisted fee token + +fee-tokens +- Query the list of non-basedenom fee tokens and their associated pool ids + +## Future directions + +* Want to add in a system to add in general "tx fee credits" for different on-chain usages + * e.g. making 0 fee txs under certain usecases +* If other chains would like to use this, we should brainstorm mechanisms for extending the metadata proto fields From f5472e054709baba2490a8ea055da2fc00d1999a Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Sat, 21 May 2022 11:10:50 -0500 Subject: [PATCH 04/26] feat: local dev environment (#1554) * localosmosis * extract docker runs to script * change ubuntu to alpine * nicco suggestions * update changelog --- CHANGELOG.md | 1 + Dockerfile | 2 +- Makefile | 11 ++++++ tests/localosmosis/Dockerfile | 51 +++++++++++++++++++++++++++ tests/localosmosis/README.md | 38 ++++++++++++++++++++ tests/localosmosis/docker-compose.yml | 14 ++++++++ tests/localosmosis/keys.sh | 13 +++++++ tests/localosmosis/setup.sh | 46 ++++++++++++++++++++++++ 8 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 tests/localosmosis/Dockerfile create mode 100644 tests/localosmosis/README.md create mode 100644 tests/localosmosis/docker-compose.yml create mode 100755 tests/localosmosis/keys.sh create mode 100755 tests/localosmosis/setup.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b815fadd4c0..651c47d29cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +* [#1554](https://github.com/osmosis-labs/osmosis/pull/1554) local dev environment * [#1535](https://github.com/osmosis-labs/osmosis/pull/1535) upgrade wasmd to v0.27.0.rc3-osmo and ibc-go to v3 * [#1435] `x/tokenfactory` create denom fee for spam resistance * [#1429] solver for multi-asset CFMM diff --git a/Dockerfile b/Dockerfile index 8171358b5ac..b1d1331a642 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ COPY --from=build /osmosis/build/osmosisd /bin/osmosisd ENV HOME /osmosis WORKDIR $HOME -EXPOSE 26656 +EXPOSE 26656 EXPOSE 26657 EXPOSE 1317 diff --git a/Makefile b/Makefile index f5723691837..edbd3cc43c5 100644 --- a/Makefile +++ b/Makefile @@ -267,6 +267,17 @@ format: ### Localnet ### ############################################################################### +localnet-keys: + . tests/localosmosis/keys.sh + +localnet-build: + @docker build -t local:osmosis -f tests/localosmosis/Dockerfile . + +localnet-start: + @docker-compose -f tests/localosmosis/docker-compose.yml up + +localnet-remove: + @docker-compose -f tests/localosmosis/docker-compose.yml down .PHONY: all build-linux install format lint \ go-mod-cache draw-deps clean build build-contract-tests-hooks \ diff --git a/tests/localosmosis/Dockerfile b/tests/localosmosis/Dockerfile new file mode 100644 index 00000000000..85f681e5889 --- /dev/null +++ b/tests/localosmosis/Dockerfile @@ -0,0 +1,51 @@ +# syntax=docker/dockerfile:1 + +# -------------------------------------------------------- +# Build +# -------------------------------------------------------- + +FROM golang:1.18.2-alpine3.15 as build + +RUN set -eux; apk add --no-cache ca-certificates build-base; +RUN apk add git +# Needed by github.com/zondax/hid +RUN apk add linux-headers + +WORKDIR /osmosis +COPY . /osmosis + + +# CosmWasm: see https://github.com/CosmWasm/wasmvm/releases +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.0.0/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a +ADD https://github.com/CosmWasm/wasmvm/releases/download/v1.0.0/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a +RUN sha256sum /lib/libwasmvm_muslc.aarch64.a | grep 7d2239e9f25e96d0d4daba982ce92367aacf0cbd95d2facb8442268f2b1cc1fc +RUN sha256sum /lib/libwasmvm_muslc.x86_64.a | grep f6282df732a13dec836cda1f399dd874b1e3163504dbd9607c6af915b2740479 + +# CosmWasm: copy the right library according to architecture. The final location will be found by the linker flag `-lwasmvm_muslc` +RUN cp /lib/libwasmvm_muslc.$(uname -m).a /lib/libwasmvm_muslc.a + +RUN BUILD_TAGS=muslc LINK_STATICALLY=true make build + +# -------------------------------------------------------- +# Runner +# -------------------------------------------------------- + +FROM alpine + +COPY --from=build /osmosis/build/osmosisd /bin/osmosisd +COPY /tests/localosmosis/setup.sh /setup.sh + +ENV HOME /osmosis +WORKDIR $HOME +RUN apk update +RUN apk add jq +RUN apk add moreutils +RUN rm -rf /var/cache/apk/* +RUN chmod +x /setup.sh +RUN /setup.sh +EXPOSE 26656 +EXPOSE 26657 +EXPOSE 1317 + +ENTRYPOINT ["osmosisd"] +CMD ["start"] diff --git a/tests/localosmosis/README.md b/tests/localosmosis/README.md new file mode 100644 index 00000000000..4c99ab03184 --- /dev/null +++ b/tests/localosmosis/README.md @@ -0,0 +1,38 @@ +# LocalOsmosis + +You can now quickly test your changes to Osmosis with just a few commands: + +1. Make any change to the osmosis code that you want to test + +2. From the Osmosis home folder, run `make localnet-build` + - This compiles all your changes to docker image called local:osmosis (~60 seconds) + +3. Once complete, run `make localnet-start` + - You will now be running a local network with your changes! + +4. To add your validator wallet and 9 other preloaded wallets automatically, run `make localnet-keys` + - These keys are added to your --keyring-backend test + - If the keys are already on your keyring, you will get an "Error: aborted" + - Ensure you use the name of the account as listed in the table below, as well as ensure you append the `--keyring-backend test` to your txs + - Example: `osmosisd tx bank send lo-test2 osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks --keyring-backend test --chain-id localosmosis` + +5. To remove all block history and start from scratch, run `make localnet-remove` + +## Accounts + +LocalOsmosis is pre-configured with one validator and 9 accounts with ION and OSMO balances. + + +| Account | Address | Mnemonic | +| --------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| lo-val | `osmo1phaxpevm5wecex2jyaqty2a4v02qj7qmlmzk5a`
`osmovaloper1phaxpevm5wecex2jyaqty2a4v02qj7qm9v24r6` | `satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn` | +| lo-test1 | `osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks` | `notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius` | +| lo-test2 | `osmo18s5lynnmx37hq4wlrw9gdn68sg2uxp5rgk26vv` | `quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty` | +| lo-test3 | `osmo1qwexv7c6sm95lwhzn9027vyu2ccneaqad4w8ka` | `symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb` | +| lo-test4 | `osmo14hcxlnwlqtq75ttaxf674vk6mafspg8xwgnn53` | `bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty` | +| lo-test5 | `osmo12rr534cer5c0vj53eq4y32lcwguyy7nndt0u2t` | `second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal` | +| lo-test6 | `osmo1nt33cjd5auzh36syym6azgc8tve0jlvklnq7jq` | `spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick` | +| lo-test7 | `osmo10qfrpash5g2vk3hppvu45x0g860czur8ff5yx0` | `noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate` | +| lo-test8 | `osmo1f4tvsdukfwh6s9swrc24gkuz23tp8pd3e9r5fa` | `cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride` | +| lo-test9 | `osmo1myv43sqgnj5sm4zl98ftl45af9cfzk7nhjxjqh` | `index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred` | +| lo-test10 | `osmo14gs9zqh8m49yy9kscjqu9h72exyf295afg6kgk` | `prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder` | diff --git a/tests/localosmosis/docker-compose.yml b/tests/localosmosis/docker-compose.yml new file mode 100644 index 00000000000..a52d6c5a56b --- /dev/null +++ b/tests/localosmosis/docker-compose.yml @@ -0,0 +1,14 @@ +version: "3" + +services: + osmosisd: + image: local:osmosis + user: "root:root" + command: + - start + - --rpc.laddr=tcp://0.0.0.0:26657 + ports: + - "26657:26657" + - "1317:1317" + - "9090:9090" + - "9091:9091" diff --git a/tests/localosmosis/keys.sh b/tests/localosmosis/keys.sh new file mode 100755 index 00000000000..084337ee464 --- /dev/null +++ b/tests/localosmosis/keys.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn" | osmosisd keys add lo-val --recover --keyring-backend test +echo "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius" | osmosisd keys add lo-test1 --recover --keyring-backend test +echo "quality vacuum heart guard buzz spike sight swarm shove special gym robust assume sudden deposit grid alcohol choice devote leader tilt noodle tide penalty" | osmosisd keys add lo-test2 --recover --keyring-backend test +echo "symbol force gallery make bulk round subway violin worry mixture penalty kingdom boring survey tool fringe patrol sausage hard admit remember broken alien absorb" | osmosisd keys add lo-test3 --recover --keyring-backend test +echo "bounce success option birth apple portion aunt rural episode solution hockey pencil lend session cause hedgehog slender journey system canvas decorate razor catch empty" | osmosisd keys add lo-test4 --recover --keyring-backend test +echo "second render cat sing soup reward cluster island bench diet lumber grocery repeat balcony perfect diesel stumble piano distance caught occur example ozone loyal" | osmosisd keys add lo-test5 --recover --keyring-backend test +echo "spatial forest elevator battle also spoon fun skirt flight initial nasty transfer glory palm drama gossip remove fan joke shove label dune debate quick" | osmosisd keys add lo-test6 --recover --keyring-backend test +echo "noble width taxi input there patrol clown public spell aunt wish punch moment will misery eight excess arena pen turtle minimum grain vague inmate" | osmosisd keys add lo-test7 --recover --keyring-backend test +echo "cream sport mango believe inhale text fish rely elegant below earth april wall rug ritual blossom cherry detail length blind digital proof identify ride" | osmosisd keys add lo-test8 --recover --keyring-backend test +echo "index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred" | osmosisd keys add lo-test9 --recover --keyring-backend test +echo "prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder" | osmosisd keys add lo-test10 --recover --keyring-backend test diff --git a/tests/localosmosis/setup.sh b/tests/localosmosis/setup.sh new file mode 100755 index 00000000000..cc2cf878026 --- /dev/null +++ b/tests/localosmosis/setup.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +# change staking denom to uosmo +osmosisd init --chain-id=localosmosis val +echo "satisfy adjust timber high purchase tuition stool faith fine install that you unaware feed domain license impose boss human eager hat rent enjoy dawn" | osmosisd keys add val --recover --keyring-backend=test +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["staking"]["params"]["bond_denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +osmosisd add-genesis-account osmo1phaxpevm5wecex2jyaqty2a4v02qj7qmlmzk5a 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo1cyyzpxplxdzkeea7kwsydadg87357qnahakaks 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo18s5lynnmx37hq4wlrw9gdn68sg2uxp5rgk26vv 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo1qwexv7c6sm95lwhzn9027vyu2ccneaqad4w8ka 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo14hcxlnwlqtq75ttaxf674vk6mafspg8xwgnn53 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo12rr534cer5c0vj53eq4y32lcwguyy7nndt0u2t 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo1nt33cjd5auzh36syym6azgc8tve0jlvklnq7jq 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo10qfrpash5g2vk3hppvu45x0g860czur8ff5yx0 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo1f4tvsdukfwh6s9swrc24gkuz23tp8pd3e9r5fa 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo1myv43sqgnj5sm4zl98ftl45af9cfzk7nhjxjqh 100000000000uosmo,100000000000uion +osmosisd add-genesis-account osmo14gs9zqh8m49yy9kscjqu9h72exyf295afg6kgk 100000000000uosmo,100000000000uion +osmosisd gentx val 500000000uosmo --keyring-backend=test --chain-id=localosmosis +osmosisd collect-gentxs +# update staking genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["staking"]["params"]["unbonding_time"]="240s"' | sponge $HOME/.osmosisd/config/genesis.json +# update crisis variable to uosmo +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["crisis"]["constant_fee"]["denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +# udpate gov genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["gov"]["voting_params"]["voting_period"]="60s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +# update epochs genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["epochs"]["epochs"][1]["duration"]="60s"' | sponge $HOME/.osmosisd/config/genesis.json +# update poolincentives genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["poolincentives"]["lockable_durations"][0]="120s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["poolincentives"]["lockable_durations"][1]="180s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["poolincentives"]["lockable_durations"][2]="240s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["poolincentives"]["params"]["minted_denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +# update incentives genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["incentives"]["lockable_durations"][0]="1s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["incentives"]["lockable_durations"][1]="120s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["incentives"]["lockable_durations"][2]="180s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["incentives"]["lockable_durations"][3]="240s"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["incentives"]["params"]["distr_epoch_identifier"]="day"' | sponge $HOME/.osmosisd/config/genesis.json +# update mint genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["mint"]["params"]["mint_denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["mint"]["params"]["epoch_identifier"]="day"' | sponge $HOME/.osmosisd/config/genesis.json +# update gamm genesis +cat $HOME/.osmosisd/config/genesis.json | jq '.app_state["gamm"]["params"]["pool_creation_fee"][0]["denom"]="uosmo"' | sponge $HOME/.osmosisd/config/genesis.json +# remove seeds +sed -i.bak -E 's#^(seeds[[:space:]]+=[[:space:]]+).*$#\1""#' ~/.osmosisd/config/config.toml From 8aaa84bda305baf7cacac59d7853c1c0bc7da76a Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 21 May 2022 13:15:18 -0700 Subject: [PATCH 05/26] fix: gov support for superfluid (#1555) Closes: #XXX ## What is the purpose of the change @czarcas7ic and I ran into issues manually testing #1191 . We both reproduces this test case: https://github.com/osmosis-labs/osmosis/discussions/1543#discussioncomment-2786650 Upon further investigation, it was found that staking keeper was never replaced with superfluid keeper in the gov module so that the new logic could not be used for calculating tally and overriding validator votes by SF stakers. We need to e2e test this case in a future PR: https://github.com/osmosis-labs/osmosis/issues/1556 ## Testing and Verifying This change is a trivial rework / code cleanup without any test coverage. ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? not applicable --- app/keepers/keepers.go | 2 +- x/superfluid/keeper/keeper.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 7298d7175e9..19af2725c00 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -366,7 +366,7 @@ func (appKeepers *AppKeepers) InitNormalKeepers( govKeeper := govkeeper.NewKeeper( appCodec, appKeepers.keys[govtypes.StoreKey], appKeepers.GetSubspace(govtypes.ModuleName), appKeepers.AccountKeeper, appKeepers.BankKeeper, - appKeepers.StakingKeeper, govRouter) + appKeepers.SuperfluidKeeper, govRouter) appKeepers.GovKeeper = &govKeeper } diff --git a/x/superfluid/keeper/keeper.go b/x/superfluid/keeper/keeper.go index 854e51b1f55..a91ca541fad 100644 --- a/x/superfluid/keeper/keeper.go +++ b/x/superfluid/keeper/keeper.go @@ -9,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) @@ -30,6 +31,8 @@ type Keeper struct { lms types.LockupMsgServer } +var _ govtypes.StakingKeeper = (*Keeper)(nil) + // NewKeeper returns an instance of Keeper. func NewKeeper(cdc codec.Codec, storeKey sdk.StoreKey, paramSpace paramtypes.Subspace, ak authkeeper.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, dk types.DistrKeeper, ek types.EpochKeeper, lk types.LockupKeeper, gk types.GammKeeper, ik types.IncentivesKeeper, lms types.LockupMsgServer) *Keeper { // set KeyTable if it has not already been set From fc2c1050f822e4e68bfe234900fe1dc75266fe9f Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 24 May 2022 08:27:36 -0700 Subject: [PATCH 06/26] refactor: e2e setup to be more extensible for state sync (#1565) Closes: #XXX ## What is the purpose of the change This PR refactors e2e test setup to have an abstraction `chainConfig` that has information about which validators should not be run during initialization. This is a first step toward testing state-sync as we want to postpone running some nodes so that we can test nodes "catching up". Additionally, this PR extracts a separate package `docker` for managing and storing all information related to docker images. ## Brief Changelog - create a `chainConfig` struct to encapsulate all configurations related to chains in a single abstraction * remove global variables `propHeightA`, `propHeightB`, `votingPeriodA`, `votingPeriodB`. Instead, these are part of the `chainConfig` struct * `skipRunValidatorIndexes` - this is needed to skip certain validators from being run during setup so that we can test state-sync post-initialization and upgrade - remove code duplication for every chain. Instead, always loop over `chainConfig`s so that if we add more chains later, the setup would work out of the box ## Testing and Verifying - tested running both with and without upgrade ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? not applicable --- tests/e2e/docker/image_config.go | 64 ++++++ tests/e2e/e2e_setup_test.go | 333 +++++++++++++++++-------------- tests/e2e/e2e_test.go | 11 +- tests/e2e/e2e_util_test.go | 26 ++- 4 files changed, 266 insertions(+), 168 deletions(-) create mode 100644 tests/e2e/docker/image_config.go diff --git a/tests/e2e/docker/image_config.go b/tests/e2e/docker/image_config.go new file mode 100644 index 00000000000..db072e5e694 --- /dev/null +++ b/tests/e2e/docker/image_config.go @@ -0,0 +1,64 @@ +package docker + +// ImageConfig contains all images and their respective tags +// needed for running e2e tests. +type ImageConfig struct { + InitRepository string + InitTag string + + OsmosisRepository string + OsmosisTag string + + RelayerRepository string + RelayerTag string +} + +const ( + // Local osmosis repo/version. + // It is used when skipping upgrade by setting OSMOSIS_E2E_SKIP_UPGRADE to true). + // This image should be pre-built with `make docker-build-debug` either in CI or locally. + LocalOsmoRepository = "osmosis" + LocalOsmoTag = "debug" + // Local osmosis repo/version for osmosis initialization. + // It is used when skipping upgrade by setting OSMOSIS_E2E_SKIP_UPGRADE to true). + // This image should be pre-built with `make docker-build-e2e-chain-init` either in CI or locally. + localInitRepository = "osmosis-e2e-chain-init" + localInitTag = "debug" + // Pre-upgrade osmosis repo/tag to pull. + // It should be uploaded to Docker Hub. OSMOSIS_E2E_SKIP_UPGRADE should be unset + // for this functionality to be used. + previousVersionOsmoRepository = "osmolabs/osmosis-dev" + previousVersionOsmoTag = "v8.0.0-debug" + // Pre-upgrade repo/tag for osmosis initialization (this should be one version below upgradeVersion) + previousVersionInitRepository = "osmolabs/osmosis-init" + previousVersionInitTag = "v8.0.0" + // Hermes repo/version for relayer + relayerRepository = "osmolabs/hermes" + relayerTag = "0.13.0" +) + +// Returns ImageConfig needed for running e2e test. +// If isUpgrade is true, returns images for running the upgrade +// Otherwise, returns images for running non-upgrade e2e tests. +func NewImageConfig(isUpgrade bool) *ImageConfig { + config := &ImageConfig{ + RelayerRepository: relayerRepository, + RelayerTag: relayerTag, + } + + if isUpgrade { + config.InitRepository = previousVersionInitRepository + config.InitTag = previousVersionInitTag + + config.OsmosisRepository = previousVersionOsmoRepository + config.OsmosisTag = previousVersionOsmoTag + } else { + config.InitRepository = localInitRepository + config.InitTag = localInitTag + + config.OsmosisRepository = LocalOsmoRepository + config.OsmosisTag = LocalOsmoTag + } + + return config +} diff --git a/tests/e2e/e2e_setup_test.go b/tests/e2e/e2e_setup_test.go index 399bfdc3e32..d5fe898766e 100644 --- a/tests/e2e/e2e_setup_test.go +++ b/tests/e2e/e2e_setup_test.go @@ -21,25 +21,33 @@ import ( rpchttp "github.com/tendermint/tendermint/rpc/client/http" "github.com/osmosis-labs/osmosis/v7/tests/e2e/chain" + dockerconfig "github.com/osmosis-labs/osmosis/v7/tests/e2e/docker" "github.com/osmosis-labs/osmosis/v7/tests/e2e/util" ) -var ( +type status struct { + LatestHeight string `json:"latest_block_height"` +} + +type syncInfo struct { + SyncInfo status `json:"SyncInfo"` +} + +type chainConfig struct { + // voting period is number of blocks it takes to deposit, 1.2 seconds per validator to vote on the prop, and a buffer. + votingPeriod float32 + // upgrade proposal height for chain. + propHeight int + // Indexes of the validators to skip from running during initialization. + // This is needed for testing functionality like state-sync where we would + // like to start a node during tests post-initialization. + skipRunValidatorIndexes map[int]struct{} + chain *chain.Chain +} + +const ( // osmosis version being upgraded to (folder must exist here https://github.com/osmosis-labs/osmosis/tree/main/app/upgrades) upgradeVersion = "v9" - // osmosis repo/version for initialization (this should be one version below upgradeVersion) - initRepository = "osmolabs/osmosis-init" - initVersion = "v8.0.0" - // pre upgrade osmosis repo/version to pull (should match initVersion numer) - debugRepository = "osmolabs/osmosis-dev" - debugVersion = "v8.0.0-debug" - // hermes repo/version for relayer - relayerRepository = "osmolabs/hermes" - relayerVersion = "0.13.0" - // voting period for chain A - votingPeriodA float32 - // voting period for chain B - votingPeriodB float32 // estimated number of blocks it takes to submit for a proposal propSubmitBlocks float32 = 10 // estimated number of blocks it takes to deposit for a proposal @@ -48,14 +56,11 @@ var ( propVoteBlocks float32 = 1.2 // number of blocks used as a calculation buffer propBufferBlocks float32 = 5 - // variable used to switch between chain A and B prop height in for loop - propHeight int - // upgrade proposal height for chain A - propHeightA int - // upgrade proposal height for chain B - propHeightB int // max retries for json unmarshalling maxRetries = 60 +) + +var ( // whatever number of validator configs get posted here are how many validators that will spawn on chain A and B respectively validatorConfigsChainA = []*chain.ValidatorConfig{ { @@ -79,6 +84,13 @@ var ( SnapshotInterval: 1500, SnapshotKeepRecent: 2, }, + { + Pruning: "everything", + PruningKeepRecent: "0", + PruningInterval: "0", + SnapshotInterval: 0, + SnapshotKeepRecent: 0, + }, } validatorConfigsChainB = []*chain.ValidatorConfig{ { @@ -109,20 +121,13 @@ type IntegrationTestSuite struct { suite.Suite tmpDirs []string - chains []*chain.Chain + chainConfigs []*chainConfig dkrPool *dockertest.Pool dkrNet *dockertest.Network hermesResource *dockertest.Resource initResource *dockertest.Resource valResources map[string][]*dockertest.Resource -} - -type status struct { - LatestHeight string `json:"latest_block_height"` -} - -type syncInfo struct { - SyncInfo status `json:"SyncInfo"` + dockerImages dockerconfig.ImageConfig } func TestIntegrationTestSuite(t *testing.T) { @@ -132,7 +137,7 @@ func TestIntegrationTestSuite(t *testing.T) { func (s *IntegrationTestSuite) SetupSuite() { s.T().Log("setting up e2e integration test suite...") - s.chains = make([]*chain.Chain, 0, 2) + s.chainConfigs = make([]*chainConfig, 0, 2) // The e2e test flow is as follows: // @@ -142,37 +147,40 @@ func (s *IntegrationTestSuite) SetupSuite() { // 2. Start both networks. // 3. Run IBC relayer betweeen the two chains. // 4. Execute various e2e tests, including IBC. + var ( + skipUpgrade bool + err error + ) + if str := os.Getenv("OSMOSIS_E2E_SKIP_UPGRADE"); len(str) > 0 { - skipUpgrade, err := strconv.ParseBool(str) + skipUpgrade, err = strconv.ParseBool(str) s.Require().NoError(err) + } - if skipUpgrade { - debugRepository = "osmosis" - debugVersion = "debug" - s.configureDockerResources(chain.ChainAID, chain.ChainBID) - s.configureChain(chain.ChainAID, validatorConfigsChainA) - s.configureChain(chain.ChainBID, validatorConfigsChainB) - - s.runValidators(s.chains[0], 0) - s.runValidators(s.chains[1], 10) - s.runIBCRelayer() - s.runPostUpgradeTests() - return + s.dockerImages = *dockerconfig.NewImageConfig(!skipUpgrade) + + s.configureDockerResources(chain.ChainAID, chain.ChainBID) + s.configureChain(chain.ChainAID, validatorConfigsChainA, map[int]struct{}{ + 3: {}, // skip validator at index 3 + }) + s.configureChain(chain.ChainBID, validatorConfigsChainB, map[int]struct{}{}) + + for i, chainConfig := range s.chainConfigs { + s.runValidators(chainConfig, s.dockerImages.OsmosisRepository, s.dockerImages.OsmosisTag, i*10) + } + + // Run a relayer between every possible pair of chains. + for i := 0; i < len(s.chainConfigs); i++ { + for j := i + 1; j < len(s.chainConfigs); j++ { + s.runIBCRelayer(s.chainConfigs[i].chain, s.chainConfigs[j].chain) } } - s.configureDockerResources(chain.ChainAID, chain.ChainBID) - s.configureChain(chain.ChainAID, validatorConfigsChainA) - s.configureChain(chain.ChainBID, validatorConfigsChainB) - - s.runValidators(s.chains[0], 0) - s.runValidators(s.chains[1], 10) - s.runIBCRelayer() - // pre upgrade state creation - s.createPreUpgradeState() - // initialize and run the upgrade - s.upgrade() - // post upgrade tests - s.runPostUpgradeTests() + + if !skipUpgrade { + s.createPreUpgradeState() + s.upgrade() + s.runPostUpgradeTests() + } } func (s *IntegrationTestSuite) TearDownSuite() { @@ -197,8 +205,8 @@ func (s *IntegrationTestSuite) TearDownSuite() { s.Require().NoError(s.dkrPool.RemoveNetwork(s.dkrNet)) - for _, chain := range s.chains { - os.RemoveAll(chain.ChainMeta.DataDir) + for _, chainConfig := range s.chainConfigs { + os.RemoveAll(chainConfig.chain.ChainMeta.DataDir) } for _, td := range s.tmpDirs { @@ -206,12 +214,21 @@ func (s *IntegrationTestSuite) TearDownSuite() { } } -func (s *IntegrationTestSuite) runValidators(c *chain.Chain, portOffset int) { - s.T().Logf("starting %s validator containers...", c.ChainMeta.Id) - s.valResources[c.ChainMeta.Id] = make([]*dockertest.Resource, len(c.Validators)) +func (s *IntegrationTestSuite) runValidators(chainConfig *chainConfig, dockerRepository, dockerTag string, portOffset int) { + chain := chainConfig.chain + s.T().Logf("starting %s validator containers...", chain.ChainMeta.Id) + s.valResources[chain.ChainMeta.Id] = make([]*dockertest.Resource, len(chain.Validators)-len(chainConfig.skipRunValidatorIndexes)) pwd, err := os.Getwd() s.Require().NoError(err) - for i, val := range c.Validators { + for i, val := range chain.Validators { + // Skip some validators from running during set up. + // This is needed for testing functionality like + // state-sunc where we might want to start some validators during tests. + if _, ok := chainConfig.skipRunValidatorIndexes[i]; ok { + s.T().Logf("skipping %s validator with index %d from running...", val.Name, i) + continue + } + runOpts := &dockertest.RunOptions{ Name: val.Name, NetworkID: s.dkrNet.Network.ID, @@ -219,8 +236,8 @@ func (s *IntegrationTestSuite) runValidators(c *chain.Chain, portOffset int) { fmt.Sprintf("%s/:/osmosis/.osmosisd", val.ConfigDir), fmt.Sprintf("%s/scripts:/osmosis", pwd), }, - Repository: debugRepository, - Tag: debugVersion, + Repository: dockerRepository, + Tag: dockerTag, Cmd: []string{ "start", }, @@ -245,7 +262,7 @@ func (s *IntegrationTestSuite) runValidators(c *chain.Chain, portOffset int) { resource, err := s.dkrPool.RunWithOptions(runOpts, noRestart) s.Require().NoError(err) - s.valResources[c.ChainMeta.Id][i] = resource + s.valResources[chain.ChainMeta.Id][i] = resource s.T().Logf("started %s validator container: %s", resource.Container.Name[1:], resource.Container.ID) } @@ -275,15 +292,15 @@ func (s *IntegrationTestSuite) runValidators(c *chain.Chain, portOffset int) { ) } -func (s *IntegrationTestSuite) runIBCRelayer() { +func (s *IntegrationTestSuite) runIBCRelayer(chainA *chain.Chain, chainB *chain.Chain) { s.T().Log("starting Hermes relayer container...") tmpDir, err := ioutil.TempDir("", "osmosis-e2e-testnet-hermes-") s.Require().NoError(err) s.tmpDirs = append(s.tmpDirs, tmpDir) - osmoAVal := s.chains[0].Validators[0] - osmoBVal := s.chains[1].Validators[0] + osmoAVal := chainA.Validators[0] + osmoBVal := chainB.Validators[0] hermesCfgPath := path.Join(tmpDir, "hermes") s.Require().NoError(os.MkdirAll(hermesCfgPath, 0o755)) @@ -295,9 +312,9 @@ func (s *IntegrationTestSuite) runIBCRelayer() { s.hermesResource, err = s.dkrPool.RunWithOptions( &dockertest.RunOptions{ - Name: fmt.Sprintf("%s-%s-relayer", s.chains[0].ChainMeta.Id, s.chains[1].ChainMeta.Id), - Repository: relayerRepository, - Tag: relayerVersion, + Name: fmt.Sprintf("%s-%s-relayer", chainA.ChainMeta.Id, chainB.ChainMeta.Id), + Repository: s.dockerImages.RelayerRepository, + Tag: s.dockerImages.RelayerTag, NetworkID: s.dkrNet.Network.ID, Cmd: []string{ "start", @@ -313,12 +330,12 @@ func (s *IntegrationTestSuite) runIBCRelayer() { "3031/tcp": {{HostIP: "", HostPort: "3031"}}, }, Env: []string{ - fmt.Sprintf("OSMO_A_E2E_CHAIN_ID=%s", s.chains[0].ChainMeta.Id), - fmt.Sprintf("OSMO_B_E2E_CHAIN_ID=%s", s.chains[1].ChainMeta.Id), + fmt.Sprintf("OSMO_A_E2E_CHAIN_ID=%s", chainA.ChainMeta.Id), + fmt.Sprintf("OSMO_B_E2E_CHAIN_ID=%s", chainB.ChainMeta.Id), fmt.Sprintf("OSMO_A_E2E_VAL_MNEMONIC=%s", osmoAVal.Mnemonic), fmt.Sprintf("OSMO_B_E2E_VAL_MNEMONIC=%s", osmoBVal.Mnemonic), - fmt.Sprintf("OSMO_A_E2E_VAL_HOST=%s", s.valResources[s.chains[0].ChainMeta.Id][0].Container.Name[1:]), - fmt.Sprintf("OSMO_B_E2E_VAL_HOST=%s", s.valResources[s.chains[1].ChainMeta.Id][0].Container.Name[1:]), + fmt.Sprintf("OSMO_A_E2E_VAL_HOST=%s", s.valResources[chainA.ChainMeta.Id][0].Container.Name[1:]), + fmt.Sprintf("OSMO_B_E2E_VAL_HOST=%s", s.valResources[chainB.ChainMeta.Id][0].Container.Name[1:]), }, Entrypoint: []string{ "sh", @@ -367,41 +384,39 @@ func (s *IntegrationTestSuite) runIBCRelayer() { time.Sleep(10 * time.Second) // create the client, connection and channel between the two Osmosis chains - s.connectIBCChains() + s.connectIBCChains(chainA, chainB) } -func (s *IntegrationTestSuite) configureChain(chainId string, validatorConfigs []*chain.ValidatorConfig) { - +func (s *IntegrationTestSuite) configureChain(chainId string, validatorConfigs []*chain.ValidatorConfig, skipValidatorIndexes map[int]struct{}) { s.T().Logf("starting e2e infrastructure for chain-id: %s", chainId) tmpDir, err := ioutil.TempDir("", "osmosis-e2e-testnet-") s.T().Logf("temp directory for chain-id %v: %v", chainId, tmpDir) s.Require().NoError(err) - b, err := json.Marshal(validatorConfigs) + validatorConfigBytes, err := json.Marshal(validatorConfigs) s.Require().NoError(err) numVal := float32(len(validatorConfigs)) - // voting period is number of blocks it takes to deposit, 1.2 seconds per validator to vote on the prop, then a buffer - votingPeriodNum := propDepositBlocks + numVal*propVoteBlocks + propBufferBlocks - if chainId == chain.ChainAID { - votingPeriodA = votingPeriodNum - } else if chainId == chain.ChainBID { - votingPeriodB = votingPeriodNum + + newChainConfig := chainConfig{ + votingPeriod: propDepositBlocks + numVal*propVoteBlocks + propBufferBlocks, + skipRunValidatorIndexes: skipValidatorIndexes, } - votingPeriod := time.Duration(int(votingPeriodNum) * 1000000000) + + votingPeriodDuration := time.Duration(int(newChainConfig.votingPeriod) * 1000000000) s.initResource, err = s.dkrPool.RunWithOptions( &dockertest.RunOptions{ Name: fmt.Sprintf("%s", chainId), - Repository: initRepository, - Tag: initVersion, + Repository: s.dockerImages.InitRepository, + Tag: s.dockerImages.InitTag, NetworkID: s.dkrNet.Network.ID, Cmd: []string{ fmt.Sprintf("--data-dir=%s", tmpDir), fmt.Sprintf("--chain-id=%s", chainId), - fmt.Sprintf("--config=%s", b), - fmt.Sprintf("--voting-period=%v", votingPeriod), + fmt.Sprintf("--config=%s", validatorConfigBytes), + fmt.Sprintf("--voting-period=%v", votingPeriodDuration), }, User: "root:root", Mounts: []string{ @@ -412,8 +427,6 @@ func (s *IntegrationTestSuite) configureChain(chainId string, validatorConfigs [ ) s.Require().NoError(err) - var newChain chain.Chain - fileName := fmt.Sprintf("%v/%v-encode", tmpDir, chainId) s.T().Logf("serialized init file for chain-id %v: %v", chainId, fileName) @@ -421,7 +434,7 @@ func (s *IntegrationTestSuite) configureChain(chainId string, validatorConfigs [ // without this, test attempts to unmarshal file before docker container is finished writing for i := 0; i < maxRetries; i++ { encJson, _ := os.ReadFile(fileName) - err = json.Unmarshal(encJson, &newChain) + err = json.Unmarshal(encJson, &newChainConfig.chain) if err == nil { break } @@ -434,8 +447,9 @@ func (s *IntegrationTestSuite) configureChain(chainId string, validatorConfigs [ time.Sleep(1 * time.Second) } } - s.chains = append(s.chains, &newChain) s.Require().NoError(s.dkrPool.Purge(s.initResource)) + + s.chainConfigs = append(s.chainConfigs, &newChainConfig) } func (s *IntegrationTestSuite) configureDockerResources(chainIDOne, chainIDTwo string) { @@ -459,39 +473,36 @@ func noRestart(config *docker.HostConfig) { func (s *IntegrationTestSuite) upgrade() { // submit, deposit, and vote for upgrade proposal // prop height = current height + voting period + time it takes to submit proposal + small buffer - currentHeightA := s.getCurrentChainHeight(s.valResources[s.chains[0].ChainMeta.Id][0].Container.ID) - propHeightA = currentHeightA + int(votingPeriodA) + int(propSubmitBlocks) + int(propBufferBlocks) - s.submitProposal(s.chains[0], propHeightA) - s.depositProposal(s.chains[0]) - s.voteProposal(s.chains[0]) - // prop height = current height + voting period + time it takes to submit proposal + small buffer - currentHeightB := s.getCurrentChainHeight(s.valResources[s.chains[1].ChainMeta.Id][0].Container.ID) - propHeightB = currentHeightB + int(votingPeriodB) + int(propSubmitBlocks) + int(propBufferBlocks) - s.submitProposal(s.chains[1], propHeightB) - s.depositProposal(s.chains[1]) - s.voteProposal(s.chains[1]) + for _, chainConfig := range s.chainConfigs { + currentHeight := s.getCurrentChainHeight(s.valResources[chainConfig.chain.ChainMeta.Id][0].Container.ID) + chainConfig.propHeight = currentHeight + int(chainConfig.votingPeriod) + int(propSubmitBlocks) + int(propBufferBlocks) + s.submitProposal(chainConfig.chain, chainConfig.propHeight) + s.depositProposal(chainConfig.chain) + s.voteProposal(chainConfig) + } // wait till all chains halt at upgrade height - for _, c := range s.chains { - if c.ChainMeta.Id == chain.ChainAID { - propHeight = propHeightA - } else { - propHeight = propHeightB - } - for i := range c.Validators { + for _, chainConfig := range s.chainConfigs { + curChain := chainConfig.chain + + for i := range chainConfig.chain.Validators { + if _, ok := chainConfig.skipRunValidatorIndexes[i]; ok { + continue + } + // use counter to ensure no new blocks are being created counter := 0 - s.T().Logf("waiting to reach upgrade height on %s validator container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], s.valResources[c.ChainMeta.Id][i].Container.ID) + s.T().Logf("waiting to reach upgrade height on %s validator container: %s", s.valResources[curChain.ChainMeta.Id][i].Container.Name[1:], s.valResources[curChain.ChainMeta.Id][i].Container.ID) s.Require().Eventually( func() bool { - currentHeight := s.getCurrentChainHeight(s.valResources[c.ChainMeta.Id][i].Container.ID) - if currentHeight != propHeight { - s.T().Logf("current block height on %s is %v, waiting for block %v container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], currentHeight, propHeight, s.valResources[c.ChainMeta.Id][i].Container.ID) + currentHeight := s.getCurrentChainHeight(s.valResources[curChain.ChainMeta.Id][i].Container.ID) + if currentHeight != chainConfig.propHeight { + s.T().Logf("current block height on %s is %v, waiting for block %v container: %s", s.valResources[curChain.ChainMeta.Id][i].Container.Name[1:], currentHeight, chainConfig.propHeight, s.valResources[curChain.ChainMeta.Id][i].Container.ID) } - if currentHeight > propHeight { + if currentHeight > chainConfig.propHeight { panic("chain did not halt at upgrade height") } - if currentHeight == propHeight { + if currentHeight == chainConfig.propHeight { counter++ } return counter == 3 @@ -499,34 +510,48 @@ func (s *IntegrationTestSuite) upgrade() { 5*time.Minute, time.Second, ) - s.T().Logf("reached upgrade height on %s container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], s.valResources[c.ChainMeta.Id][i].Container.ID) + s.T().Logf("reached upgrade height on %s container: %s", s.valResources[curChain.ChainMeta.Id][i].Container.Name[1:], s.valResources[curChain.ChainMeta.Id][i].Container.ID) } } // remove all containers so we can upgrade them to the new version - for _, chain := range s.chains { - for i := range chain.Validators { + for _, chainConfig := range s.chainConfigs { + curChain := chainConfig.chain + for valIdx := range curChain.Validators { + if _, ok := chainConfig.skipRunValidatorIndexes[valIdx]; ok { + continue + } + var opts docker.RemoveContainerOptions - opts.ID = s.valResources[chain.ChainMeta.Id][i].Container.ID + opts.ID = s.valResources[curChain.ChainMeta.Id][valIdx].Container.ID opts.Force = true s.dkrPool.Client.RemoveContainer(opts) - s.T().Logf("removed container: %s", s.valResources[chain.ChainMeta.Id][i].Container.Name[1:]) + s.T().Logf("removed container: %s", s.valResources[curChain.ChainMeta.Id][valIdx].Container.Name[1:]) } } - s.upgradeContainers(s.chains[0]) - s.upgradeContainers(s.chains[1]) + + // remove all containers so we can upgrade them to the new version + for _, chainConfig := range s.chainConfigs { + s.upgradeContainers(chainConfig, chainConfig.propHeight) + } } -func (s *IntegrationTestSuite) upgradeContainers(c *chain.Chain) { +func (s *IntegrationTestSuite) upgradeContainers(chainConfig *chainConfig, propHeight int) { // upgrade containers to the locally compiled daemon - s.T().Logf("starting upgrade for chain-id: %s...", c.ChainMeta.Id) + chain := chainConfig.chain + s.T().Logf("starting upgrade for chain-id: %s...", chain.ChainMeta.Id) pwd, err := os.Getwd() s.Require().NoError(err) - for i, val := range c.Validators { + + for i, val := range chain.Validators { + if _, ok := chainConfig.skipRunValidatorIndexes[i]; ok { + continue + } + runOpts := &dockertest.RunOptions{ Name: val.Name, - Repository: "osmosis", - Tag: "debug", + Repository: dockerconfig.LocalOsmoRepository, + Tag: dockerconfig.LocalOsmoTag, NetworkID: s.dkrNet.Network.ID, User: "root:root", Mounts: []string{ @@ -537,47 +562,51 @@ func (s *IntegrationTestSuite) upgradeContainers(c *chain.Chain) { resource, err := s.dkrPool.RunWithOptions(runOpts, noRestart) s.Require().NoError(err) - s.valResources[c.ChainMeta.Id][i] = resource + s.valResources[chain.ChainMeta.Id][i] = resource s.T().Logf("started %s validator container: %s", resource.Container.Name[1:], resource.Container.ID) } // check that we are creating blocks again - for i := range c.Validators { - if c.ChainMeta.Id == chain.ChainAID { - propHeight = propHeightA - } else { - propHeight = propHeightB + for i := range chain.Validators { + if _, ok := chainConfig.skipRunValidatorIndexes[i]; ok { + continue } + s.Require().Eventually( func() bool { - currentHeight := s.getCurrentChainHeight(s.valResources[c.ChainMeta.Id][i].Container.ID) + currentHeight := s.getCurrentChainHeight(s.valResources[chain.ChainMeta.Id][i].Container.ID) if currentHeight <= propHeight { - s.T().Logf("current block height on %s is %v, waiting to create blocks container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], currentHeight, s.valResources[c.ChainMeta.Id][i].Container.ID) + s.T().Logf("current block height on %s is %v, waiting to create blocks container: %s", s.valResources[chain.ChainMeta.Id][i].Container.Name[1:], currentHeight, s.valResources[chain.ChainMeta.Id][i].Container.ID) } return currentHeight > propHeight }, 5*time.Minute, time.Second, ) - s.T().Logf("upgrade successful on %s validator container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], s.valResources[c.ChainMeta.Id][i].Container.ID) + s.T().Logf("upgrade successful on %s validator container: %s", s.valResources[chain.ChainMeta.Id][i].Container.Name[1:], s.valResources[chain.ChainMeta.Id][i].Container.ID) } - } func (s *IntegrationTestSuite) createPreUpgradeState() { - s.sendIBC(s.chains[0], s.chains[1], s.chains[1].Validators[0].PublicAddress, chain.OsmoToken) - s.sendIBC(s.chains[1], s.chains[0], s.chains[0].Validators[0].PublicAddress, chain.OsmoToken) - s.sendIBC(s.chains[0], s.chains[1], s.chains[1].Validators[0].PublicAddress, chain.StakeToken) - s.sendIBC(s.chains[1], s.chains[0], s.chains[0].Validators[0].PublicAddress, chain.StakeToken) - s.createPool(s.chains[0], "pool1A.json") - s.createPool(s.chains[1], "pool1B.json") + chainA := s.chainConfigs[0].chain + chainB := s.chainConfigs[1].chain + + s.sendIBC(chainA, chainB, chainB.Validators[0].PublicAddress, chain.OsmoToken) + s.sendIBC(chainB, chainA, chainA.Validators[0].PublicAddress, chain.OsmoToken) + s.sendIBC(chainA, chainB, chainB.Validators[0].PublicAddress, chain.StakeToken) + s.sendIBC(chainB, chainA, chainA.Validators[0].PublicAddress, chain.StakeToken) + s.createPool(chainA, "pool1A.json") + s.createPool(chainB, "pool1B.json") } func (s *IntegrationTestSuite) runPostUpgradeTests() { - s.sendIBC(s.chains[0], s.chains[1], s.chains[1].Validators[0].PublicAddress, chain.OsmoToken) - s.sendIBC(s.chains[1], s.chains[0], s.chains[0].Validators[0].PublicAddress, chain.OsmoToken) - s.sendIBC(s.chains[0], s.chains[1], s.chains[1].Validators[0].PublicAddress, chain.StakeToken) - s.sendIBC(s.chains[1], s.chains[0], s.chains[0].Validators[0].PublicAddress, chain.StakeToken) - s.createPool(s.chains[0], "pool2A.json") - s.createPool(s.chains[1], "pool2B.json") + chainA := s.chainConfigs[0].chain + chainB := s.chainConfigs[1].chain + + s.sendIBC(chainA, chainB, chainB.Validators[0].PublicAddress, chain.OsmoToken) + s.sendIBC(chainB, chainA, chainA.Validators[0].PublicAddress, chain.OsmoToken) + s.sendIBC(chainA, chainB, chainB.Validators[0].PublicAddress, chain.StakeToken) + s.sendIBC(chainB, chainA, chainA.Validators[0].PublicAddress, chain.StakeToken) + s.createPool(chainA, "pool2A.json") + s.createPool(chainB, "pool2B.json") } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index f329cdcd80f..43030e83b5a 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -5,9 +5,10 @@ import ( ) func (s *IntegrationTestSuite) TestIBCTokenTransfer() { - s.Run("send_uosmo_to_chainB", func() { - // compare coins of reciever pre and post IBC send - // diff should only be the amount sent - s.sendIBC(s.chains[0], s.chains[1], s.chains[1].Validators[0].PublicAddress, chain.OsmoToken) - }) + chainA := s.chainConfigs[0].chain + chainB := s.chainConfigs[1].chain + + // compare coins of reciever pre and post IBC send + // diff should only be the amount sent + s.sendIBC(chainA, chainB, chainB.Validators[0].PublicAddress, chain.OsmoToken) } diff --git a/tests/e2e/e2e_util_test.go b/tests/e2e/e2e_util_test.go index 1a6c26f7870..f0324a81e6a 100644 --- a/tests/e2e/e2e_util_test.go +++ b/tests/e2e/e2e_util_test.go @@ -17,8 +17,8 @@ import ( "github.com/osmosis-labs/osmosis/v7/tests/e2e/util" ) -func (s *IntegrationTestSuite) connectIBCChains() { - s.T().Logf("connecting %s and %s chains via IBC", s.chains[0].ChainMeta.Id, s.chains[1].ChainMeta.Id) +func (s *IntegrationTestSuite) connectIBCChains(chainA *chain.Chain, chainB *chain.Chain) { + s.T().Logf("connecting %s and %s chains via IBC", chainA.ChainMeta.Id, chainB.ChainMeta.Id) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() @@ -33,8 +33,8 @@ func (s *IntegrationTestSuite) connectIBCChains() { "hermes", "create", "channel", - s.chains[0].ChainMeta.Id, - s.chains[1].ChainMeta.Id, + chainA.ChainMeta.Id, + chainB.ChainMeta.Id, "--port-a=transfer", "--port-b=transfer", }, @@ -63,7 +63,7 @@ func (s *IntegrationTestSuite) connectIBCChains() { "failed to connect chains via IBC: %s", errBuf.String(), ) - s.T().Logf("connected %s and %s chains via IBC", s.chains[0].ChainMeta.Id, s.chains[1].ChainMeta.Id) + s.T().Logf("connected %s and %s chains via IBC", chainA.ChainMeta.Id, chainB.ChainMeta.Id) } func (s *IntegrationTestSuite) sendIBC(srcChain *chain.Chain, dstChain *chain.Chain, recipient string, token sdk.Coin) { @@ -225,12 +225,16 @@ func (s *IntegrationTestSuite) depositProposal(c *chain.Chain) { } -func (s *IntegrationTestSuite) voteProposal(c *chain.Chain) { +func (s *IntegrationTestSuite) voteProposal(chainConfig *chainConfig) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() + chain := chainConfig.chain - s.T().Logf("voting for upgrade proposal for chain-id: %s", c.ChainMeta.Id) - for i := range c.Validators { + s.T().Logf("voting for upgrade proposal for chain-id: %s", chain.ChainMeta.Id) + for i := range chain.Validators { + if _, ok := chainConfig.skipRunValidatorIndexes[i]; ok { + continue + } var ( outBuf bytes.Buffer @@ -243,10 +247,10 @@ func (s *IntegrationTestSuite) voteProposal(c *chain.Chain) { Context: ctx, AttachStdout: true, AttachStderr: true, - Container: s.valResources[c.ChainMeta.Id][i].Container.ID, + Container: s.valResources[chain.ChainMeta.Id][i].Container.ID, User: "root", Cmd: []string{ - "osmosisd", "tx", "gov", "vote", "1", "yes", "--from=val", fmt.Sprintf("--chain-id=%s", c.ChainMeta.Id), "-b=block", "--yes", "--keyring-backend=test", + "osmosisd", "tx", "gov", "vote", "1", "yes", "--from=val", fmt.Sprintf("--chain-id=%s", chain.ChainMeta.Id), "-b=block", "--yes", "--keyring-backend=test", }, }) s.Require().NoError(err) @@ -264,7 +268,7 @@ func (s *IntegrationTestSuite) voteProposal(c *chain.Chain) { "tx returned a non-zero code; stdout: %s, stderr: %s", outBuf.String(), errBuf.String(), ) - s.T().Logf("successfully voted for proposal from %s container: %s", s.valResources[c.ChainMeta.Id][i].Container.Name[1:], s.valResources[c.ChainMeta.Id][i].Container.ID) + s.T().Logf("successfully voted for proposal from %s container: %s", s.valResources[chain.ChainMeta.Id][i].Container.Name[1:], s.valResources[chain.ChainMeta.Id][i].Container.ID) } } From 2f3c7b1771a0c799bb4f7f3cac4d8e4e8f4a5037 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Tue, 24 May 2022 20:12:55 +0200 Subject: [PATCH 07/26] Fix sendblock test (#1568) ## What is the purpose of the change SendBlock_test was failing before, due to the from address not being 20 or 32 bytes. Not sure why this didn't fail in CI earlier. ## Brief Changelog Fixes a testcase that was failing on main. ## Testing and Verifying Tests pass ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? not applicable --- ante/sendblock_test.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ante/sendblock_test.go b/ante/sendblock_test.go index 38ab9d57100..59818a5be2c 100644 --- a/ante/sendblock_test.go +++ b/ante/sendblock_test.go @@ -14,19 +14,21 @@ func TestSendBlockDecorator(t *testing.T) { to sdk.AccAddress expectPass bool }{ - {sdk.AccAddress("honest-sender"), sdk.AccAddress("honest-address"), true}, - {sdk.AccAddress("honest-sender"), sdk.AccAddress("recovery-address"), true}, - {sdk.AccAddress("malicious-sender"), sdk.AccAddress("recovery-address"), true}, - {sdk.AccAddress("malicious-sender"), sdk.AccAddress("random-address"), false}, + {sdk.AccAddress("honest-sender_______"), sdk.AccAddress("honest-address"), true}, + {sdk.AccAddress("honest-sender_______"), sdk.AccAddress("recovery-address"), true}, + {sdk.AccAddress("malicious-sender____"), sdk.AccAddress("recovery-address"), true}, + {sdk.AccAddress("malicious-sender____"), sdk.AccAddress("random-address"), false}, } permittedOnlySendTo := map[string]string{ - sdk.AccAddress("malicious-sender").String(): sdk.AccAddress("recovery-address").String(), + sdk.AccAddress("malicious-sender____").String(): sdk.AccAddress("recovery-address").String(), } decorator := NewSendBlockDecorator(SendBlockOptions{permittedOnlySendTo}) for _, testCase := range testCases { - err := decorator.CheckIfBlocked([]sdk.Msg{bank.NewMsgSend(testCase.from, testCase.to, sdk.NewCoins(sdk.NewInt64Coin("test", 1)))}) + err := decorator.CheckIfBlocked( + []sdk.Msg{ + bank.NewMsgSend(testCase.from, testCase.to, sdk.NewCoins(sdk.NewInt64Coin("test", 1)))}) if testCase.expectPass { require.NoError(t, err) } else { From 78ed8771627b6dd9c55272e0ba857c80545d6bc5 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 25 May 2022 02:47:47 +0200 Subject: [PATCH 08/26] Add msg_filter_ante_handler to block IBCTimeoutOnClose (#1571) Closes: #XXX ## What is the purpose of the change Add a msg_filter_ante for v9, and block MsgTimeoutOnClose, due to edge cases that can happen if Terra re-enables the previously closed IBC channels. (This is because Closed channels were never meant to be re-opened) ## Brief Changelog Adds a ante handler filter for IBC Timeout close messages. This is a port of Bez' old work. ## Testing and Verifying Covered by provided tests/ ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? yes - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? TODO --- app/ante.go | 4 +- app/upgrades/v9/msg_filter_ante.go | 35 +++++++++++++ app/upgrades/v9/msg_filter_ante_test.go | 70 +++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 app/upgrades/v9/msg_filter_ante.go create mode 100644 app/upgrades/v9/msg_filter_ante_test.go diff --git a/app/ante.go b/app/ante.go index 0705af4d3b2..ba999fc802a 100644 --- a/app/ante.go +++ b/app/ante.go @@ -12,8 +12,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/signing" osmoante "github.com/osmosis-labs/osmosis/v7/ante" + v9 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v9" - v8 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8" txfeeskeeper "github.com/osmosis-labs/osmosis/v7/x/txfees/keeper" txfeestypes "github.com/osmosis-labs/osmosis/v7/x/txfees/types" ) @@ -42,7 +42,7 @@ func NewAnteHandler( wasmkeeper.NewLimitSimulationGasDecorator(wasmConfig.SimulationGasLimit), wasmkeeper.NewCountTXDecorator(txCounterStoreKey), ante.NewRejectExtensionOptionsDecorator(), - v8.MsgFilterDecorator{}, + v9.MsgFilterDecorator{}, // Use Mempool Fee Decorator from our txfees module instead of default one from auth // https://github.com/cosmos/cosmos-sdk/blob/master/x/auth/middleware/fee.go#L34 mempoolFeeDecorator, diff --git a/app/upgrades/v9/msg_filter_ante.go b/app/upgrades/v9/msg_filter_ante.go new file mode 100644 index 00000000000..b6035b35976 --- /dev/null +++ b/app/upgrades/v9/msg_filter_ante.go @@ -0,0 +1,35 @@ +package v9 + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + ibcchanneltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" +) + +// MsgFilterDecorator defines an AnteHandler decorator for the v9 upgrade that +// provide height-gated message filtering acceptance. +type MsgFilterDecorator struct{} + +// AnteHandle performs an AnteHandler check that returns an error if the tx contains a message +// that is blocked. +// Right now, we block MsgTimeoutOnClose due to incorrect behavior that could occur if a packet is re-enabled. +func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + if hasInvalidMsgs(tx.GetMsgs()) { + currHeight := ctx.BlockHeight() + return ctx, fmt.Errorf("tx contains unsupported message types at height %d", currHeight) + } + + return next(ctx, tx, simulate) +} + +func hasInvalidMsgs(msgs []sdk.Msg) bool { + for _, msg := range msgs { + switch msg.(type) { + case *ibcchanneltypes.MsgTimeoutOnClose: + return true + } + } + + return false +} diff --git a/app/upgrades/v9/msg_filter_ante_test.go b/app/upgrades/v9/msg_filter_ante_test.go new file mode 100644 index 00000000000..0a3d9b5fee5 --- /dev/null +++ b/app/upgrades/v9/msg_filter_ante_test.go @@ -0,0 +1,70 @@ +package v9_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/stretchr/testify/require" + + ibcchanneltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types" + + "github.com/osmosis-labs/osmosis/v7/app" + v8 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v8" + v9 "github.com/osmosis-labs/osmosis/v7/app/upgrades/v9" +) + +func noOpAnteDecorator() sdk.AnteHandler { + return func(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) { + return ctx, nil + } +} + +func TestMsgFilterDecorator(t *testing.T) { + handler := v9.MsgFilterDecorator{} + txCfg := app.MakeEncodingConfig().TxConfig + + addr1 := sdk.AccAddress([]byte("addr1_______________")) + addr2 := sdk.AccAddress([]byte("addr2_______________")) + + testCases := []struct { + name string + ctx sdk.Context + msgs []sdk.Msg + expectErr bool + }{ + { + name: "valid tx", + ctx: sdk.Context{}.WithBlockHeight(v8.UpgradeHeight - 1), + msgs: []sdk.Msg{ + banktypes.NewMsgSend(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("foo", 5))), + }, + expectErr: false, + }, + { + name: "invalid tx", + ctx: sdk.Context{}.WithBlockHeight(v8.UpgradeHeight), + msgs: []sdk.Msg{ + banktypes.NewMsgSend(addr1, addr2, sdk.NewCoins(sdk.NewInt64Coin("foo", 5))), + &ibcchanneltypes.MsgTimeoutOnClose{}, + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + txBuilder := txCfg.NewTxBuilder() + require.NoError(t, txBuilder.SetMsgs(tc.msgs...)) + + _, err := handler.AnteHandle(tc.ctx, txBuilder.GetTx(), false, noOpAnteDecorator()) + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} From 09f3184b47bcaf498fcabd46d4ea60a2cdde4e8b Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 25 May 2022 03:04:43 +0200 Subject: [PATCH 09/26] Adding ICA Host module (#1564) --- app/keepers/keepers.go | 26 ++++++++++++++- app/modules.go | 8 +++++ app/upgrades/v9/constants.go | 4 ++- app/upgrades/v9/upgrade_test.go | 58 +++++++++++++++++++++++++++++++++ app/upgrades/v9/upgrades.go | 58 +++++++++++++++++++++++++++++++-- 5 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 app/upgrades/v9/upgrade_test.go diff --git a/app/keepers/keepers.go b/app/keepers/keepers.go index 19af2725c00..d3c3387eb43 100644 --- a/app/keepers/keepers.go +++ b/app/keepers/keepers.go @@ -32,6 +32,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/upgrade" upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + + icahost "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host" + icahostkeeper "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/keeper" + icahosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" ibctransferkeeper "github.com/cosmos/ibc-go/v3/modules/apps/transfer/keeper" ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ibcclient "github.com/cosmos/ibc-go/v3/modules/core/02-client" @@ -82,6 +86,7 @@ type AppKeepers struct { // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper + ScopedICAHostKeeper capabilitykeeper.ScopedKeeper ScopedTransferKeeper capabilitykeeper.ScopedKeeper ScopedWasmKeeper capabilitykeeper.ScopedKeeper @@ -93,6 +98,7 @@ type AppKeepers struct { DistrKeeper *distrkeeper.Keeper SlashingKeeper *slashingkeeper.Keeper IBCKeeper *ibckeeper.Keeper + ICAHostKeeper *icahostkeeper.Keeper TransferKeeper *ibctransferkeeper.Keeper Bech32IBCKeeper *bech32ibckeeper.Keeper Bech32ICS20Keeper *bech32ics20keeper.Keeper @@ -108,6 +114,7 @@ type AppKeepers struct { GovKeeper *govkeeper.Keeper WasmKeeper *wasm.Keeper TokenFactoryKeeper *tokenfactorykeeper.Keeper + // IBC modules // transfer module TransferModule transfer.AppModule @@ -206,9 +213,22 @@ func (appKeepers *AppKeepers) InitNormalKeepers( appKeepers.TransferModule = transfer.NewAppModule(*appKeepers.TransferKeeper) transferIBCModule := transfer.NewIBCModule(*appKeepers.TransferKeeper) + icaHostKeeper := icahostkeeper.NewKeeper( + appCodec, appKeepers.keys[icahosttypes.StoreKey], + appKeepers.GetSubspace(icahosttypes.SubModuleName), + appKeepers.IBCKeeper.ChannelKeeper, + &appKeepers.IBCKeeper.PortKeeper, + appKeepers.AccountKeeper, + appKeepers.ScopedICAHostKeeper, + bApp.MsgServiceRouter(), + ) + appKeepers.ICAHostKeeper = &icaHostKeeper + + icaHostIBCModule := icahost.NewIBCModule(*appKeepers.ICAHostKeeper) // Create static IBC router, add transfer route, then set and seal it ibcRouter := porttypes.NewRouter() - ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferIBCModule) + ibcRouter.AddRoute(icahosttypes.SubModuleName, icaHostIBCModule). + AddRoute(ibctransfertypes.ModuleName, transferIBCModule) // Note: the sealing is done after creating wasmd and wiring that up appKeepers.Bech32IBCKeeper = bech32ibckeeper.NewKeeper( @@ -389,6 +409,7 @@ func (appKeepers *AppKeepers) InitSpecialKeepers( // add capability keeper and ScopeToModule for ibc module appKeepers.CapabilityKeeper = capabilitykeeper.NewKeeper(appCodec, appKeepers.keys[capabilitytypes.StoreKey], appKeepers.memKeys[capabilitytypes.MemStoreKey]) appKeepers.ScopedIBCKeeper = appKeepers.CapabilityKeeper.ScopeToModule(ibchost.ModuleName) + appKeepers.ScopedICAHostKeeper = appKeepers.CapabilityKeeper.ScopeToModule(icahosttypes.SubModuleName) appKeepers.ScopedTransferKeeper = appKeepers.CapabilityKeeper.ScopeToModule(ibctransfertypes.ModuleName) appKeepers.ScopedWasmKeeper = appKeepers.CapabilityKeeper.ScopeToModule(wasm.ModuleName) appKeepers.CapabilityKeeper.Seal() @@ -424,6 +445,7 @@ func (appKeepers *AppKeepers) initParamsKeeper(appCodec codec.BinaryCodec, legac paramsKeeper.Subspace(crisistypes.ModuleName) paramsKeeper.Subspace(ibctransfertypes.ModuleName) paramsKeeper.Subspace(ibchost.ModuleName) + paramsKeeper.Subspace(icahosttypes.SubModuleName) paramsKeeper.Subspace(incentivestypes.ModuleName) paramsKeeper.Subspace(poolincentivestypes.ModuleName) paramsKeeper.Subspace(superfluidtypes.ModuleName) @@ -492,6 +514,7 @@ func (appKeepers *AppKeepers) SetupHooks() { ) } +// TODO: We need to automate this, by bundling with a module struct... func KVStoreKeys() []string { return []string{ authtypes.StoreKey, @@ -503,6 +526,7 @@ func KVStoreKeys() []string { govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, + icahosttypes.StoreKey, upgradetypes.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, diff --git a/app/modules.go b/app/modules.go index 50e097b2988..6260f85f255 100644 --- a/app/modules.go +++ b/app/modules.go @@ -5,6 +5,9 @@ import ( ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ibc "github.com/cosmos/ibc-go/v3/modules/core" ibchost "github.com/cosmos/ibc-go/v3/modules/core/24-host" + + ica "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts" + icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" "github.com/osmosis-labs/bech32-ibc/x/bech32ibc" bech32ibctypes "github.com/osmosis-labs/bech32-ibc/x/bech32ibc/types" "github.com/osmosis-labs/bech32-ibc/x/bech32ics20" @@ -64,9 +67,11 @@ 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, + icatypes.ModuleName: nil, minttypes.ModuleName: {authtypes.Minter, authtypes.Burner}, minttypes.DeveloperVestingModuleAcctName: nil, stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, @@ -114,6 +119,7 @@ func appModules( evidence.NewAppModule(*app.EvidenceKeeper), authzmodule.NewAppModule(appCodec, *app.AuthzKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry), ibc.NewAppModule(app.IBCKeeper), + ica.NewAppModule(nil, app.ICAHostKeeper), params.NewAppModule(*app.ParamsKeeper), app.TransferModule, gamm.NewAppModule(appCodec, *app.GAMMKeeper, app.AccountKeeper, app.BankKeeper), @@ -154,6 +160,7 @@ func orderBeginBlockers() []string { stakingtypes.ModuleName, ibchost.ModuleName, ibctransfertypes.ModuleName, + icatypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, govtypes.ModuleName, @@ -199,6 +206,7 @@ var modulesOrderInitGenesis = []string{ minttypes.ModuleName, crisistypes.ModuleName, ibchost.ModuleName, + icatypes.ModuleName, gammtypes.ModuleName, txfeestypes.ModuleName, genutiltypes.ModuleName, diff --git a/app/upgrades/v9/constants.go b/app/upgrades/v9/constants.go index 197cc8f0fb9..791e83fd329 100644 --- a/app/upgrades/v9/constants.go +++ b/app/upgrades/v9/constants.go @@ -5,6 +5,8 @@ import ( store "github.com/cosmos/cosmos-sdk/store/types" + icahosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" + tokenfactorytypes "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" ) @@ -19,7 +21,7 @@ var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, StoreUpgrades: store.StoreUpgrades{ - Added: []string{tokenfactorytypes.ModuleName}, + Added: []string{tokenfactorytypes.ModuleName, icahosttypes.StoreKey}, Deleted: []string{ClaimsModuleName}, }, } diff --git a/app/upgrades/v9/upgrade_test.go b/app/upgrades/v9/upgrade_test.go new file mode 100644 index 00000000000..2fa4fbcec6f --- /dev/null +++ b/app/upgrades/v9/upgrade_test.go @@ -0,0 +1,58 @@ +package v9_test + +import ( + "fmt" + + upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +const dummyUpgradeHeight = 5 + +func (suite *UpgradeTestSuite) TestUpgradePayments() { + testCases := []struct { + msg string + pre_update func() + update func() + post_update func() + expPass bool + }{ + { + "Test that upgrade does not panic", + func() { + // Create pool 1 + suite.PrepareBalancerPool() + }, + func() { + // run upgrade + // TODO: Refactor this all into a helper fn + suite.Ctx = suite.Ctx.WithBlockHeight(dummyUpgradeHeight - 1) + plan := upgradetypes.Plan{Name: "v9", Height: dummyUpgradeHeight} + err := suite.App.UpgradeKeeper.ScheduleUpgrade(suite.Ctx, plan) + suite.Require().NoError(err) + plan, exists := suite.App.UpgradeKeeper.GetUpgradePlan(suite.Ctx) + suite.Require().True(exists) + + suite.Ctx = suite.Ctx.WithBlockHeight(dummyUpgradeHeight) + suite.Require().NotPanics(func() { + beginBlockRequest := abci.RequestBeginBlock{} + suite.App.BeginBlocker(suite.Ctx, beginBlockRequest) + }) + }, + func() { + + }, + true, + }, + } + + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.msg), func() { + suite.SetupTest() // reset + + tc.pre_update() + tc.update() + tc.post_update() + }) + } +} diff --git a/app/upgrades/v9/upgrades.go b/app/upgrades/v9/upgrades.go index 37f891b618a..8103fffd67f 100644 --- a/app/upgrades/v9/upgrades.go +++ b/app/upgrades/v9/upgrades.go @@ -3,8 +3,20 @@ package v9 import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" + gammtypes "github.com/osmosis-labs/osmosis/v7/x/gamm/types" + + ica "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts" + icacontrollertypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/controller/types" + icahosttypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/host/types" + icatypes "github.com/cosmos/ibc-go/v3/modules/apps/27-interchain-accounts/types" + "github.com/osmosis-labs/osmosis/v7/app/keepers" ) @@ -13,8 +25,50 @@ func CreateUpgradeHandler( configurator module.Configurator, keepers *keepers.AppKeepers, ) upgradetypes.UpgradeHandler { - return func(ctx sdk.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { ExecuteProp214(ctx, keepers.GAMMKeeper) - return mm.RunMigrations(ctx, configurator, vm) + + // Add Interchain Accounts host module + // set the ICS27 consensus version so InitGenesis is not run + fromVM[icatypes.ModuleName] = mm.Modules[icatypes.ModuleName].ConsensusVersion() + + // create ICS27 Controller submodule params, controller module not enabled. + controllerParams := icacontrollertypes.Params{} + + // create ICS27 Host submodule params + hostParams := icahosttypes.Params{ + HostEnabled: true, + AllowMessages: []string{ + sdk.MsgTypeURL(&banktypes.MsgSend{}), + sdk.MsgTypeURL(&stakingtypes.MsgDelegate{}), + sdk.MsgTypeURL(&stakingtypes.MsgBeginRedelegate{}), + sdk.MsgTypeURL(&stakingtypes.MsgCreateValidator{}), + sdk.MsgTypeURL(&stakingtypes.MsgEditValidator{}), + sdk.MsgTypeURL(&distrtypes.MsgWithdrawDelegatorReward{}), + sdk.MsgTypeURL(&distrtypes.MsgSetWithdrawAddress{}), + sdk.MsgTypeURL(&distrtypes.MsgWithdrawValidatorCommission{}), + sdk.MsgTypeURL(&distrtypes.MsgFundCommunityPool{}), + sdk.MsgTypeURL(&govtypes.MsgVote{}), + sdk.MsgTypeURL(&authz.MsgExec{}), + sdk.MsgTypeURL(&authz.MsgGrant{}), + sdk.MsgTypeURL(&authz.MsgRevoke{}), + sdk.MsgTypeURL(&gammtypes.MsgJoinPool{}), + sdk.MsgTypeURL(&gammtypes.MsgExitPool{}), + sdk.MsgTypeURL(&gammtypes.MsgSwapExactAmountIn{}), + sdk.MsgTypeURL(&gammtypes.MsgSwapExactAmountOut{}), + sdk.MsgTypeURL(&gammtypes.MsgJoinSwapExternAmountIn{}), + sdk.MsgTypeURL(&gammtypes.MsgJoinSwapShareAmountOut{}), + sdk.MsgTypeURL(&gammtypes.MsgExitSwapExternAmountOut{}), + sdk.MsgTypeURL(&gammtypes.MsgExitSwapShareAmountIn{}), + }, + } + + // initialize ICS27 module + icamodule, correctTypecast := mm.Modules[icatypes.ModuleName].(ica.AppModule) + if !correctTypecast { + panic("mm.Modules[icatypes.ModuleName] is not of type ica.AppModule") + } + icamodule.InitModule(ctx, controllerParams, hostParams) + return mm.RunMigrations(ctx, configurator, fromVM) } } From 1510160b2a263811e3d4d2a4c1b3531566da6111 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 24 May 2022 18:12:51 -0700 Subject: [PATCH 10/26] chore: upgrade sdk with app version fix for state-sync (#1570) Closes: #XXX ## What is the purpose of the change Upgrade SDK with the app version change and set the app version in the upgrade handler ## Testing and Verifying - e2e test for state sync is in progress: https://github.com/osmosis-labs/osmosis/pull/1572 - upgrade itself is verified by the existing e2e test - needs manual testing ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? yes - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? yes - How is the feature or change documented? not applicable --- CHANGELOG.md | 1 + app/upgrades/v9/upgrades.go | 8 ++++++++ go.mod | 2 +- go.sum | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 651c47d29cf..c2b9feab5c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +* [#1570](https://github.com/osmosis-labs/osmosis/pull/1570) upgrade sdk with app version fix for state-sync * [#1554](https://github.com/osmosis-labs/osmosis/pull/1554) local dev environment * [#1535](https://github.com/osmosis-labs/osmosis/pull/1535) upgrade wasmd to v0.27.0.rc3-osmo and ibc-go to v3 * [#1435] `x/tokenfactory` create denom fee for spam resistance diff --git a/app/upgrades/v9/upgrades.go b/app/upgrades/v9/upgrades.go index 8103fffd67f..773ca2437de 100644 --- a/app/upgrades/v9/upgrades.go +++ b/app/upgrades/v9/upgrades.go @@ -20,6 +20,8 @@ import ( "github.com/osmosis-labs/osmosis/v7/app/keepers" ) +const preUpgradeAppVersion = 8 + func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, @@ -27,6 +29,12 @@ func CreateUpgradeHandler( ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { ExecuteProp214(ctx, keepers.GAMMKeeper) + + // We set the app version to pre-upgrade because it will be incremented by one + // after the upgrade is applied by the handler. + if err := keepers.UpgradeKeeper.SetAppVersion(ctx, preUpgradeAppVersion); err != nil { + return nil, err + } // Add Interchain Accounts host module // set the ICS27 consensus version so InitGenesis is not run diff --git a/go.mod b/go.mod index 40a05f68865..675632ee331 100644 --- a/go.mod +++ b/go.mod @@ -272,7 +272,7 @@ replace ( // branch: v0.27.0.rc3-osmo, current tag: v0.27.0.rc3-osmo github.com/CosmWasm/wasmd => github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58 // Our cosmos-sdk branch is: https://github.com/osmosis-labs/cosmos-sdk v0.45.0x-osmo-v7 - github.com/cosmos/cosmos-sdk => github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220517190359-30ebc413ddff + github.com/cosmos/cosmos-sdk => github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220524162204-830f277f8259 // Use Osmosis fast iavl github.com/cosmos/iavl => github.com/osmosis-labs/iavl v0.17.3-osmo-v7 // use cosmos-compatible protobufs diff --git a/go.sum b/go.sum index 49538c6b7c4..be81fce9ae8 100644 --- a/go.sum +++ b/go.sum @@ -1024,8 +1024,8 @@ github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm1 github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/osmosis-labs/bech32-ibc v0.3.0-rc1 h1:frHKHEdPfzoK2iMF2GeWKudLLzUXz+6GJcdZ/TMcs2k= github.com/osmosis-labs/bech32-ibc v0.3.0-rc1/go.mod h1:X5/FZHMPL+B3ufuVyY2/koxVjd4hIwyTLjYP1DZwppQ= -github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220517190359-30ebc413ddff h1:GwNR/GMCSfZsllKo0ZX/tE+sucumAA5K96M7bteZzKI= -github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220517190359-30ebc413ddff/go.mod h1:pMiEr6WR7drhXAXK1FOdAKPazWCi7b+WOyWOF4O0OXY= +github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220524162204-830f277f8259 h1:myaa05LG9MtkqvE9jErU0qWZPENOwc7kiqmFLeG3cTs= +github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220524162204-830f277f8259/go.mod h1:pMiEr6WR7drhXAXK1FOdAKPazWCi7b+WOyWOF4O0OXY= github.com/osmosis-labs/iavl v0.17.3-osmo-v7 h1:6KcADC/WhL7yDmNQxUIJt2XmzNt4FfRmq9gRke45w74= github.com/osmosis-labs/iavl v0.17.3-osmo-v7/go.mod h1:lJEOIlsd3sVO0JDyXWIXa9/Ur5FBscP26zJx0KxHjto= github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58 h1:15l3Iss2oCGCeJRi2g3CuCnqmEjpAr3Le7cDnoN/LS0= From ddb3157681dcc98b36b1d0cb161ec6234962c396 Mon Sep 17 00:00:00 2001 From: "Matt, Park" <45252226+mattverse@users.noreply.github.com> Date: Wed, 25 May 2022 10:30:09 +0900 Subject: [PATCH 11/26] Token factory documentation (#1545) * Token factory documentation * Fix lint I messed up on another PR Co-authored-by: Dev Ojha --- app/upgrades/v9/upgrades.go | 4 +- x/tokenfactory/spec/README.md | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/app/upgrades/v9/upgrades.go b/app/upgrades/v9/upgrades.go index 773ca2437de..5ca07a3c47e 100644 --- a/app/upgrades/v9/upgrades.go +++ b/app/upgrades/v9/upgrades.go @@ -29,8 +29,8 @@ func CreateUpgradeHandler( ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { ExecuteProp214(ctx, keepers.GAMMKeeper) - - // We set the app version to pre-upgrade because it will be incremented by one + + // We set the app version to pre-upgrade because it will be incremented by one // after the upgrade is applied by the handler. if err := keepers.UpgradeKeeper.SetAppVersion(ctx, preUpgradeAppVersion); err != nil { return nil, err diff --git a/x/tokenfactory/spec/README.md b/x/tokenfactory/spec/README.md index adb52c4f08b..5a95dab8ecb 100644 --- a/x/tokenfactory/spec/README.md +++ b/x/tokenfactory/spec/README.md @@ -16,3 +16,74 @@ created denom. Once a denom is created, the original creator is given accounts using the authz module. The `ChangeAdmin` functionality, allows changing the master admin account, or even setting it to `""`, meaning no account has admin privileges of the asset. + + +## Messages + +### CreateDenom +- Creates a denom of `factory/{creator address}/{nonce}` given the denom creator address and the denom nonce. The case a denom has a slash in its nonce is handled within the module. +``` {.go} +message MsgCreateDenom { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + string nonce = 2 [ (gogoproto.moretags) = "yaml:\"nonce\"" ]; +} +``` + +**State Modifications:** +- Fund community pool with the denom creation fee from the creator address, set in `Params` +- Set `DenomMetaData` via bank keeper +- Set `AuthorityMetadata` for the given denom to store the admin for the created denom `factory/{creator address}/{nonce}`. Admin is automatically set as the Msg sender +- Add denom to the `CreatorPrefixStore`, where a state of denoms created per creator is kept + +### Mint +- Minting of a specific denom is only allowed for the creator of the denom registered during `CreateDenom` +``` {.go} +message MsgMint { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + cosmos.base.v1beta1.Coin amount = 2 [ + (gogoproto.moretags) = "yaml:\"amount\"", + (gogoproto.nullable) = false + ]; +} +``` + +**State Modifications:** +- Saftey check the following + - Check that the denom minting is created via `tokenfactory` module + - Check that the sender of the message is the admin of the denom +- Mint designated amount of tokens for the denom via `bank` module + + + +### Burn +- Burning of a specific denom is only allowed for the creator of the denom registered during `CreateDenom` +``` {.go} +message MsgBurn { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + cosmos.base.v1beta1.Coin amount = 2 [ + (gogoproto.moretags) = "yaml:\"amount\"", + (gogoproto.nullable) = false + ]; +} +``` + +**State Modifications:** +- Saftey check the following + - Check that the denom minting is created via `tokenfactory` module + - Check that the sender of the message is the admin of the denom +- Burn designated amount of tokens for the denom via `bank` module + + +### ChangeAdmin +- Burning of a specific denom is only allowed for the creator of the denom registered during `CreateDenom` +``` {.go} +message MsgChangeAdmin { + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + string denom = 2 [ (gogoproto.moretags) = "yaml:\"denom\"" ]; + string newAdmin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; +} +``` + +**State Modifications:** +- Check that sender of the message is the admin of denom +- Modify `AuthorityMetadata` state entry to change the admin of the denom \ No newline at end of file From 4562b8b70857056c934495baee624bf26dbd939e Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 25 May 2022 03:36:34 +0200 Subject: [PATCH 12/26] Make CI only run on pushes to main / release branches. Comment out less useful CLI tests (#1573) ## What is the purpose of the change I'm losing a ton of time due to waiting for CI. This PR de-duplicates the on-push / pull-request CI contention. Now we only have CI run on pushes to main and release branches. (With sub-optimal release branch matching) Also comments out some of the less useless gamm tests. Were trying to eliminate these anyway, cref https://github.com/cosmos/cosmos-sdk/pull/11877 ## Testing and Verifying Tests still run on this PR, and they remain running on main. ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? CI out of scope of changelog - How is the feature or change documented? not applicable --- .github/workflows/test.yml | 5 ++- x/gamm/client/cli/cli_test.go | 80 ++++++++++++++++++----------------- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3c182008c6..6981e851aa8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,12 @@ name: Tests & Code Coverage on: pull_request: - push: branches: - "**" + push: + branches: + - "main" + - "v[0-9]**" jobs: should_run_go_test: diff --git a/x/gamm/client/cli/cli_test.go b/x/gamm/client/cli/cli_test.go index d7cd840dde7..f98623f0772 100644 --- a/x/gamm/client/cli/cli_test.go +++ b/x/gamm/client/cli/cli_test.go @@ -152,45 +152,47 @@ func (s *IntegrationTestSuite) TestNewCreatePoolCmd() { `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), false, &sdk.TxResponse{}, 0, }, - { - "future governor time", - fmt.Sprintf(` - { - "%s": "1node0token,3stake", - "%s": "100node0token,100stake", - "%s": "0.001", - "%s": "0.001", - "%s": "2h" - } - `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), - false, &sdk.TxResponse{}, 0, - }, - { - "future governor token + time", - fmt.Sprintf(` - { - "%s": "1node0token,3stake", - "%s": "100node0token,100stake", - "%s": "0.001", - "%s": "0.001", - "%s": "token,1000h" - } - `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), - false, &sdk.TxResponse{}, 0, - }, - { - "invalid future governor", - fmt.Sprintf(` - { - "%s": "1node0token,3stake", - "%s": "100node0token,100stake", - "%s": "0.001", - "%s": "0.001", - "%s": "validdenom,invalidtime" - } - `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), - true, &sdk.TxResponse{}, 7, - }, + // Due to CI time concerns, we leave these CLI tests commented out, and instead guaranteed via + // the logic tests. + // { + // "future governor time", + // fmt.Sprintf(` + // { + // "%s": "1node0token,3stake", + // "%s": "100node0token,100stake", + // "%s": "0.001", + // "%s": "0.001", + // "%s": "2h" + // } + // `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), + // false, &sdk.TxResponse{}, 0, + // }, + // { + // "future governor token + time", + // fmt.Sprintf(` + // { + // "%s": "1node0token,3stake", + // "%s": "100node0token,100stake", + // "%s": "0.001", + // "%s": "0.001", + // "%s": "token,1000h" + // } + // `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), + // false, &sdk.TxResponse{}, 0, + // }, + // { + // "invalid future governor", + // fmt.Sprintf(` + // { + // "%s": "1node0token,3stake", + // "%s": "100node0token,100stake", + // "%s": "0.001", + // "%s": "0.001", + // "%s": "validdenom,invalidtime" + // } + // `, cli.PoolFileWeights, cli.PoolFileInitialDeposit, cli.PoolFileSwapFee, cli.PoolFileExitFee, cli.PoolFileFutureGovernor), + // true, &sdk.TxResponse{}, 7, + // }, { "not valid json", "bad json", From c8ac95c6a6ea42e934d49599eafc8609b3c6fe61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 May 2022 01:52:58 +0000 Subject: [PATCH 13/26] chore(deps): Bump github.com/ory/dockertest/v3 from 3.8.1 to 3.9.0 (#1579) Bumps [github.com/ory/dockertest/v3](https://github.com/ory/dockertest) from 3.8.1 to 3.9.0.
Commits
  • 148c3da chore(deps): bump gotest.tools/v3 from 3.1.0 to 3.2.0 (#353)
  • e38b974 chore: containerd/continuity (#352)
  • cfab898 chore(deps): bump github.com/opencontainers/runc from 1.1.0 to 1.1.1 (#350)
  • f6f31ef chore(deps): bump github.com/cenkalti/backoff/v4 from 4.1.2 to 4.1.3 (#351)
  • 230039c fix: resource host for rootless containers (#348)
  • 23d5405 chore(deps): bump github.com/docker/cli (#349)
  • d93907d chore(deps): bump github.com/docker/cli (#345)
  • ef54b32 feat: add platform field to run options to support Apple M1 (#344)
  • 593d31d chore(deps): bump github.com/Microsoft/go-winio from 0.5.1 to 0.5.2 (#340)
  • 824f7a0 chore(deps): bump github.com/stretchr/testify from 1.7.0 to 1.7.1 (#347)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/ory/dockertest/v3&package-manager=go_modules&previous-version=3.8.1&new-version=3.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- go.mod | 12 ++++++------ go.sum | 40 +++++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index 675632ee331..d08264390ac 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golangci/golangci-lint v1.46.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 - github.com/ory/dockertest/v3 v3.8.1 + github.com/ory/dockertest/v3 v3.9.0 github.com/osmosis-labs/bech32-ibc v0.3.0-rc1 github.com/pkg/errors v0.9.1 github.com/rakyll/statik v0.1.7 @@ -46,7 +46,7 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v2 v2.1.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.5.1 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/OpenPeeDeeP/depguard v1.1.0 // indirect github.com/Workiva/go-datastructures v1.0.53 // indirect @@ -63,14 +63,14 @@ require ( github.com/breml/errchkjson v0.3.0 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/butuzov/ireturn v0.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.1.2 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4 // indirect github.com/coinbase/rosetta-sdk-go v0.7.0 // indirect github.com/confio/ics23/go v0.7.0 // indirect - github.com/containerd/continuity v0.2.1 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/btcutil v1.0.4 // indirect github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect @@ -83,7 +83,7 @@ require ( github.com/dgraph-io/badger/v2 v2.2007.3 // indirect github.com/dgraph-io/ristretto v0.0.3 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect - github.com/docker/cli v20.10.11+incompatible // indirect + github.com/docker/cli v20.10.14+incompatible // indirect github.com/docker/docker v20.10.7+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect @@ -190,7 +190,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.3 // indirect + github.com/opencontainers/runc v1.1.1 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.0 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index be81fce9ae8..4a26cd5e017 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,9 @@ github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuN github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -226,8 +227,8 @@ github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRt github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= @@ -241,10 +242,12 @@ github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6pr github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4 h1:tFXjAxje9thrTF4h57Ckik+scJjTWdwAtZqZPtOT48M= github.com/chavacava/garif v0.0.0-20220316182200-5cad0b5181d4/go.mod h1:W8EnPSQ8Nv4fUjc/v1/8tHFqhuOJXnRub0dTfuAQktU= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= @@ -263,9 +266,11 @@ github.com/confio/ics23/go v0.6.6/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4ur github.com/confio/ics23/go v0.7.0 h1:00d2kukk7sPoHWL4zZBZwzxnpA2pec1NPdwbSokJ5w8= github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4uraToOaKg= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.2.1 h1:/EeEo2EtN3umhbbgCveyjifoMYg0pS+nMMEemaYw634= github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -301,6 +306,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daixiang0/gci v0.3.3 h1:55xJKH7Gl9Vk6oQ1cMkwrDWjAkT1D+D1G9kNmRcAIY4= github.com/daixiang0/gci v0.3.3/go.mod h1:1Xr2bxnQbDxCqqulUOv8qpGqkgRw9RSCGGjEC2LjF8o= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= @@ -330,8 +336,8 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v20.10.11+incompatible h1:tXU1ezXcruZQRrMP8RN2z9N91h+6egZTS1gsPsKantc= -github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= +github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -434,6 +440,7 @@ github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-sourcemap/sourcemap v2.1.2+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.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -468,6 +475,7 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E 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= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0= @@ -819,7 +827,6 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg= github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= -github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= @@ -905,7 +912,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -913,7 +919,7 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1004,10 +1010,12 @@ github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrB github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.1 h1:PJ9DSs2sVwE0iVr++pAHE6QkS9tzcVWozlPifdwMgrU= +github.com/opencontainers/runc v1.1.1/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1020,8 +1028,8 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= -github.com/ory/dockertest/v3 v3.8.1 h1:vU/8d1We4qIad2YM0kOwRVtnyue7ExvacPiw1yDm17g= -github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= +github.com/ory/dockertest/v3 v3.9.0 h1:U7M9FfYEwF4uqEE6WUSFs7K+Hvb31CsCX5uZUZD3olI= +github.com/ory/dockertest/v3 v3.9.0/go.mod h1:jgm0rnguArPXsVduy+oUjzFtD0Na+DDNbUl8W5v+ez8= github.com/osmosis-labs/bech32-ibc v0.3.0-rc1 h1:frHKHEdPfzoK2iMF2GeWKudLLzUXz+6GJcdZ/TMcs2k= github.com/osmosis-labs/bech32-ibc v0.3.0-rc1/go.mod h1:X5/FZHMPL+B3ufuVyY2/koxVjd4hIwyTLjYP1DZwppQ= github.com/osmosis-labs/cosmos-sdk v0.45.1-0.20220524162204-830f277f8259 h1:myaa05LG9MtkqvE9jErU0qWZPENOwc7kiqmFLeG3cTs= @@ -1180,6 +1188,7 @@ github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa h1:0U2s5loxr github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/securego/gosec/v2 v2.11.0 h1:+PDkpzR41OI2jrw1q6AdXZCbsNGNGT7pQjal0H0cArI= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= @@ -1651,7 +1660,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1687,12 +1695,15 @@ golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2029,8 +2040,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 18ea4a915315b8de5e65e1dcc601cf3b95f77f47 Mon Sep 17 00:00:00 2001 From: Mauro Lacy Date: Wed, 25 May 2022 16:45:13 +0200 Subject: [PATCH 14/26] Create denom wasm bindings (#1563) * Add CreatDenom message * Add create denom tests * Update osmo_reflect to latest version * Add CreateDenom message tests * Fail mint of denom does not exist * Adapt mint tests to new Mint contract Co-authored-by: Dev Ojha --- app/wasm/bindings/msg.go | 18 +++++-- app/wasm/message_plugin.go | 38 ++++++++++----- app/wasm/test/custom_msg_test.go | 50 +++++++++++++++++++- app/wasm/test/messages_test.go | 70 ++++++++++++++++++++++++++++ app/wasm/testdata/osmo_reflect.wasm | Bin 280580 -> 281948 bytes app/wasm/testdata/version.txt | 2 +- 6 files changed, 160 insertions(+), 18 deletions(-) diff --git a/app/wasm/bindings/msg.go b/app/wasm/bindings/msg.go index fbb20386bdb..182c0607283 100644 --- a/app/wasm/bindings/msg.go +++ b/app/wasm/bindings/msg.go @@ -3,16 +3,26 @@ package wasmbindings import sdk "github.com/cosmos/cosmos-sdk/types" type OsmosisMsg struct { - /// Contracts can mint native tokens that have an auto-generated denom - /// namespaced under the contract's address. A contract may create any number - /// of independent sub-denoms. + /// Contracts can create denoms, namespaced under the contract's address. + //A contract may create any number of independent sub-denoms. + CreateDenom *CreateDenom `json:"create_denom,omitempty"` + /// Contracts can mint native tokens for an existing denom + /// namespaced under the contract's address. MintTokens *MintTokens `json:"mint_tokens,omitempty"` /// Swap over one or more pools Swap *SwapMsg `json:"swap,omitempty"` } +type CreateDenom struct { + /// Sub_denoms (nonces) are validated as part of the full denomination. + /// Can be up to 128 - prefix length (currently 7) - bech32 address length (4 (osmo) + 39) - number of separators (2) = + /// 76 "alphanumeric" (https://github.com/cosmos/cosmos-sdk/blob/2646b474c7beb0c93d4fafd395ef345f41afc251/types/coin.go#L677) + /// characters long. + /// Empty sub-denoms are valid. The token will then be prefix + contract address, i.e. "factory//" + SubDenom string `json:"sub_denom"` +} + type MintTokens struct { - /// Must be 2-32 alphanumeric characters SubDenom string `json:"sub_denom"` Amount sdk.Int `json:"amount"` Recipient string `json:"recipient"` diff --git a/app/wasm/message_plugin.go b/app/wasm/message_plugin.go index 24cbb27d1e1..609c260a9e2 100644 --- a/app/wasm/message_plugin.go +++ b/app/wasm/message_plugin.go @@ -39,12 +39,15 @@ var _ wasmkeeper.Messenger = (*CustomMessenger)(nil) func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { if msg.Custom != nil { - // only handle the happy path where this is really minting / swapping ... + // only handle the happy path where this is really creating / minting / swapping ... // leave everything else for the wrapped version var contractMsg wasmbindings.OsmosisMsg if err := json.Unmarshal(msg.Custom, &contractMsg); err != nil { return nil, nil, sdkerrors.Wrap(err, "osmosis msg") } + if contractMsg.CreateDenom != nil { + return m.createDenom(ctx, contractAddr, contractMsg.CreateDenom) + } if contractMsg.MintTokens != nil { return m.mintTokens(ctx, contractAddr, contractMsg.MintTokens) } @@ -55,6 +58,29 @@ func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } +func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *wasmbindings.CreateDenom) ([]sdk.Event, [][]byte, error) { + err := PerformCreateDenom(m.tokenFactory, m.bank, ctx, contractAddr, createDenom) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform create denom") + } + return nil, nil, nil +} + +func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *wasmbindings.CreateDenom) error { + if createDenom == nil { + return wasmvmtypes.InvalidRequest{Err: "create denom null create denom"} + } + + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + + // Create denom + _, err := msgServer.CreateDenom(sdk.WrapSDKContext(ctx), tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.SubDenom)) + if err != nil { + return sdkerrors.Wrap(err, "creating denom") + } + return nil +} + func (m *CustomMessenger) mintTokens(ctx sdk.Context, contractAddr sdk.AccAddress, mint *wasmbindings.MintTokens) ([]sdk.Event, [][]byte, error) { err := PerformMint(m.tokenFactory, m.bank, ctx, contractAddr, mint) if err != nil { @@ -88,16 +114,6 @@ func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - // Check if denom already exists - _, found := b.GetDenomMetaData(ctx, denom) - if !found { - // Create denom - _, err := msgServer.CreateDenom(sdk.WrapSDKContext(ctx), tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), mint.SubDenom)) - if err != nil { - return sdkerrors.Wrap(err, "creating token for mint") - } - } - // Mint through token factory / message server _, err = msgServer.Mint(sdk.WrapSDKContext(ctx), tokenfactorytypes.NewMsgMint(contractAddr.String(), coin)) if err != nil { diff --git a/app/wasm/test/custom_msg_test.go b/app/wasm/test/custom_msg_test.go index 6eb6ea42945..57297e15274 100644 --- a/app/wasm/test/custom_msg_test.go +++ b/app/wasm/test/custom_msg_test.go @@ -2,6 +2,7 @@ package wasm import ( "encoding/json" + "fmt" "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" "testing" @@ -15,6 +16,37 @@ import ( wasmbindings "github.com/osmosis-labs/osmosis/v7/app/wasm/bindings" ) +func TestCreateDenomMsg(t *testing.T) { + creator := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, creator) + + lucky := RandomAccountAddress() + reflect := instantiateReflectContract(t, ctx, osmosis, lucky) + require.NotEmpty(t, reflect) + + // Fund reflect contract with 100 base denom creation fees + reflectAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, reflect, reflectAmount) + + msg := wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ + SubDenom: "SUN", + }} + err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + + // query the denom and see if it matches + query := wasmbindings.OsmosisQuery{ + FullDenom: &wasmbindings.FullDenom{ + Contract: reflect.String(), + SubDenom: "SUN", + }, + } + resp := wasmbindings.FullDenomResponse{} + queryCustom(t, ctx, osmosis, reflect, query, &resp) + + require.Equal(t, resp.Denom, fmt.Sprintf("factory/%s/SUN", reflect.String())) +} + func TestMintMsg(t *testing.T) { creator := RandomAccountAddress() osmosis, ctx := SetupCustomApp(t, creator) @@ -31,14 +63,21 @@ func TestMintMsg(t *testing.T) { balances := osmosis.BankKeeper.GetAllBalances(ctx, lucky) require.Empty(t, balances) + // Create denom for minting + msg := wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ + SubDenom: "SUN", + }} + err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + amount, ok := sdk.NewIntFromString("808010808") require.True(t, ok) - msg := wasmbindings.OsmosisMsg{MintTokens: &wasmbindings.MintTokens{ + msg = wasmbindings.OsmosisMsg{MintTokens: &wasmbindings.MintTokens{ SubDenom: "SUN", Amount: amount, Recipient: lucky.String(), }} - err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) balances = osmosis.BankKeeper.GetAllBalances(ctx, lucky) @@ -82,6 +121,13 @@ func TestMintMsg(t *testing.T) { require.Equal(t, resp.Denom, coin.Denom) // now mint another amount / denom + // create it first + msg = wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ + SubDenom: "MOON", + }} + err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) + require.NoError(t, err) + amount = amount.SubRaw(1) msg = wasmbindings.OsmosisMsg{MintTokens: &wasmbindings.MintTokens{ SubDenom: "MOON", diff --git a/app/wasm/test/messages_test.go b/app/wasm/test/messages_test.go index bd50b8c6594..c652f9a360c 100644 --- a/app/wasm/test/messages_test.go +++ b/app/wasm/test/messages_test.go @@ -13,6 +13,55 @@ import ( "github.com/stretchr/testify/require" ) +func TestCreateDenom(t *testing.T) { + actor := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, actor) + + // Fund actor with 100 base denom creation fees + actorAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, actor, actorAmount) + + specs := map[string]struct { + createDenom *wasmbindings.CreateDenom + expErr bool + }{ + "valid sub-denom": { + createDenom: &wasmbindings.CreateDenom{ + SubDenom: "MOON", + }, + }, + "empty sub-denom": { + createDenom: &wasmbindings.CreateDenom{ + SubDenom: "", + }, + expErr: false, + }, + "invalid sub-denom": { + createDenom: &wasmbindings.CreateDenom{ + SubDenom: "sub-denom_2", + }, + expErr: true, + }, + "null create denom": { + createDenom: nil, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // when + gotErr := wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, spec.createDenom) + // then + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + }) + } + +} + func TestMint(t *testing.T) { actor := RandomAccountAddress() osmosis, ctx := SetupCustomApp(t, actor) @@ -21,6 +70,19 @@ func TestMint(t *testing.T) { actorAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) fundAccount(t, ctx, osmosis, actor, actorAmount) + // Create denoms for valid mint tests + validDenom := wasmbindings.CreateDenom{ + SubDenom: "MOON", + } + err := wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, &validDenom) + require.NoError(t, err) + + emptyDenom := wasmbindings.CreateDenom{ + SubDenom: "", + } + err = wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, &emptyDenom) + require.NoError(t, err) + lucky := RandomAccountAddress() // lucky was broke @@ -49,6 +111,14 @@ func TestMint(t *testing.T) { }, expErr: false, }, + "nonexistent sub-denom": { + mint: &wasmbindings.MintTokens{ + SubDenom: "SUN", + Amount: amount, + Recipient: lucky.String(), + }, + expErr: true, + }, "invalid sub-denom": { mint: &wasmbindings.MintTokens{ SubDenom: "sub-denom_2", diff --git a/app/wasm/testdata/osmo_reflect.wasm b/app/wasm/testdata/osmo_reflect.wasm index 594988bb0a943c20dee1d5aca5b6a8efb2e0ce72..7315ce0614767527e1d2dd681320721537fd5258 100644 GIT binary patch delta 101002 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 diff --git a/app/wasm/testdata/version.txt b/app/wasm/testdata/version.txt index 60f63432822..195fba2f9a7 100644 --- a/app/wasm/testdata/version.txt +++ b/app/wasm/testdata/version.txt @@ -1 +1 @@ -v0.6.0 +v0.6.0 23016dce From 31a28e320bb6f1397569de7a2e5ca4f31c2125cf Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 26 May 2022 00:07:35 +0200 Subject: [PATCH 15/26] Move tokenfactory genesis logic into keeper package, de-expose setAuthorityMetadata (#1581) Subcomponent of #1402 ## What is the purpose of the change Move tokenfactory genesis logic into keeper package, de-expose setAuthorityMetadata. Lets us improve API abstractions / start making more keeper methods private. ## Testing and Verifying This change is a trivial rework / code cleanup without any test coverage. ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? not applicable This would merit changelog entries for other modules, as they would be in a release and this change would be API breaking for them. --- x/tokenfactory/keeper/admins.go | 6 +++--- x/tokenfactory/keeper/createdenom.go | 2 +- x/tokenfactory/{ => keeper}/genesis.go | 9 ++++----- x/tokenfactory/{ => keeper}/genesis_test.go | 7 +++---- x/tokenfactory/module.go | 4 ++-- 5 files changed, 13 insertions(+), 15 deletions(-) rename x/tokenfactory/{ => keeper}/genesis.go (80%) rename x/tokenfactory/{ => keeper}/genesis_test.go (82%) diff --git a/x/tokenfactory/keeper/admins.go b/x/tokenfactory/keeper/admins.go index d8958019c2c..3a62d2304ed 100644 --- a/x/tokenfactory/keeper/admins.go +++ b/x/tokenfactory/keeper/admins.go @@ -19,8 +19,8 @@ func (k Keeper) GetAuthorityMetadata(ctx sdk.Context, denom string) (types.Denom return metadata, nil } -// SetAuthorityMetadata stores authority metadata for a specific denom -func (k Keeper) SetAuthorityMetadata(ctx sdk.Context, denom string, metadata types.DenomAuthorityMetadata) error { +// setAuthorityMetadata stores authority metadata for a specific denom +func (k Keeper) setAuthorityMetadata(ctx sdk.Context, denom string, metadata types.DenomAuthorityMetadata) error { err := metadata.Validate() if err != nil { return err @@ -45,5 +45,5 @@ func (k Keeper) setAdmin(ctx sdk.Context, denom string, admin string) error { metadata.Admin = admin - return k.SetAuthorityMetadata(ctx, denom, metadata) + return k.setAuthorityMetadata(ctx, denom, metadata) } diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go index 1411b8c7849..e669893ea3a 100644 --- a/x/tokenfactory/keeper/createdenom.go +++ b/x/tokenfactory/keeper/createdenom.go @@ -44,7 +44,7 @@ func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, denomNonce stri authorityMetadata := types.DenomAuthorityMetadata{ Admin: creatorAddr, } - err = k.SetAuthorityMetadata(ctx, denom, authorityMetadata) + err = k.setAuthorityMetadata(ctx, denom, authorityMetadata) if err != nil { return "", err } diff --git a/x/tokenfactory/genesis.go b/x/tokenfactory/keeper/genesis.go similarity index 80% rename from x/tokenfactory/genesis.go rename to x/tokenfactory/keeper/genesis.go index 1d6a7d8fab7..bace751ceab 100644 --- a/x/tokenfactory/genesis.go +++ b/x/tokenfactory/keeper/genesis.go @@ -1,15 +1,14 @@ -package tokenfactory +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/keeper" "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" ) // InitGenesis initializes the tokenfactory module's state from a provided genesis // state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { k.CreateModuleAccount(ctx) if genState.Params.DenomCreationFee == nil { @@ -26,7 +25,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) if err != nil { panic(err) } - err = k.SetAuthorityMetadata(ctx, genDenom.GetDenom(), genDenom.GetAuthorityMetadata()) + err = k.setAuthorityMetadata(ctx, genDenom.GetDenom(), genDenom.GetAuthorityMetadata()) if err != nil { panic(err) } @@ -34,7 +33,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) } // ExportGenesis returns the tokenfactory module's exported genesis. -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { genDenoms := []types.GenesisDenom{} iterator := k.GetAllDenomsIterator(ctx) defer iterator.Close() diff --git a/x/tokenfactory/genesis_test.go b/x/tokenfactory/keeper/genesis_test.go similarity index 82% rename from x/tokenfactory/genesis_test.go rename to x/tokenfactory/keeper/genesis_test.go index 3749c043ccd..870da69c390 100644 --- a/x/tokenfactory/genesis_test.go +++ b/x/tokenfactory/keeper/genesis_test.go @@ -1,4 +1,4 @@ -package tokenfactory_test +package keeper_test import ( "testing" @@ -9,7 +9,6 @@ import ( simapp "github.com/osmosis-labs/osmosis/v7/app" appparams "github.com/osmosis-labs/osmosis/v7/app/params" - "github.com/osmosis-labs/osmosis/v7/x/tokenfactory" "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" ) @@ -35,8 +34,8 @@ func TestGenesis(t *testing.T) { app := simapp.Setup(false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - tokenfactory.InitGenesis(ctx, *app.TokenFactoryKeeper, genesisState) - exportedGenesis := tokenfactory.ExportGenesis(ctx, *app.TokenFactoryKeeper) + app.TokenFactoryKeeper.InitGenesis(ctx, genesisState) + exportedGenesis := app.TokenFactoryKeeper.ExportGenesis(ctx) require.NotNil(t, exportedGenesis) require.Equal(t, genesisState, *exportedGenesis) } diff --git a/x/tokenfactory/module.go b/x/tokenfactory/module.go index ae92e789b94..9e8afe33b08 100644 --- a/x/tokenfactory/module.go +++ b/x/tokenfactory/module.go @@ -146,14 +146,14 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.Ra var genState types.GenesisState cdc.MustUnmarshalJSON(gs, &genState) - InitGenesis(ctx, am.keeper, genState) + am.keeper.InitGenesis(ctx, genState) return []abci.ValidatorUpdate{} } // ExportGenesis returns the capability module's exported genesis state as raw JSON bytes. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - genState := ExportGenesis(ctx, am.keeper) + genState := am.keeper.ExportGenesis(ctx) return cdc.MustMarshalJSON(genState) } From a49c6c88f1315a7bcfcf662977aa5c5180fa2b4b Mon Sep 17 00:00:00 2001 From: Xiangan He <76530366+xBalbinus@users.noreply.github.com> Date: Thu, 26 May 2022 09:34:16 -0400 Subject: [PATCH 16/26] Stableswap Test additions (#1524) * nonlinear stableswap tests * gofmt * fixed nonlinear test * fixed nonlinear test * added docs on calculations, added back more linear tests * gofmt * Update based on feedback Co-authored-by: Roman * test combination * test combination * gofmt * added additive tolerance of 1 to fix multiplicative tolerance tests * added additive tolerance of 1 to fix multiplicative tolerance tests * fixed multiplicative tolerance tests Co-authored-by: Xiangan He Co-authored-by: Matt, Park <45252226+mattverse@users.noreply.github.com> Co-authored-by: Roman Co-authored-by: Aleksandr Bezobchuk --- osmoutils/binary_search.go | 2 +- osmoutils/binary_search_test.go | 124 +++++++++----------------------- 2 files changed, 36 insertions(+), 90 deletions(-) diff --git a/osmoutils/binary_search.go b/osmoutils/binary_search.go index 18c2a0866e0..e7985e49a1e 100644 --- a/osmoutils/binary_search.go +++ b/osmoutils/binary_search.go @@ -42,7 +42,7 @@ func (e ErrTolerance) Compare(expected sdk.Int, actual sdk.Int) int { } // Check additive tolerance equations - if !e.AdditiveTolerance.IsNil() && !e.AdditiveTolerance.IsZero() { + if !e.AdditiveTolerance.IsNil() { if diff.GT(e.AdditiveTolerance) { return comparisonSign } diff --git a/osmoutils/binary_search_test.go b/osmoutils/binary_search_test.go index c125a17ba3e..365ccb6d482 100644 --- a/osmoutils/binary_search_test.go +++ b/osmoutils/binary_search_test.go @@ -13,73 +13,16 @@ func TestBinarySearch(t *testing.T) { lineF := func(a sdk.Int) (sdk.Int, error) { return a, nil } - noErrTolerance := ErrTolerance{AdditiveTolerance: sdk.ZeroInt()} - tests := []struct { - f func(sdk.Int) (sdk.Int, error) - lowerbound sdk.Int - upperbound sdk.Int - targetOutput sdk.Int - errTolerance ErrTolerance - maxIterations int - - expectedSolvedInput sdk.Int - expectErr bool - }{ - {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 51, sdk.NewInt(1 + (1 << 25)), false}, - {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 10, sdk.Int{}, true}, - } - - for _, tc := range tests { - actualSolvedInput, err := BinarySearch(tc.f, tc.lowerbound, tc.upperbound, tc.targetOutput, tc.errTolerance, tc.maxIterations) - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.True(sdk.IntEq(t, tc.expectedSolvedInput, actualSolvedInput)) - } - } -} - -func TestBinarySearchNonlinear(t *testing.T) { - // straight line function that returns input. Simplest to binary search on, - // binary search directly reveals one bit of the answer in each iteration with this function. - lineF := func(a sdk.Int) (sdk.Int, error) { - return a, nil - } - noErrTolerance := ErrTolerance{AdditiveTolerance: sdk.ZeroInt()} - tests := []struct { - f func(sdk.Int) (sdk.Int, error) - lowerbound sdk.Int - upperbound sdk.Int - targetOutput sdk.Int - errTolerance ErrTolerance - maxIterations int - - expectedSolvedInput sdk.Int - expectErr bool - }{ - {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 51, sdk.NewInt(1 + (1 << 25)), false}, - {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 10, sdk.Int{}, true}, - } - - for _, tc := range tests { - actualSolvedInput, err := BinarySearch(tc.f, tc.lowerbound, tc.upperbound, tc.targetOutput, tc.errTolerance, tc.maxIterations) - if tc.expectErr { - require.Error(t, err) - } else { - require.NoError(t, err) - require.True(sdk.IntEq(t, tc.expectedSolvedInput, actualSolvedInput)) - } - } -} - -func TestBinarySearchNonlinearNonzero(t *testing.T) { - // non-linear function that returns input. Simplest to binary search on, - // binary search directly reveals one bit of the answer in each iteration with this function. - lineF := func(a sdk.Int) (sdk.Int, error) { - return a, nil + expF := func(a sdk.Int) (sdk.Int, error) { + calculation := sdk.Dec(a) + result := calculation.Power(3) + output := sdk.Int(result) + return output, nil } noErrTolerance := ErrTolerance{AdditiveTolerance: sdk.ZeroInt()} + testErrToleranceAdditive := ErrTolerance{AdditiveTolerance: sdk.NewInt(1 << 20)} + testErrToleranceMultiplicative := ErrTolerance{AdditiveTolerance: sdk.ZeroInt(), MultiplicativeTolerance: sdk.NewDec(10)} + testErrToleranceBoth := ErrTolerance{AdditiveTolerance: sdk.NewInt(1 << 20), MultiplicativeTolerance: sdk.NewDec(1 << 3)} tests := []struct { f func(sdk.Int) (sdk.Int, error) lowerbound sdk.Int @@ -90,9 +33,24 @@ func TestBinarySearchNonlinearNonzero(t *testing.T) { expectedSolvedInput sdk.Int expectErr bool + // This binary searches inputs to a monotonic increasing function F + // We stop when the answer is within error bounds stated by errTolerance + // First, (lowerbound + upperbound) / 2 becomes the current estimate. + // A current output is also defined as f(current estimate). In this case f is lineF + // We then compare the current output with the target output to see if it's within error tolerance bounds. If not, continue binary searching by iterating. + // If it is, we return current output + // Additive error bounds are solid addition / subtraction bounds to error, while multiplicative bounds take effect after dividing by the minimum between the two compared numbers. }{ {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 51, sdk.NewInt(1 + (1 << 25)), false}, {lineF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 10, sdk.Int{}, true}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 51, sdk.NewInt(322539792367616), false}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), noErrTolerance, 10, sdk.Int{}, true}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt((1 << 15)), testErrToleranceAdditive, 51, sdk.NewInt(1 << 46), false}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt((1 << 30)), testErrToleranceAdditive, 10, sdk.Int{}, true}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), testErrToleranceMultiplicative, 51, sdk.NewInt(322539792367616), false}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt(1 + (1 << 25)), testErrToleranceMultiplicative, 10, sdk.Int{}, true}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt((1 << 15)), testErrToleranceBoth, 51, sdk.NewInt(1 << 45), false}, + {expF, sdk.ZeroInt(), sdk.NewInt(1 << 50), sdk.NewInt((1 << 30)), testErrToleranceBoth, 10, sdk.Int{}, true}, } for _, tc := range tests { @@ -108,6 +66,9 @@ func TestBinarySearchNonlinearNonzero(t *testing.T) { func TestErrTolerance_Compare(t *testing.T) { ZeroErrTolerance := ErrTolerance{AdditiveTolerance: sdk.ZeroInt(), MultiplicativeTolerance: sdk.Dec{}} + NonZeroErrAdditive := ErrTolerance{AdditiveTolerance: sdk.NewInt(10), MultiplicativeTolerance: sdk.Dec{}} + NonZeroErrMultiplicative := ErrTolerance{AdditiveTolerance: sdk.ZeroInt(), MultiplicativeTolerance: sdk.NewDec(10)} + NonZeroErrBoth := ErrTolerance{AdditiveTolerance: sdk.NewInt(1), MultiplicativeTolerance: sdk.NewDec(10)} tests := []struct { name string tol ErrTolerance @@ -119,30 +80,15 @@ func TestErrTolerance_Compare(t *testing.T) { {"0 tolerance: <", ZeroErrTolerance, sdk.NewInt(1000), sdk.NewInt(1001), -1}, {"0 tolerance: =", ZeroErrTolerance, sdk.NewInt(1001), sdk.NewInt(1001), 0}, {"0 tolerance: >", ZeroErrTolerance, sdk.NewInt(1002), sdk.NewInt(1001), 1}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := tt.tol.Compare(tt.input, tt.reference); got != tt.expectedCompareResult { - t.Errorf("ErrTolerance.Compare() = %v, want %v", got, tt.expectedCompareResult) - } - }) - } -} - -func TestErrToleranceNonzero_Compare(t *testing.T) { - // Nonzero error tolerance test - NonZeroErrTolerance := ErrTolerance{AdditiveTolerance: sdk.NewInt(10), MultiplicativeTolerance: sdk.Dec{}} - tests := []struct { - name string - tol ErrTolerance - input sdk.Int - reference sdk.Int - - expectedCompareResult int - }{ - {"Nonzero tolerance: <", NonZeroErrTolerance, sdk.NewInt(420), sdk.NewInt(1001), -1}, - {"Nonzero tolerance: =", NonZeroErrTolerance, sdk.NewInt(1002), sdk.NewInt(1001), 0}, - {"Nonzero tolerance: >", NonZeroErrTolerance, sdk.NewInt(1230), sdk.NewInt(1001), 1}, + {"Nonzero additive tolerance: <", NonZeroErrAdditive, sdk.NewInt(420), sdk.NewInt(1001), -1}, + {"Nonzero additive tolerance: =", NonZeroErrAdditive, sdk.NewInt(1011), sdk.NewInt(1001), 0}, + {"Nonzero additive tolerance: >", NonZeroErrAdditive, sdk.NewInt(1230), sdk.NewInt(1001), 1}, + {"Nonzero multiplicative tolerance: <", NonZeroErrMultiplicative, sdk.NewInt(1000), sdk.NewInt(1001), -1}, + {"Nonzero multiplicative tolerance: =", NonZeroErrMultiplicative, sdk.NewInt(1001), sdk.NewInt(1001), 0}, + {"Nonzero multiplicative tolerance: >", NonZeroErrMultiplicative, sdk.NewInt(1002), sdk.NewInt(1001), 1}, + {"Nonzero both tolerance: <", NonZeroErrBoth, sdk.NewInt(990), sdk.NewInt(1001), -1}, + {"Nonzero both tolerance: =", NonZeroErrBoth, sdk.NewInt(1002), sdk.NewInt(1001), 0}, + {"Nonzero both tolerance: >", NonZeroErrBoth, sdk.NewInt(1011), sdk.NewInt(1001), 1}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 20835e624b40273313fb2261e29d4617de526fb9 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 26 May 2022 15:51:36 +0200 Subject: [PATCH 17/26] Add CI cache (#1583) ## What is the purpose of the change Try adding a cache for go build data, to help speedup CI. Copied from mvdan here: https://github.com/mvdan/github-actions-golang/blob/master/.github/workflows/test.yml cref #1578 ## Brief Changelog - Adds go cache to CI ## Testing and Verifying We will need to keep an eye on if there are edge cases that make this not work / give incorrect results, or things don't speedup. A bit hard to know this without it being in place though. ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? yes - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no (I don't think CI is in scope) - How is the feature or change documented? We need to figure out a strategy for if/how we want to document CI. --- .github/workflows/lint.yml | 18 +++++++++++ .github/workflows/test.yml | 32 +++++++++++++++++++ app/app.go | 2 +- app/modules.go | 64 ++++++++++++++++++++------------------ 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index adba0ad3bca..5a21f444522 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,6 +20,24 @@ jobs: **/**.go go.mod go.sum + - name: Get data from build cache + uses: actions/cache@v2 + with: + # In order: + # * Module download cache + # * Linter cache (Linux) + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/golangci-lint + ~/.cache/go-build + ~/Library/Caches/go-build + ~\AppData\Local\go-build + key: ${{ runner.os }}-go-linter-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-linter-${{ matrix.go-version }}- - name: Run golangci-lint run: make lint if: env.GIT_DIFF diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6981e851aa8..9dfe7f4f18e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,22 @@ jobs: go-version: 1.18 - name: Display go version run: go version + - name: Get data from build cache + uses: actions/cache@v2 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + ~\AppData\Local\go-build + key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-${{ matrix.go-version }}- - name: Run all tests run: | make test-cover @@ -59,6 +75,22 @@ jobs: **/**.go go.mod go.sum + - name: Get data from build cache + uses: actions/cache@v2 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + # * Build cache (Windows) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + ~\AppData\Local\go-build + key: ${{ runner.os }}-go-docker-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-docker-${{ matrix.go-version }}- - name: Build Docker Image run: | make docker-build-debug diff --git a/app/app.go b/app/app.go index f792b6b614b..b32481a64cb 100644 --- a/app/app.go +++ b/app/app.go @@ -233,7 +233,7 @@ func NewOsmosisApp( // NOTE: Capability module must occur first so that it can initialize any capabilities // so that other modules that want to create or claim capabilities afterwards in InitChain // can do so safely. - app.mm.SetOrderInitGenesis(modulesOrderInitGenesis...) + app.mm.SetOrderInitGenesis(OrderInitGenesis(app.mm.ModuleNames())...) app.mm.RegisterInvariants(app.CrisisKeeper) app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino) diff --git a/app/modules.go b/app/modules.go index 6260f85f255..ff1775f2927 100644 --- a/app/modules.go +++ b/app/modules.go @@ -194,37 +194,39 @@ func OrderEndBlockers(allModuleNames []string) []string { return ord.TotalOrdering() } -// modulesOrderInitGenesis returns module names in order for init genesis calls. -var modulesOrderInitGenesis = []string{ - capabilitytypes.ModuleName, - authtypes.ModuleName, - banktypes.ModuleName, - distrtypes.ModuleName, - stakingtypes.ModuleName, - slashingtypes.ModuleName, - govtypes.ModuleName, - minttypes.ModuleName, - crisistypes.ModuleName, - ibchost.ModuleName, - icatypes.ModuleName, - gammtypes.ModuleName, - txfeestypes.ModuleName, - genutiltypes.ModuleName, - evidencetypes.ModuleName, - paramstypes.ModuleName, - upgradetypes.ModuleName, - vestingtypes.ModuleName, - ibctransfertypes.ModuleName, - bech32ibctypes.ModuleName, // comes after ibctransfertypes - poolincentivestypes.ModuleName, - superfluidtypes.ModuleName, - tokenfactorytypes.ModuleName, - incentivestypes.ModuleName, - epochstypes.ModuleName, - lockuptypes.ModuleName, - authz.ModuleName, - // wasm after ibc transfer - wasm.ModuleName, +// OrderInitGenesis returns module names in order for init genesis calls. +func OrderInitGenesis(allModuleNames []string) []string { + return []string{ + capabilitytypes.ModuleName, + authtypes.ModuleName, + banktypes.ModuleName, + distrtypes.ModuleName, + stakingtypes.ModuleName, + slashingtypes.ModuleName, + govtypes.ModuleName, + minttypes.ModuleName, + crisistypes.ModuleName, + ibchost.ModuleName, + icatypes.ModuleName, + gammtypes.ModuleName, + txfeestypes.ModuleName, + genutiltypes.ModuleName, + evidencetypes.ModuleName, + paramstypes.ModuleName, + upgradetypes.ModuleName, + vestingtypes.ModuleName, + ibctransfertypes.ModuleName, + bech32ibctypes.ModuleName, // comes after ibctransfertypes + poolincentivestypes.ModuleName, + superfluidtypes.ModuleName, + tokenfactorytypes.ModuleName, + incentivestypes.ModuleName, + epochstypes.ModuleName, + lockuptypes.ModuleName, + authz.ModuleName, + // wasm after ibc transfer + wasm.ModuleName, + } } // simulationModules returns modules for simulation manager From 1edc59c6d02b716dc3f10dbdb3a706466652c84b Mon Sep 17 00:00:00 2001 From: Xiangan He <76530366+xBalbinus@users.noreply.github.com> Date: Thu, 26 May 2022 10:59:04 -0400 Subject: [PATCH 18/26] Docs fixes (#1566) * docbuild automation * tokenmodule spec docs * sidebar fixes * formatting fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * superlinter fixes * docs fixes * docs fixes * docs fixes * docs fixes * docs fixes * markdown lint fixes + disabled M024 * markdown lint fixes + disabled M024 * markdown lint fixes + disabled M024 * markdown lint fixes + disabled M024 * markdown lint fixes + disabled M024 * markdownlint yaml fixes * markdownlint yaml fixes * markdownlint yaml fixes Co-authored-by: Xiangan He Co-authored-by: Daniel Farina --- .markdownlint.yml | 1 + x/epochs/spec/01_concepts.md | 5 - x/epochs/spec/02_state.md | 48 -- x/epochs/spec/03_events.md | 16 - x/epochs/spec/04_keeper.md | 21 - x/epochs/spec/05_hooks.md | 29 -- x/epochs/spec/06_queries.md | 13 - x/epochs/spec/07_future_improvements.md | 26 -- x/epochs/spec/README.md | 187 +++++++- x/gamm/spec/01_concepts.md | 76 ---- x/gamm/spec/02_pool_params.md | 31 -- x/gamm/spec/03_msgs.md | 43 -- x/gamm/spec/04_params.md | 12 - x/gamm/spec/05_queries_transactions.md | 328 -------------- x/gamm/spec/0x_weights.md | 26 -- x/gamm/spec/README.md | 553 ++++++++++++++++++++++-- x/incentives/spec/01_concepts.md | 10 - x/incentives/spec/02_state.md | 88 ---- x/incentives/spec/03_messages.md | 41 -- x/incentives/spec/04_events.md | 42 -- x/incentives/spec/05_hooks.md | 14 - x/incentives/spec/06_queries.md | 34 -- x/incentives/spec/07_params.md | 12 - x/incentives/spec/README.md | 236 +++++++++- x/lockup/spec/01_concepts.md | 8 - x/lockup/spec/02_state.md | 160 ------- x/lockup/spec/03_messages.md | 63 --- x/lockup/spec/04_events.md | 64 --- x/lockup/spec/05_keeper.md | 88 ---- x/lockup/spec/06_hooks.md | 20 - x/lockup/spec/07_queries.md | 48 -- x/lockup/spec/08_params.md | 15 - x/lockup/spec/09_endblocker.md | 31 -- x/lockup/spec/README.md | 490 ++++++++++++++++++++- x/mint/spec/01_concepts.md | 25 -- x/mint/spec/02_state.md | 44 -- x/mint/spec/03_end_epoch.md | 34 -- x/mint/spec/04_params.md | 42 -- x/mint/spec/05_events.md | 17 - x/mint/spec/README.md | 162 ++++++- x/pool-incentives/spec/01_concepts.md | 18 - x/pool-incentives/spec/02_state.md | 43 -- x/pool-incentives/spec/03_gov.md | 52 --- x/pool-incentives/spec/README.md | 109 ++++- 44 files changed, 1659 insertions(+), 1766 deletions(-) delete mode 100644 x/epochs/spec/01_concepts.md delete mode 100644 x/epochs/spec/02_state.md delete mode 100644 x/epochs/spec/03_events.md delete mode 100644 x/epochs/spec/04_keeper.md delete mode 100644 x/epochs/spec/05_hooks.md delete mode 100644 x/epochs/spec/06_queries.md delete mode 100644 x/epochs/spec/07_future_improvements.md delete mode 100644 x/gamm/spec/01_concepts.md delete mode 100644 x/gamm/spec/02_pool_params.md delete mode 100644 x/gamm/spec/03_msgs.md delete mode 100644 x/gamm/spec/04_params.md delete mode 100644 x/gamm/spec/05_queries_transactions.md delete mode 100644 x/gamm/spec/0x_weights.md delete mode 100644 x/incentives/spec/01_concepts.md delete mode 100644 x/incentives/spec/02_state.md delete mode 100644 x/incentives/spec/03_messages.md delete mode 100644 x/incentives/spec/04_events.md delete mode 100644 x/incentives/spec/05_hooks.md delete mode 100644 x/incentives/spec/06_queries.md delete mode 100644 x/incentives/spec/07_params.md delete mode 100644 x/lockup/spec/01_concepts.md delete mode 100644 x/lockup/spec/02_state.md delete mode 100644 x/lockup/spec/03_messages.md delete mode 100644 x/lockup/spec/04_events.md delete mode 100644 x/lockup/spec/05_keeper.md delete mode 100644 x/lockup/spec/06_hooks.md delete mode 100644 x/lockup/spec/07_queries.md delete mode 100644 x/lockup/spec/08_params.md delete mode 100644 x/lockup/spec/09_endblocker.md delete mode 100644 x/mint/spec/01_concepts.md delete mode 100644 x/mint/spec/02_state.md delete mode 100644 x/mint/spec/03_end_epoch.md delete mode 100644 x/mint/spec/04_params.md delete mode 100644 x/mint/spec/05_events.md delete mode 100644 x/pool-incentives/spec/01_concepts.md delete mode 100644 x/pool-incentives/spec/02_state.md delete mode 100644 x/pool-incentives/spec/03_gov.md diff --git a/.markdownlint.yml b/.markdownlint.yml index 847d67f6a0f..20408908c89 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,5 +1,6 @@ # Default state for all rules default: true +MD024: false MD003: # Heading style style: "atx" diff --git a/x/epochs/spec/01_concepts.md b/x/epochs/spec/01_concepts.md deleted file mode 100644 index 08321050861..00000000000 --- a/x/epochs/spec/01_concepts.md +++ /dev/null @@ -1,5 +0,0 @@ -# Concepts - -The purpose of `epochs` module is to provide generalized epoch interface -to other modules so that they can easily implement epochs without -keeping own code for epochs. diff --git a/x/epochs/spec/02_state.md b/x/epochs/spec/02_state.md deleted file mode 100644 index 8565510c87f..00000000000 --- a/x/epochs/spec/02_state.md +++ /dev/null @@ -1,48 +0,0 @@ -# State - -Epochs module keeps `EpochInfo` objects and modify the information as -epochs info changes. Epochs are initialized as part of genesis -initialization, and modified on begin blockers or end blockers. - -## Epoch information type - -``` {.protobuf} -message EpochInfo { - string identifier = 1; - google.protobuf.Timestamp start_time = 2 [ - (gogoproto.stdtime) = true, - (gogoproto.nullable) = false, - (gogoproto.moretags) = "yaml:\"start_time\"" - ]; - google.protobuf.Duration duration = 3 [ - (gogoproto.nullable) = false, - (gogoproto.stdduration) = true, - (gogoproto.jsontag) = "duration,omitempty", - (gogoproto.moretags) = "yaml:\"duration\"" - ]; - int64 current_epoch = 4; - google.protobuf.Timestamp current_epoch_start_time = 5 [ - (gogoproto.stdtime) = true, - (gogoproto.nullable) = false, - (gogoproto.moretags) = "yaml:\"current_epoch_start_time\"" - ]; - bool epoch_counting_started = 6; - reserved 7; - int64 current_epoch_start_height = 8; -} -``` - -EpochInfo keeps `identifier`, `start_time`,`duration`, `current_epoch`, -`current_epoch_start_time`, `epoch_counting_started`, -`current_epoch_start_height`. - -1. `identifier` keeps epoch identification string. -2. `start_time` keeps epoch counting start time, if block time passes - `start_time`, `epoch_counting_started` is set. -3. `duration` keeps target epoch duration. -4. `current_epoch` keeps current active epoch number. -5. `current_epoch_start_time` keeps the start time of current epoch. -6. `epoch_number` is counted only when `epoch_counting_started` flag is - set. -7. `current_epoch_start_height` keeps the start block height of current - epoch. diff --git a/x/epochs/spec/03_events.md b/x/epochs/spec/03_events.md deleted file mode 100644 index 0db1865ea6b..00000000000 --- a/x/epochs/spec/03_events.md +++ /dev/null @@ -1,16 +0,0 @@ -# Events - -The `epochs` module emits the following events: - -## BeginBlocker - - Type Attribute Key Attribute Value - --------------; ---------------; -----------------; - epoch\_start epoch\_number {epoch\_number} - epoch\_start start\_time {start\_time} - -## EndBlocker - - Type Attribute Key Attribute Value - ------------; ---------------; -----------------; - epoch\_end epoch\_number {epoch\_number} diff --git a/x/epochs/spec/04_keeper.md b/x/epochs/spec/04_keeper.md deleted file mode 100644 index ce0220ec94a..00000000000 --- a/x/epochs/spec/04_keeper.md +++ /dev/null @@ -1,21 +0,0 @@ -# Keepers - -## Keeper functions - -Epochs keeper module provides utility functions to manage epochs. - -``` {.go} -// Keeper is the interface for lockup module keeper -type Keeper interface { - // GetEpochInfo returns epoch info by identifier - GetEpochInfo(ctx sdk.Context, identifier string) types.EpochInfo - // SetEpochInfo set epoch info - SetEpochInfo(ctx sdk.Context, epoch types.EpochInfo) - // DeleteEpochInfo delete epoch info - DeleteEpochInfo(ctx sdk.Context, identifier string) - // IterateEpochInfo iterate through epochs - IterateEpochInfo(ctx sdk.Context, fn func(index int64, epochInfo types.EpochInfo) (stop bool)) - // Get all epoch infos - AllEpochInfos(ctx sdk.Context) []types.EpochInfo -} -``` diff --git a/x/epochs/spec/05_hooks.md b/x/epochs/spec/05_hooks.md deleted file mode 100644 index 364ffb59fb0..00000000000 --- a/x/epochs/spec/05_hooks.md +++ /dev/null @@ -1,29 +0,0 @@ -# Hooks - - -``` {.go} - // the first block whose timestamp is after the duration is counted as the end of the epoch - AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) - // new epoch is next block of epoch end block - BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64) -``` - -## How modules receive hooks - -On hook receiver function of other modules, they need to filter -`epochIdentifier` and only do executions for only specific -epochIdentifier. Filtering epochIdentifier could be in `Params` of other -modules so that they can be modified by governance. Governance can -change epoch from `week` to `day` as their need. - -## Panic isolation - -If a given epoch hook panics, its state update is reverted, but we keep -proceeding through the remaining hooks. This allows more advanced epoch -logic to be used, without concern over state machine halting, or halting -subsequent modules. - -This does mean that if there is behavior you expect from a prior epoch -hook, and that epoch hook reverted, your hook may also have an issue. So -do keep in mind "what if a prior hook didn't get executed" in the safety -checks you consider for a new epoch hook. diff --git a/x/epochs/spec/06_queries.md b/x/epochs/spec/06_queries.md deleted file mode 100644 index 72f58208082..00000000000 --- a/x/epochs/spec/06_queries.md +++ /dev/null @@ -1,13 +0,0 @@ -# Queries - -Epochs module is providing below queries to check the module's state. - - -``` {.protobuf} -service Query { - // EpochInfos provide running epochInfos - rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) {} - // CurrentEpoch provide current epoch of specified identifier - rpc CurrentEpoch(QueryCurrentEpochRequest) returns (QueryCurrentEpochResponse) {} -} -``` diff --git a/x/epochs/spec/07_future_improvements.md b/x/epochs/spec/07_future_improvements.md deleted file mode 100644 index 0236d30e1d1..00000000000 --- a/x/epochs/spec/07_future_improvements.md +++ /dev/null @@ -1,26 +0,0 @@ -# Future Improvements - -## Lack point using this module - -In current design each epoch should be at least 2 blocks as start block -should be different from endblock. Because of this, each epoch time will -be `max(blocks_time x 2, epoch_duration)`. If epoch\_duration is set to -`1s`, and `block_time` is `5s`, actual epoch time should be `10s`. We -definitely recommend configure epoch\_duration as more than 2x -block\_time, to use this module correctly. If you enforce to set it to -1s, it's same as 10s - could make module logic invalid. - -TODO for postlaunch: We should see if we can architect things such that -the receiver doesn't have to do this filtering, and the epochs module -would pre-filter for them. - -## Block-time drifts problem - -This implementation has block time drift based on block time. For -instance, we have an epoch of 100 units that ends at t=100, if we have a -block at t=97 and a block at t=104 and t=110, this epoch ends at t=104. -And new epoch start at t=110. There are time drifts here, for around 1-2 -blocks time. It will slow down epochs. - -It's going to slow down epoch by 10-20s per week when epoch duration is -1 week. This should be resolved after launch. diff --git a/x/epochs/spec/README.md b/x/epochs/spec/README.md index 8e32d19e5fa..2f36d92d1c1 100644 --- a/x/epochs/spec/README.md +++ b/x/epochs/spec/README.md @@ -11,17 +11,154 @@ they can easily be signalled upon such events. ## Contents -1. **[Concept](01_concepts.md)** -2. **[State](02_state.md)** -3. **[Events](03_events.md)** -4. **[Keeper](04_keeper.md)**\ -5. **[Hooks](05_hooks.md)**\ -6. **[Queries](06_queries.md)**\ -7. **[Future improvements](07_future_improvements.md)** +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Events](#events)** +4. **[Keeper](#keeper)** +5. **[Hooks](#hooks)** +6. **[Queries](#queries)** +7. **[Future improvements](#future-improvements)** + +## Concepts + +The purpose of `epochs` module is to provide generalized epoch interface +to other modules so that they can easily implement epochs without +keeping own code for epochs. + +## State + +Epochs module keeps `EpochInfo` objects and modify the information as +epochs info changes. Epochs are initialized as part of genesis +initialization, and modified on begin blockers or end blockers. + +### Epoch information type + +```protobuf +message EpochInfo { + string identifier = 1; + google.protobuf.Timestamp start_time = 2 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"start_time\"" + ]; + google.protobuf.Duration duration = 3 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.jsontag) = "duration,omitempty", + (gogoproto.moretags) = "yaml:\"duration\"" + ]; + int64 current_epoch = 4; + google.protobuf.Timestamp current_epoch_start_time = 5 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"current_epoch_start_time\"" + ]; + bool epoch_counting_started = 6; + reserved 7; + int64 current_epoch_start_height = 8; +} +``` + +EpochInfo keeps `identifier`, `start_time`,`duration`, `current_epoch`, +`current_epoch_start_time`, `epoch_counting_started`, +`current_epoch_start_height`. + +1. `identifier` keeps epoch identification string. +2. `start_time` keeps epoch counting start time, if block time passes + `start_time`, `epoch_counting_started` is set. +3. `duration` keeps target epoch duration. +4. `current_epoch` keeps current active epoch number. +5. `current_epoch_start_time` keeps the start time of current epoch. +6. `epoch_number` is counted only when `epoch_counting_started` flag is + set. +7. `current_epoch_start_height` keeps the start block height of current + epoch. + +## Events + +The `epochs` module emits the following events: + +### BeginBlocker + +| Type | Attribute Key | Attribute Value | +| --------------| ---------------| -----------------| +| epoch\_start | epoch\_number | {epoch\_number} | +| epoch\_start | start\_time | {start\_time} | + +### EndBlocker + +| Type | Attribute Key | Attribute Value | +| ------------| ---------------| -----------------| +| epoch\_end | epoch\_number | {epoch\_number} | + +## Keepers + +### Keeper functions + +Epochs keeper module provides utility functions to manage epochs. + +```go +// Keeper is the interface for lockup module keeper +type Keeper interface { + // GetEpochInfo returns epoch info by identifier + GetEpochInfo(ctx sdk.Context, identifier string) types.EpochInfo + // SetEpochInfo set epoch info + SetEpochInfo(ctx sdk.Context, epoch types.EpochInfo) + // DeleteEpochInfo delete epoch info + DeleteEpochInfo(ctx sdk.Context, identifier string) + // IterateEpochInfo iterate through epochs + IterateEpochInfo(ctx sdk.Context, fn func(index int64, epochInfo types.EpochInfo) (stop bool)) + // Get all epoch infos + AllEpochInfos(ctx sdk.Context) []types.EpochInfo +} +``` + +## Hooks + + +```go + // the first block whose timestamp is after the duration is counted as the end of the epoch + AfterEpochEnd(ctx sdk.Context, epochIdentifier string, epochNumber int64) + // new epoch is next block of epoch end block + BeforeEpochStart(ctx sdk.Context, epochIdentifier string, epochNumber int64) +``` + +### How modules receive hooks + +On hook receiver function of other modules, they need to filter +`epochIdentifier` and only do executions for only specific +epochIdentifier. Filtering epochIdentifier could be in `Params` of other +modules so that they can be modified by governance. Governance can +change epoch from `week` to `day` as their need. + +### Panic isolation + +If a given epoch hook panics, its state update is reverted, but we keep +proceeding through the remaining hooks. This allows more advanced epoch +logic to be used, without concern over state machine halting, or halting +subsequent modules. + +This does mean that if there is behavior you expect from a prior epoch +hook, and that epoch hook reverted, your hook may also have an issue. So +do keep in mind "what if a prior hook didn't get executed" in the safety +checks you consider for a new epoch hook. + ## Queries -### epoch-infos +Epochs module is providing below queries to check the module's state. + + +```protobuf +service Query { + // EpochInfos provide running epochInfos + rpc EpochInfos(QueryEpochsInfoRequest) returns (QueryEpochsInfoResponse) {} + // CurrentEpoch provide current epoch of specified identifier + rpc CurrentEpoch(QueryCurrentEpochRequest) returns (QueryCurrentEpochResponse) {} +} +``` + +### Epoch Infos Query the currently running epochInfos @@ -51,7 +188,8 @@ epochs: ``` ::: -### current-epoch +### Current Epoch + Query the current epoch by the specified identifier @@ -72,4 +210,33 @@ Which in this example outputs: ```sh current_epoch: "183" ``` -::: \ No newline at end of file + +::: + +## Future Improvements + +### Lack point using this module + +In current design each epoch should be at least 2 blocks as start block +should be different from endblock. Because of this, each epoch time will +be `max(blocks_time x 2, epoch_duration)`. If epoch\_duration is set to +`1s`, and `block_time` is `5s`, actual epoch time should be `10s`. We +definitely recommend configure epoch\_duration as more than 2x +block\_time, to use this module correctly. If you enforce to set it to +1s, it's same as 10s - could make module logic invalid. + +TODO for postlaunch: We should see if we can architect things such that +the receiver doesn't have to do this filtering, and the epochs module +would pre-filter for them. + +### Block-time drifts problem + +This implementation has block time drift based on block time. For +instance, we have an epoch of 100 units that ends at t=100, if we have a +block at t=97 and a block at t=104 and t=110, this epoch ends at t=104. +And new epoch start at t=110. There are time drifts here, for around 1-2 +blocks time. It will slow down epochs. + +It's going to slow down epoch by 10-20s per week when epoch duration is +1 week. This should be resolved after launch. + diff --git a/x/gamm/spec/01_concepts.md b/x/gamm/spec/01_concepts.md deleted file mode 100644 index 1341464015f..00000000000 --- a/x/gamm/spec/01_concepts.md +++ /dev/null @@ -1,76 +0,0 @@ -# Concepts - -The `x/gamm` module implements an AMM using Balancer style pools with -varying amounts and weights of assets in pools. - -## Pool - -### Creation of Pool - -At an initial creation of the pool, a fixed amount of 100 share token is -minted in the pool and sent to the creator of the pool's account. The -pool share denom is in the format of `gamm/pool/{poolID}` and is -displayed in the format of `GAMM-{poolID}` to the user. Pool assets are -sorted in alphabetical order by default. - -### Joining Pool - -When joining a pool, a user provides the maximum amount of tokens -they're willing to deposit, while the front end takes care of the -calculation of how many share tokens the user is eligible for at the -specific moment of sending the transaction. - -Calculation of exactly how many tokens are needed to get the designated -share is done at the moment of processing the transaction, validating -that it does not exceed the maximum amount of tokens the user is willing -to deposit. After the validation, GAMM share tokens of the pool are -minted and sent to the user's account. Joining the pool using a single -asset is also possible. - -### Exiting Pool - -When exiting a pool, the user provides the minimum amount of tokens they -are willing to receive as they are returning their shares of the pool. -However, unlike joining a pool, exiting a pool requires the user to pay -the exit fee, which is set as a param of the pool. The user's share -tokens burnt as result. Exiting the pool using a single asset is also -possible. - -+++ - -## Swap - -During the process of swapping a specific asset, the token the user is -putting into the pool is denoted as `tokenIn`, while the token that -would be returned to the user, the asset that is being swapped for, -after the swap is denoted as `tokenOut` throughout the module. - -Given a `tokenIn`, the following calculations are done to calculate how -many tokens are to be swapped into and removed from the pool: - -`tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ (tokenWeightIn / tokenWeightOut)]` - -The calculation is also able to be reversed, the case where user -provides `tokenOut`. The calculation for the amount of tokens that the -user should be putting in is done through the following formula: - -`tokenBalanceIn * [{tokenBalanceOut / (tokenBalanceOut - tokenAmountOut)} ^ (tokenWeightOut / tokenWeightIn) -1] / tokenAmountIn` - -### Spot Price - -Meanwhile, calculation of the spot price with a swap fee is done using -the following formula: - -`spotPrice / (1 - swapFee)`, where `spotPrice` is defined as: - -`(tokenBalanceIn / tokenWeightIn) / (tokenBalanceOut / tokenWeightOut)` - -+++ - -### Multi-Hop - -All tokens are swapped using a multi-hop mechanism. That is, all swaps -are routed via the most cost-efficient way, swapping in and out from -multiple pools in the process. - -+++ diff --git a/x/gamm/spec/02_pool_params.md b/x/gamm/spec/02_pool_params.md deleted file mode 100644 index b7d9dc92f8b..00000000000 --- a/x/gamm/spec/02_pool_params.md +++ /dev/null @@ -1,31 +0,0 @@ -# Pool Parameters - -The `x/gamm` module contains the following `Pool` parameters: - - Key Type - --------------------------; ----------------------------; - SwapFee sdk.Dec - ExitFee sdk.Dec - SmoothWeightChangeParams \*SmoothWeightChangeParams - -- `SwapFee`: The swap fee is the cut of all swaps that goes to the - Liquidity Providers (LPs) for a pool. Suppose a pool has a swap fee - `sf`. Then if a user wants to swap `T` tokens in the pool, `sf * T` - tokens go to the LP's, and then `(1 - sf) * T` tokens are swapped - according to the AMM swap function. -- `ExitFee`: The exit fee is a fee that is applied to LP's that want - to remove their liquidity from the pool. Suppose a pool has an exit - fee `ef`. If they currently have `S` LP shares, then when they - remove their liquidity they get tokens worth `(1 - ef) * S` shares - back. The remaining `ef * S` shares are then burned, and the tokens - corresponding to these shares are kept as liquidity. -- `SmoothWeightChangeParams`: These params allows pool governance to - smoothly change the weights of the assets it holds in the pool. E.g. - it can slowly move from a 2:1 ratio, to a 1:1 ratio. The params - consist of `StartTime`, `Duration`, `InitialPoolWeights` and - `TargetPoolWeights`, where the latter two params are a list of - `PoolAsset` that define the `Token` and `Weight`. Currently, smooth - weight changes are implemented as a linear change in weight ratios - over a given duration of time. So weights changed from 4:1 to 2:2 - over 2 days, then at day 1 of the change, the weights would be - 3:1.5, and at day 2 its 2:2, and will remain at these weight ratios. diff --git a/x/gamm/spec/03_msgs.md b/x/gamm/spec/03_msgs.md deleted file mode 100644 index 69c72c77d27..00000000000 --- a/x/gamm/spec/03_msgs.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# Messages - -The `x/gamm` module supports the following message types: - -## MsgCreateBalancerPool - -+++ - -## MsgJoinPool - -+++ - -## MsgExitPool - -+++ - -## MsgSwapExactAmountIn - -+++ - -## MsgSwapExactAmountOut - -+++ - -## MsgJoinSwapExternAmountIn - -+++ - -### MsgJoinSwapShareAmountOut - -+++ - -### MsgExitSwapShareAmountIn - -+++ - -### MsgExitSwapExternAmountOut - -+++ diff --git a/x/gamm/spec/04_params.md b/x/gamm/spec/04_params.md deleted file mode 100644 index 8ba2a083c71..00000000000 --- a/x/gamm/spec/04_params.md +++ /dev/null @@ -1,12 +0,0 @@ -# Parameters - -The `x/gamm` module contains the following parameters: - - Key Type Example - -----------------; -----------; --------------------------------------------; - PoolCreationFee sdk.Coins \[{"denom":"uosmo","amount":"100000000"}\] - -## PoolCreationFee - -This parameter defines the amount of coins paid to community pool at the -time of pool creation which is introduced to prevent spam pool creation. diff --git a/x/gamm/spec/05_queries_transactions.md b/x/gamm/spec/05_queries_transactions.md deleted file mode 100644 index 4138b2a07f8..00000000000 --- a/x/gamm/spec/05_queries_transactions.md +++ /dev/null @@ -1,328 +0,0 @@ -# GAMM Module - -This document introduces the [Queries](#queries) and [Transactions](#transactions) of the **G**eneralized **A**utomated **M**arket **M**aker (GAMM) module. The GAMM module provides the logic to create and interact with liquidity pools on the Osmosis DEX. - -## Queries - -The **Query** submodule of the GAMM module provides the logic to request information from the liquidity pools. It contains the following functions: - -- [Estimate Swap Exact Amount In](#estimate-swap-exact-amount-in) -- [Estimate Swap Exact Amount Out](#estimate-swap-exact-amount-out) -- [Num Pools](#num-pools) -- [Pool](#pool) -- [Pool Assets](#pool-assets) -- [Pool Params](#pool-params) -- [Pools](#pools) -- [Spot Price](#spot-price) -- [Total Liquidity](#total-liquidity) -- [Total Share](#total-share) - -### Estimate Swap Exact Amount In -Query the estimated result of the [Swap Exact Amount In](#swap-exact-amount-in) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. -#### Usage -```sh -osmosisd query gamm estimate-swap-exact-amount-in [flags] -``` -#### Example -Query the amount of ATOM the sender would receive for swapping 1 OSMO in pool 1. - -```sh -osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 -``` - - -### Estimate Swap Exact Amount Out -Query the estimated result of the [Swap Exact Amount Out](#swap-exact-amount-out) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. -#### Usage -```sh -osmosisd query gamm estimate-swap-exact-amount-out [flags] -``` -#### Example -Query the amount of OSMO the sender would require to swap 1 ATOM out of pool 1. - -```sh -osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --swap-route-pool-ids 1 --swap-route-denoms uosmo -``` - - -### Num Pools -Query the number of active pools. -#### Usage -```sh -osmosisd query gamm num-pools -``` - -## Pool -<<<<<<< HEAD:x/gamm/spec/05_queries_transactions.md -Query the parameter and assets of a specific pool. - -### Usage - -```sh -osmosisd query gamm pool [flags] -``` - -### Example - ->>>>>>> main:x/gamm/README.md -Query parameters and assets from pool 1. - -```sh -osmosisd query gamm pool 1 -``` - - -### Pool Assets -Query the assets of a specific pool. This query is a reduced form of the [Pool](#pool) query. -#### Usage -```sh -osmosisd query gamm pool-assets [flags] -``` - -Query the assets from pool 1. -#### Example -```sh -osmosisd query gamm pool-assets 1 -``` - - -### Pool Params -Query the parameters of a specific pool. This query is a reduced form of the [Pool](#pool) query. -#### Usage -```sh -osmosisd query gamm pool-params [flags] -``` - -Query the parameters from pool 1. -#### Example -```sh -osmosisd query gamm pool-params 1 -``` - - -### Pools -Query parameters and assets of all active pools. - - -#### Usage -Query the price of OSMO based on the price of ATOM in pool 1. - -```sh -osmosisd query gamm spot-price 1 uosmo ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 -``` - - -### Total Liquidity -Query the total liquidity of all active pools. -#### Usage -```sh -osmosisd query gamm total-liquidity -``` - - -### Total Share -Query the total amount of GAMM shares of a specific pool. -#### Usage -```sh -osmosisd query gamm total-share [flags] -``` -#### Example -Query the total amount of GAMM shares of pool 1. - -```sh -osmosisd query gamm total-share 1 -``` - - - - -### Transactions -The **Transaction** submodule of the GAMM module provides the logic to create and interact with the liquidity pools. It contains the following functions: - -- [Create Pool](#create-pool) -- [Join Pool](#join-pool) -- [Exit Pool](#exit-pool) -- [Join Swap Extern Amount In](#join-swap-extern-amount-in) -- [Exit Swap Extern Amount Out](#exit-swap-extern-amount-out) -- [Join Swap Share Amount Out](#join-swap-share-amount-out) -- [Exit Swap Share Amount In](#exit-swap-share-amount-in) -- [Swap Exact Amount In](#swap-exact-amount-in) -- [Swap Exact Amount Out](#swap-exact-amount-out) - - -### Create Pool -Create a new liquidity pool and provide the initial liquidity to it. Pool initialization parameters must be provided through a JSON file using the flag *pool-file*. - -#### Usage - -```sh -osmosisd tx gamm create-pool [flags] -``` - -The configuration file *config.json* must specify the following parameters. - -```json -{ - "weights": [list weighted denoms], - "initial-deposit": [list of denoms with initial deposit amount], - "swap-fee": [swap fee in percentage], - "exit-fee": [exit fee in percentage], - "future-governor": [number of hours] -} -``` -#### Example -Create a new ATOM-OSMO liquidity pool with a swap and exit fee of 1%. - -```sh -tx gamm create-pool --pool-file ../public/config.json --from myKeyringWallet -``` - -The configuration file contains the following parameters. - -```json -{ - "weights": "5uatom,5uosmo", - "initial-deposit": "100uatom,100uosmo", - "swap-fee": "0.01", - "exit-fee": "0.01", - "future-governor": "168h" -} -``` - - - -### Join Pool -Join a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *max-amounts-in* and *share-amount-out* are required. - -#### Usage - -```sh -osmosisd tx gamm join-pool [flags] -``` - -#### Example - -Join pool 1 with 1 OSMO and the respective amount of ATOM, using myKeyringWallet. - -```sh -osmosisd tx gamm join-pool --pool-id 2 --max-amounts-in 1000000uosmo --max-amounts-in 1000000uion --share-amount-out 1000000 --from myKeyringWallet -``` - - -### Exit Pool -Exit a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *min-amounts-out* and *share-amount-in* are required. - -#### Usage - -```sh -osmosisd tx gamm exit-pool [flags] -``` - -#### Example - -Exit pool one with 1 OSMO and the respective amount of ATOM using myKeyringWallet. - -```sh -osmosisd tx gamm exit-pool --pool-id 1 --min-amounts-out 1000000uosmo --share-amount-in 1000000 --from myKeyringWallet -``` - - -### Join Swap Extern Amount In -Note that the flags *pool-id* is required. - -#### Usage - -```sh -osmosisd tx gamm join-swap-extern-amount-in [token-in] [share-out-min-amount] [flags] -``` - -#### Example - -```sh -osmosisd tx gamm join-swap-extern-amount-in 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet -``` - - -### Exit Swap Extern Amount Out -Note that the flag *pool-id* is required. - -```sh -osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] [flags] -``` - -#### Example - -```sh -osmosisd tx gamm exit-swap-extern-amount-out 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet -``` - - -### Join Swap Share Amount Out -Note that the flag *pool-id* is required. - -#### Usage - -```sh -osmosisd tx gamm join-swap-share-amount-out [token-in-denom] [token-in-max-amount] [share-out-amount] [flags] -``` - -#### Example - -```sh -osmosisd tx gamm join-swap-share-amount-out uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet -``` - - -### Exit Swap Share Amount In -Note that the flag *pool-id* is required. - -#### Usage - -```sh -osmosisd tx gamm exit-swap-share-amount-in [token-out-denom] [share-in-amount] [token-out-min-amount] [flags] -``` - -#### Example - -```sh -osmosisd tx gamm exit-swap-share-amount-in uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet -``` - -### Swap Exact Amount In -Swap an exact amount of tokens into a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. - -#### Usage - -```sh -osmosisd tx gamm swap-exact-amount-in [token-in] [token-out-min-amount] [flags] -``` - -#### Example - -Swap 1 OSMO through pool 1 into at least 0.3 ATOM using MyKeyringWallet. - -```sh -osmosisd tx gamm swap-exact-amount-in 1000000uosmo 300000 --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from MyKeyringWallet -``` - - -### Swap Exact Amount Out -Swap an exact amount of tokens out of a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. - -### Usage - -```sh -osmosisd tx gamm swap-exact-amount-out [token-out] [token-out-max-amount] [flags] -``` - -### Example - -Swap 1 ATOM through pool 1 into at most 2.5 OSMO using MyKeyringWallet. - -```sh -osmosisd tx gamm swap-exact-amount-out 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 250000 --swap-route-pool-ids 1 --swap-route-denoms uosmo --from MyKeyringWallet -``` - -## Other resources -* [Creating a liquidity bootstrapping pool](./client/docs/create-lbp-pool.md) -* [Creating a pool with a pool file](./client/docs/create-pool.md) diff --git a/x/gamm/spec/0x_weights.md b/x/gamm/spec/0x_weights.md deleted file mode 100644 index 3a089eb8264..00000000000 --- a/x/gamm/spec/0x_weights.md +++ /dev/null @@ -1,26 +0,0 @@ -# Weights - -Weights refer to the how we weight the reserves of assets within a pool. -Its often convenient to think of weights in terms of ratios, so a 1:1 -pool between "ATOM" and "BTC" is a pool where the spot price is -`#ATOM in pool / #BTC in pool`. - -A 2:1 pool is one where the spot price is -`2*(#ATOM in pool) / #BTC in pool`. This weights allows one to get the -same spot price in the pool, with fewer ATOM reserves. (This comes at -the cost of having higher slippage, e.g. buying 10 atoms moves the price -more than a 1:1 pool with the same BTC liquidity). - -Within the state machine, we represent weights as numbers, and the -ratios are computed internally. So you could specify a pool between -three assets, with weights 100:30:50, which is equivalent to a 10:3:5 -pool. - -The ratios provided in a CreatePool message, or governance proposal are -capped at 2\^20. However, within the state machine they are stored with -an extra 30 bits of precision, allowing for smooth changes between two -weights to happen with sufficient granularity. - -(Note, these docs are intended to get shuffled around as we write more -of the spec for x/gamm. I just wanted to document this along with the -PR, to save work for our future selves) diff --git a/x/gamm/spec/README.md b/x/gamm/spec/README.md index ac06df03feb..5189919cca9 100644 --- a/x/gamm/spec/README.md +++ b/x/gamm/spec/README.md @@ -4,26 +4,129 @@ The ``GAMM`` module (**G**eneralized **A**utomated **M**arket **M**aker) provide ## Contents -1. **[Weights](0x_weights.md)** -2. **[Concepts](01_concepts.md)** -3. **[Pool Params](02_pool_params.md)** -4. **[Messages](03_msgs.md)** -5. **[Params](04_params.md)** -5. **[Queries and Transactions](05_queries_transactions.md)** +1. **[Concepts](#concepts)** +2. **[Weights](#weights)** +3. **[Pool Parameters](#network-parameters)** +4. **[Messages](#messages)** +5. **[Transactions](#transactions)** +6. **[Queries and Transactions](#queries-and-transactions)** -## Overview +## Concepts -### Network Parameters +The `x/gamm` module implements an AMM using Balancer style pools with +varying amounts and weights of assets in pools. -Pools have the following parameters: +### Pool + +#### Creation of Pool + +At an initial creation of the pool, a fixed amount of 100 share token is +minted in the pool and sent to the creator of the pool's account. The +pool share denom is in the format of `gamm/pool/{poolID}` and is +displayed in the format of `GAMM-{poolID}` to the user. Pool assets are +sorted in alphabetical order by default. + +#### Joining Pool + +When joining a pool, a user provides the maximum amount of tokens +they're willing to deposit, while the front end takes care of the +calculation of how many share tokens the user is eligible for at the +specific moment of sending the transaction. + +Calculation of exactly how many tokens are needed to get the designated +share is done at the moment of processing the transaction, validating +that it does not exceed the maximum amount of tokens the user is willing +to deposit. After the validation, GAMM share tokens of the pool are +minted and sent to the user's account. Joining the pool using a single +asset is also possible. + +#### Exiting Pool + +When exiting a pool, the user provides the minimum amount of tokens they +are willing to receive as they are returning their shares of the pool. +However, unlike joining a pool, exiting a pool requires the user to pay +the exit fee, which is set as a param of the pool. The user's share +tokens burnt as result. Exiting the pool using a single asset is also +possible. + +[Exiting pool](https://github.com/osmosis-labs/osmosis/blob/main/x/gamm/keeper/pool_service.go) + +### Swap + +During the process of swapping a specific asset, the token the user is +putting into the pool is denoted as `tokenIn`, while the token that +would be returned to the user, the asset that is being swapped for, +after the swap is denoted as `tokenOut` throughout the module. + +Given a `tokenIn`, the following calculations are done to calculate how +many tokens are to be swapped into and removed from the pool: + +`tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ (tokenWeightIn / tokenWeightOut)]` + +The calculation is also able to be reversed, the case where user +provides `tokenOut`. The calculation for the amount of tokens that the +user should be putting in is done through the following formula: + +`tokenBalanceIn * [{tokenBalanceOut / (tokenBalanceOut - tokenAmountOut)} ^ (tokenWeightOut / tokenWeightIn) -1] / tokenAmountIn` + +#### Spot Price + +Meanwhile, calculation of the spot price with a swap fee is done using +the following formula: -- SwapFee -- ExitFee -- FutureGovernor -- Weights -- SmoothWeightChangeParams +`spotPrice / (1 - swapFee)`, where `spotPrice` is defined as: -We will go through these in sequence. +`(tokenBalanceIn / tokenWeightIn) / (tokenBalanceOut / tokenWeightOut)` + +[Spot price](https://github.com/osmosis-labs/osmosis/blob/main/x/gamm/keeper/swap.go) + +#### Multi-Hop + +All tokens are swapped using a multi-hop mechanism. That is, all swaps +are routed via the most cost-efficient way, swapping in and out from +multiple pools in the process. + +[Multi-Hop](https://github.com/osmosis-labs/osmosis/blob/main/x/gamm/keeper/multihop.go) + +## Weights + +Weights refer to the how we weight the reserves of assets within a pool. +Its often convenient to think of weights in terms of ratios, so a 1:1 +pool between "ATOM" and "BTC" is a pool where the spot price is +`#ATOM in pool / #BTC in pool`. + +A 2:1 pool is one where the spot price is +`2*(#ATOM in pool) / #BTC in pool`. This weights allows one to get the +same spot price in the pool, with fewer ATOM reserves. (This comes at +the cost of having higher slippage, e.g. buying 10 atoms moves the price +more than a 1:1 pool with the same BTC liquidity). + +Within the state machine, we represent weights as numbers, and the +ratios are computed internally. So you could specify a pool between +three assets, with weights 100:30:50, which is equivalent to a 10:3:5 +pool. + +The ratios provided in a CreatePool message, or governance proposal are +capped at 2\^20. However, within the state machine they are stored with +an extra 30 bits of precision, allowing for smooth changes between two +weights to happen with sufficient granularity. + +(Note, these docs are intended to get shuffled around as we write more +of the spec for x/gamm. I just wanted to document this along with the +PR, to save work for our future selves) + +## Network Parameters + +Pools have the following parameters: + +| Key | Type | +| --------------------------| ----------------------------| +| SwapFee | sdk.Dec | +| ExitFee | sdk.Dec | +| FutureGovernor | \*FutureGovernor | +| Weights | \*Weights | +| SmoothWeightChangeParams | \*SmoothWeightChangeParams | +| PoolCreationFee | sdk.Coins | 1. **SwapFee** - The swap fee is the cut of all swaps that goes to the Liquidity Providers (LPs) for a pool. Suppose a pool has a swap fee `s`. Then if a user wants to swap `T` tokens in the pool, `sT` tokens go to the LP's, and then `(1 - s)T` tokens are swapped according to the AMM swap function. @@ -49,10 +152,53 @@ The GAMM module also has a **PoolCreationFee** parameter, which currently is set

+ + +## Messages + +The `x/gamm` module supports the following message types: + +### MsgCreateBalancerPool + +[MsgCreateBalancerPool](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/pool-models/balancer/tx.proto#L16-L26) + +### MsgJoinPool + +[MsgJoinPool](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L27-L39) + +### MsgExitPool + +[MsgExitPool](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L44-L57) + +### MsgSwapExactAmountIn + +[MsgSwapExactAmountIn](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L68-L80) + +### MsgSwapExactAmountOut + +[MsgSwapExactAmountOut](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L90-L102) + +### MsgJoinSwapExternAmountIn + +[MsgJoinSwapExternAmountIn](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L107-L119) + +#### MsgJoinSwapShareAmountOut + +[MsgJoinSwapShareAmountOut](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L124-L138) + +#### MsgExitSwapShareAmountIn + +[MsgExitSwapShareAmountIn](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L143-L158) + +#### MsgExitSwapExternAmountOut + +[MsgExitSwapExternAmountOut](https://github.com/osmosis-labs/osmosis/blob/v7.1.0/proto/osmosis/gamm/v1beta1/tx.proto#L163-L175) + ## Transactions -### create-pool +### Create pool + Create a new liquidity pool and provide initial liquidity to it. ```sh @@ -96,8 +242,8 @@ There is now a 100 OSMO fee for creating pools. ::: +### Join pool -### join-pool Add liquidity to a specified pool to get an **exact** amount of LP shares while specifying a **maximum** number tokens willing to swap to receive said LP shares. ```sh @@ -115,8 +261,8 @@ osmosisd tx gamm join-pool --pool-id 3 --max-amounts-in 37753ibc/1480B8FD20AD5FC +### Exit pool -### exit-pool Remove liquidity from a specified pool with an **exact** amount of LP shares while specifying the **minimum** number of tokens willing to receive for said LP shares. ```sh @@ -134,8 +280,7 @@ osmosisd tx gamm exit-pool --pool-id 3 --min-amounts-out 33358ibc/1480B8FD20AD5F - -### join-swap-extern-amount-in +### Join-swap-extern-amount-in Add liquidity to a specified pool with only one of the required assets (i.e. Join pool 1 (50/50 ATOM-OSMO) with just ATOM). @@ -157,7 +302,7 @@ osmosisd tx gamm join-swap-extern-amount-in 200000ibc/1480B8FD20AD5FCAE81EA87584 -### exit-swap-extern-amount-out +### Exit-swap-extern-amount-out Remove liquidity from a specified pool with a **maximum** amount of LP shares and swap to an **exact** amount of one of the token pairs (i.e. Leave pool 1 (50/50 ATOM-OSMO) and receive 100% ATOM instead of 50% OSMO and 50% ATOM). @@ -179,7 +324,7 @@ osmosisd tx gamm exit-swap-extern-amount-out 199430ibc/1480B8FD20AD5FCAE81EA8758 -### join-swap-share-amount-out +### Join-swap-share-amount-out Swap a **maximum** amount of a specified token for another token, similar to swapping a token on the trade screen GUI (i.e. takes the specified asset and swaps it to the other asset needed to join the specified pool) and then adds an **exact** amount of LP shares to the specified pool. @@ -199,7 +344,7 @@ osmosisd tx gamm join-swap-share-amount-out uosmo 312466 14481270389710236872 -- -### exit-swap-share-amount-in +### Exit-swap-share-amount-in Remove an **exact** amount of LP shares from a specified pool, swap the LP shares to one of the token pairs to receive a **minimum** of the specified token amount. @@ -217,8 +362,7 @@ osmosisd tx gamm exit-swap-share-amount-in uosmo 14563185400026723131 298548 --p ::: - -### swap-exact-amount-in +### Swap-exact-amount-in Swap an **exact** amount of tokens for a **minimum** of another token, similar to swapping a token on the trade screen GUI. @@ -238,7 +382,7 @@ osmosisd tx gamm swap-exact-amount-in 407239ibc/1480B8FD20AD5FCAE81EA87584D26954 -### swap-exact-amount-out +### Swap-exact-amount-out Swap a **maximum** amount of tokens for an **exact** amount of another token, similar to swapping a token on the trade screen GUI. @@ -260,10 +404,157 @@ osmosisd tx gamm swap-exact-amount-out 140530uosmo 407239 --swap-route-pool-ids +## Queries and Transactions + ## Queries -### estimate-swap-exact-amount-in +The **Query** submodule of the GAMM module provides the logic to request information from the liquidity pools. It contains the following functions: + +- [Estimate Swap Exact Amount In](#estimate-swap-exact-amount-in) +- [Estimate Swap Exact Amount Out](#estimate-swap-exact-amount-out) +- [Num Pools](#num-pools) +- [Pool](#pool) +- [Pool Assets](#pool-assets) +- [Pool Params](#pool-params) +- [Pools](#pools) +- [Spot Price](#spot-price) +- [Total Liquidity](#total-liquidity) +- [Total Share](#total-share) + +### Estimate Swap Exact Amount In +Query the estimated result of the [Swap Exact Amount In](#swap-exact-amount-in) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. +#### Usage +```sh +osmosisd query gamm estimate-swap-exact-amount-in [flags] +``` +#### Example +Query the amount of ATOM the sender would receive for swapping 1 OSMO in pool 1. + +```sh +osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000uosmo --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +``` + + +### Estimate Swap Exact Amount Out +Query the estimated result of the [Swap Exact Amount Out](#swap-exact-amount-out) transaction. Note that the flags *swap-route-pool* and *swap-route-denoms* are required. +#### Usage +```sh +osmosisd query gamm estimate-swap-exact-amount-out [flags] +``` +#### Example +Query the amount of OSMO the sender would require to swap 1 ATOM out of pool 1. + +```sh +osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570unsnk4zng4uqv7cm8 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --swap-route-pool-ids 1 --swap-route-denoms uosmo +``` + +### Num Pools +Query the number of active pools. + +#### Usage +```sh +osmosisd query gamm num-pools +``` + +## Pool +Query the parameter and assets of a specific pool. + +### Usage + +```sh +osmosisd query gamm pool [flags] +``` + +### Example + +Query parameters and assets from pool 1. + +```sh +osmosisd query gamm pool 1 +``` + + +### Pool Assets +Query the assets of a specific pool. This query is a reduced form of the [Pool](#pool) query. +#### Usage +```sh +osmosisd query gamm pool-assets [flags] +``` + +#### Example + +Query the assets from pool 1. + +```sh +osmosisd query gamm pool-assets 1 +``` + + +### Pool Params +Query the parameters of a specific pool. This query is a reduced form of the [Pool](#pool) query. +#### Usage +```sh +osmosisd query gamm pool-params [flags] +``` + +Query the parameters from pool 1. +#### Example +```sh +osmosisd query gamm pool-params 1 +``` + + +### Pools +Query parameters and assets of all active pools. + + +#### Usage +Query the price of OSMO based on the price of ATOM in pool 1. + +```sh +osmosisd query gamm spot-price 1 uosmo ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 +``` + + +### Total Liquidity +Query the total liquidity of all active pools. +#### Usage +```sh +osmosisd query gamm total-liquidity +``` + + +### Total Share +Query the total amount of GAMM shares of a specific pool. +#### Usage +```sh +osmosisd query gamm total-share [flags] +``` +#### Example +Query the total amount of GAMM shares of pool 1. + +```sh +osmosisd query gamm total-share 1 +``` + + + +## Transactions + +The **Transaction** submodule of the GAMM module provides the logic to create and interact with the liquidity pools. It contains the following functions: + +- [Create Pool](#create-pool) +- [Join Pool](#join-pool) +- [Exit Pool](#exit-pool) +- [Join Swap Extern Amount In](#join-swap-extern-amount-in) +- [Exit Swap Extern Amount Out](#exit-swap-extern-amount-out) +- [Join Swap Share Amount Out](#join-swap-share-amount-out) +- [Exit Swap Share Amount In](#exit-swap-share-amount-in) +- [Swap Exact Amount In](#swap-exact-amount-in) +- [Swap Exact Amount Out](#swap-exact-amount-out) + +### Estimate-swap-exact-amount-in Query the estimated result of the [swap-exact-amount-in](#swap-exact-amount-in) transaction. @@ -282,7 +573,7 @@ osmosisd query gamm estimate-swap-exact-amount-in 1 osmo123nfq6m8f88m4g3sky570un -### estimate-swap-exact-amount-out +### Estimate-swap-exact-amount-out Query the estimated result of the [swap-exact-amount-out](#swap-exact-amount-out) transaction. @@ -301,7 +592,7 @@ osmosisd query gamm estimate-swap-exact-amount-out 1 osmo123nfq6m8f88m4g3sky570u -### num-pools +### Num-pools Query the number of active pools. @@ -311,7 +602,7 @@ osmosisd query gamm num-pools -### pool +### Pool Query the parameter and assets of a specific pool. @@ -356,7 +647,8 @@ Which outputs: ::: -### pool-assets +### Pool-assets + Query the assets of a specific pool. This query is a reduced form of the [pool](#pool) query. @@ -414,7 +706,9 @@ smooth_weight_change_params: null ::: -### pools + +### Pools + Query parameters and assets of all active pools. @@ -425,7 +719,9 @@ osmosisd query gamm pools -### spot-price + +### Spot-price + Query the spot price of a pool asset based on a specific pool it is in. @@ -453,7 +749,9 @@ In other words, at the time of this writing, ~5.314 OSMO is equivalent to 1 ATOM -### total-liquidity + +### Total-liquidity + Query the total liquidity of all active pools. @@ -464,7 +762,9 @@ osmosisd query gamm total-liquidity -### total-share + +### Total-share + Query the total amount of GAMM shares of a specific pool. @@ -489,4 +789,185 @@ totalShares: ``` Indicating there are a total of `252328895.834096787303097071 gamm/pool/1` at the time of this writing -::: \ No newline at end of file + +::: + + + +### Create Pool +Create a new liquidity pool and provide the initial liquidity to it. Pool initialization parameters must be provided through a JSON file using the flag *pool-file*. + +#### Usage + +```sh +osmosisd tx gamm create-pool [flags] +``` + +The configuration file *config.json* must specify the following parameters. + +```json +{ + "weights": [list weighted denoms], + "initial-deposit": [list of denoms with initial deposit amount], + "swap-fee": [swap fee in percentage], + "exit-fee": [exit fee in percentage], + "future-governor": [number of hours] +} +``` +#### Example +Create a new ATOM-OSMO liquidity pool with a swap and exit fee of 1%. + +```sh +tx gamm create-pool --pool-file ../public/config.json --from myKeyringWallet +``` + +The configuration file contains the following parameters. + +```json +{ + "weights": "5uatom,5uosmo", + "initial-deposit": "100uatom,100uosmo", + "swap-fee": "0.01", + "exit-fee": "0.01", + "future-governor": "168h" +} +``` + + + +### Join Pool +Join a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *max-amounts-in* and *share-amount-out* are required. + +#### Usage + +```sh +osmosisd tx gamm join-pool [flags] +``` + +#### Example + +Join pool 1 with 1 OSMO and the respective amount of ATOM, using myKeyringWallet. + +```sh +osmosisd tx gamm join-pool --pool-id 2 --max-amounts-in 1000000uosmo --max-amounts-in 1000000uion --share-amount-out 1000000 --from myKeyringWallet +``` + + +### Exit Pool +Exit a specific pool with a custom amount of tokens. Note that the flags *pool-id*, *min-amounts-out* and *share-amount-in* are required. + +#### Usage + +```sh +osmosisd tx gamm exit-pool [flags] +``` + +#### Example + +Exit pool one with 1 OSMO and the respective amount of ATOM using myKeyringWallet. + +```sh +osmosisd tx gamm exit-pool --pool-id 1 --min-amounts-out 1000000uosmo --share-amount-in 1000000 --from myKeyringWallet +``` + + +### Join Swap Extern Amount In +Note that the flags *pool-id* is required. + +#### Usage + +```sh +osmosisd tx gamm join-swap-extern-amount-in [token-in] [share-out-min-amount] [flags] +``` + +#### Example + +```sh +osmosisd tx gamm join-swap-extern-amount-in 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet +``` + + +### Exit Swap Extern Amount Out +Note that the flag *pool-id* is required. + +```sh +osmosisd tx gamm exit-swap-extern-amount-out [token-out] [share-in-max-amount] [flags] +``` + +#### Example + +```sh +osmosisd tx gamm exit-swap-extern-amount-out 1000000uosmo 1000000 --pool-id 1 --from myKeyringWallet +``` + + +### Join Swap Share Amount Out +Note that the flag *pool-id* is required. + +#### Usage + +```sh +osmosisd tx gamm join-swap-share-amount-out [token-in-denom] [token-in-max-amount] [share-out-amount] [flags] +``` + +#### Example + +```sh +osmosisd tx gamm join-swap-share-amount-out uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet +``` + + +### Exit Swap Share Amount In +Note that the flag *pool-id* is required. + +#### Usage + +```sh +osmosisd tx gamm exit-swap-share-amount-in [token-out-denom] [share-in-amount] [token-out-min-amount] [flags] +``` + +#### Example + +```sh +osmosisd tx gamm exit-swap-share-amount-in uosmo 1000000 1000000 --pool-id 1 --from myKeyringWallet +``` + +### Swap Exact Amount In +Swap an exact amount of tokens into a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. + +#### Usage + +```sh +osmosisd tx gamm swap-exact-amount-in [token-in] [token-out-min-amount] [flags] +``` + +#### Example + +Swap 1 OSMO through pool 1 into at least 0.3 ATOM using MyKeyringWallet. + +```sh +osmosisd tx gamm swap-exact-amount-in 1000000uosmo 300000 --swap-route-pool-ids 1 --swap-route-denoms ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 --from MyKeyringWallet +``` + + +### Swap Exact Amount Out +Swap an exact amount of tokens out of a specific pool. Note that the flags *swap-route-pool-ids* and *swap-route-denoms* are required. + +### Usage + +```sh +osmosisd tx gamm swap-exact-amount-out [token-out] [token-out-max-amount] [flags] +``` + +### Example + +Swap 1 ATOM through pool 1 into at most 2.5 OSMO using MyKeyringWallet. + +```sh +osmosisd tx gamm swap-exact-amount-out 1000000ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2 250000 --swap-route-pool-ids 1 --swap-route-denoms uosmo --from MyKeyringWallet +``` + +## Other resources +* [Creating a liquidity bootstrapping pool](./client/docs/create-lbp-pool.md) +* [Creating a pool with a pool file](./client/docs/create-pool.md) + diff --git a/x/incentives/spec/01_concepts.md b/x/incentives/spec/01_concepts.md deleted file mode 100644 index 5798ab5e9f2..00000000000 --- a/x/incentives/spec/01_concepts.md +++ /dev/null @@ -1,10 +0,0 @@ -# Concepts - -The purpose of `incentives` module is to provide incentives to the users -who lock specific token for specific period of time. - -Locked tokens can be of any denom, including LP tokens, IBC tokens, and -native tokens. The incentive amount is entered from the provider -directly via a specific message type. Rewards for a given pool of locked -up tokens are pooled into a gauge until the disbursement time. At the -disbursement time, they are distributed pro-rata to members of the pool. diff --git a/x/incentives/spec/02_state.md b/x/incentives/spec/02_state.md deleted file mode 100644 index 354ef654a3e..00000000000 --- a/x/incentives/spec/02_state.md +++ /dev/null @@ -1,88 +0,0 @@ -```{=html} - -``` - -State -===== - -Incentives management ---------------------- - -All the incentives that are going to be provided are locked into -`IncentivePool` until released to the appropriate recipients after a -specific period of time. - -### Gauge - -Rewards to be distributed are organized by `Gauge`. The `Gauge` -describes how users can get reward, stores the amount of coins in the -gauge, the cadence at which rewards are to be distributed, and the -number of epochs to distribute the reward over. - -``` {.protobuf} -enum LockQueryType { - option (gogoproto.goproto_enum_prefix) = false; - - ByDuration = 0; // locks which has more than specific duration - ByTime = 1; // locks which are started before specific time -} - -message QueryCondition { - LockQueryType lock_query_type = 1; // type of lock, ByLockDuration | ByLockTime - string denom = 2; // lock denom - google.protobuf.Duration duration = 3; // condition for lock duration, only valid if positive - google.protobuf.Timestamp timestamp = 4; // condition for lock start time, not valid if unset value -} - -message Gauge { - uint64 id = 1; // unique ID of a Gauge - QueryCondition distribute_to = 2; // distribute condition of a lock which meet one of these conditions - repeated cosmos.base.v1beta1.Coin coins = 3; // can distribute multiple coins - google.protobuf.Timestamp start_time = 4; // condition for lock start time, not valid if unset value - uint64 num_epochs_paid_over = 5; // number of epochs distribution will be done -} -``` - -### Gauge queues - -#### Upcoming queue - -To start release `Gauges` at a specific time, we schedule distribution -start time with time key queue. - -#### Active queue - -Active queue has all the `Gauges` that are distributing and after -distribution period finish, it's removed from the queue. - -#### Active by Denom queue - -To speed up the distribution process, module introduces the active -`Gauges` by denom. - -#### Finished queue - -Finished queue saves the `Gauges` that has finished distribution to keep -in track. - -Module state ------------- - -The state of the module is expressed by `params`, `lockable_durations` -and `gauges`. - -``` {.protobuf} -// GenesisState defines the incentives module's genesis state. -message GenesisState { - // params defines all the parameters of the module - Params params = 1 [ (gogoproto.nullable) = false ]; - repeated Gauge gauges = 2 [ (gogoproto.nullable) = false ]; - repeated google.protobuf.Duration lockable_durations = 3 [ - (gogoproto.nullable) = false, - (gogoproto.stdduration) = true, - (gogoproto.moretags) = "yaml:\"lockable_durations\"" - ]; -} -``` diff --git a/x/incentives/spec/03_messages.md b/x/incentives/spec/03_messages.md deleted file mode 100644 index ff5a1529abd..00000000000 --- a/x/incentives/spec/03_messages.md +++ /dev/null @@ -1,41 +0,0 @@ -# Messages - -## Create Gauge - -`MsgCreateGauge` can be submitted by any account to create a `Gauge`. - -``` {.go} -type MsgCreateGauge struct { - Owner sdk.AccAddress - DistributeTo QueryCondition - Rewards sdk.Coins - StartTime time.Time // start time to start distribution - NumEpochsPaidOver uint64 // number of epochs distribution will be done -} -``` - -**State modifications:** - -- Validate `Owner` has enough tokens for rewards -- Generate new `Gauge` record -- Save the record inside the keeper's time basis unlock queue -- Transfer the tokens from the `Owner` to incentives `ModuleAccount`. - -## Adding balance to Gauge - -`MsgAddToGauge` can be submitted by any account to add more incentives -to a `Gauge`. - -``` {.go} -type MsgAddToGauge struct { - GaugeID uint64 - Rewards sdk.Coins -} -``` - -**State modifications:** - -- Validate `Owner` has enough tokens for rewards -- Check if `Gauge` with specified `msg.GaugeID` is available -- Modify the `Gauge` record by adding `msg.Rewards` -- Transfer the tokens from the `Owner` to incentives `ModuleAccount`. diff --git a/x/incentives/spec/04_events.md b/x/incentives/spec/04_events.md deleted file mode 100644 index 0d7ffbbd4d1..00000000000 --- a/x/incentives/spec/04_events.md +++ /dev/null @@ -1,42 +0,0 @@ -# Events - -The incentives module emits the following events: - -## Handlers - -### MsgCreateGauge - - Type Attribute Key Attribute Value - ---------------; -------------------------; ---------------------; - create\_gauge gauge\_id {gaugeID} - create\_gauge distribute\_to {owner} - create\_gauge rewards {rewards} - create\_gauge start\_time {startTime} - create\_gauge num\_epochs\_paid\_over {numEpochsPaidOver} - message action create\_gauge - message sender {owner} - transfer recipient {moduleAccount} - transfer sender {owner} - transfer amount {amount} - -### MsgAddToGauge - - Type Attribute Key Attribute Value - ----------------; ---------------; -----------------; - add\_to\_gauge gauge\_id {gaugeID} - create\_gauge rewards {rewards} - message action create\_gauge - message sender {owner} - transfer recipient {moduleAccount} - transfer sender {owner} - transfer amount {amount} - -## EndBlockers - -### Incentives distribution - - Type Attribute Key Attribute Value - --------------; ---------------; -----------------; - transfer\[\] recipient {receiver} - transfer\[\] sender {moduleAccount} - transfer\[\] amount {distrAmount} diff --git a/x/incentives/spec/05_hooks.md b/x/incentives/spec/05_hooks.md deleted file mode 100644 index 5a55f2d9e76..00000000000 --- a/x/incentives/spec/05_hooks.md +++ /dev/null @@ -1,14 +0,0 @@ -# Hooks - -In this section we describe the "hooks" that `incentives` module provide -for other modules. - -If there's no usecase for this, we could ignore this. - -``` {.go} - AfterCreateGauge(ctx sdk.Context, gaugeId uint64) - AfterAddToGauge(ctx sdk.Context, gaugeId uint64) - AfterStartDistribution(ctx sdk.Context, gaugeId uint64) - AfterFinishDistribution(ctx sdk.Context, gaugeId uint64) - AfterDistribute(ctx sdk.Context, gaugeId uint64) -``` diff --git a/x/incentives/spec/06_queries.md b/x/incentives/spec/06_queries.md deleted file mode 100644 index dd5162d8ee7..00000000000 --- a/x/incentives/spec/06_queries.md +++ /dev/null @@ -1,34 +0,0 @@ -```{=html} - -``` - -Queries -======= - -In this section we describe the queries required on grpc server. - -``` {.protobuf} -// Query defines the gRPC querier service. -service Query { - // returns coins that is going to be distributed - rpc ModuleToDistributeCoins(ModuleToDistributeCoinsRequest) returns (ModuleToDistributeCoinsResponse) {} - // returns coins that are distributed by module so far - rpc ModuleDistributedCoins(ModuleDistributedCoinsRequest) returns (ModuleDistributedCoinsResponse) {} - // returns Gauge by id - rpc GaugeByID(GaugeByIDRequest) returns (GaugeByIDResponse) {} - // returns gauges both upcoming and active - rpc Gauges(GaugesRequest) returns (GaugesResponse) {} - // returns active gauges - rpc ActiveGauges(ActiveGaugesRequest) returns (ActiveGaugesResponse) {} - // returns scheduled gauges - rpc UpcomingGauges(UpcomingGaugesRequest) returns (UpcomingGaugesResponse) {} - // RewardsEst returns an estimate of the rewards at a future specific time. - // The querier either provides an address or a set of locks - // for which they want to find the associated rewards. - rpc RewardsEst(RewardsEstRequest) returns (RewardsEstResponse) {} - // returns lockable durations that are valid to give incentives - rpc LockableDurations(QueryLockableDurationsRequest) returns (QueryLockableDurationsResponse) {} -} -``` diff --git a/x/incentives/spec/07_params.md b/x/incentives/spec/07_params.md deleted file mode 100644 index d714ca9d467..00000000000 --- a/x/incentives/spec/07_params.md +++ /dev/null @@ -1,12 +0,0 @@ -# Parameters - -The incentives module contains the following parameters: - - Key Type Example - ---------------------- -------- ---------- - DistrEpochIdentifier string "weekly" - -Note: DistrEpochIdentifier is a epoch identifier, and module distribute -rewards at the end of epochs. As `epochs` module is handling multiple -epochs, the identifier is required to check if distribution should be -done at `AfterEpochEnd` hook diff --git a/x/incentives/spec/README.md b/x/incentives/spec/README.md index 637a530e257..0b421527fda 100644 --- a/x/incentives/spec/README.md +++ b/x/incentives/spec/README.md @@ -16,17 +16,19 @@ There are two kinds of `gauges`, perpetual and non-perpetual ones. ## Contents -1. **[Concept](01_concepts.md)** -2. **[State](02_state.md)** -3. **[Messages](03_messages.md)** -4. **[Events](04_events.md)** -5. **[Hooks](05_hooks.md)** -6. **[Queries](06_queries.md)** -7. **[Params](07_params.md)** +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Messages](#messages)** +4. **[Events](#events)** +5. **[Hooks](#hooks)** +7. **[Params](#params)** +8. **[Transactions](#transactions)** +9. **[Queries](#queries)** -## Overview +## Concepts -The purpose of incentives module is to provide incentives to users who lock certain tokens for specified periods of time. +The purpose of `incentives` module is to provide incentives to the users +who lock specific token for specific period of time. Locked tokens can be of any denomination, including LP tokens (gamm/pool/x), IBC tokens (tokens sent through IBC such as ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2), and native tokens (such as ATOM or LUNA). @@ -40,6 +42,196 @@ There are two kinds of gauges: **`perpetual`** and **`non-perpetual`**: - **`Perpetual gauges`** distribute all their tokens at a single time and only distribute their tokens again once the gauge is refilled (this is mainly used to distribute minted OSMO tokens to LP token stakers). Perpetual gauges persist and will re-disburse tokens when refilled (there is no "active" period) +## State + +### Incentives management + +All the incentives that are going to be provided are locked into +`IncentivePool` until released to the appropriate recipients after a +specific period of time. + +### Gauge + +Rewards to be distributed are organized by `Gauge`. The `Gauge` +describes how users can get reward, stores the amount of coins in the +gauge, the cadence at which rewards are to be distributed, and the +number of epochs to distribute the reward over. + +``` protobuf +enum LockQueryType { + option (gogoproto.goproto_enum_prefix) = false; + + ByDuration = 0; // locks which has more than specific duration + ByTime = 1; // locks which are started before specific time +} + +message QueryCondition { + LockQueryType lock_query_type = 1; // type of lock, ByLockDuration | ByLockTime + string denom = 2; // lock denom + google.protobuf.Duration duration = 3; // condition for lock duration, only valid if positive + google.protobuf.Timestamp timestamp = 4; // condition for lock start time, not valid if unset value +} + +message Gauge { + uint64 id = 1; // unique ID of a Gauge + QueryCondition distribute_to = 2; // distribute condition of a lock which meet one of these conditions + repeated cosmos.base.v1beta1.Coin coins = 3; // can distribute multiple coins + google.protobuf.Timestamp start_time = 4; // condition for lock start time, not valid if unset value + uint64 num_epochs_paid_over = 5; // number of epochs distribution will be done +} +``` + +### Gauge queues + +#### Upcoming queue + +To start release `Gauges` at a specific time, we schedule distribution +start time with time key queue. + +#### Active queue + +Active queue has all the `Gauges` that are distributing and after +distribution period finish, it's removed from the queue. + +#### Active by Denom queue + +To speed up the distribution process, module introduces the active +`Gauges` by denom. + +#### Finished queue + +Finished queue saves the `Gauges` that has finished distribution to keep +in track. + +#### Module state + +The state of the module is expressed by `params`, `lockable_durations` +and `gauges`. + +``` protobuf +// GenesisState defines the incentives module's genesis state. +message GenesisState { + // params defines all the parameters of the module + Params params = 1 [ (gogoproto.nullable) = false ]; + repeated Gauge gauges = 2 [ (gogoproto.nullable) = false ]; + repeated google.protobuf.Duration lockable_durations = 3 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.moretags) = "yaml:\"lockable_durations\"" + ]; +} +``` +## Messages + +### Create Gauge + +`MsgCreateGauge` can be submitted by any account to create a `Gauge`. + +``` go +type MsgCreateGauge struct { + Owner sdk.AccAddress + DistributeTo QueryCondition + Rewards sdk.Coins + StartTime time.Time // start time to start distribution + NumEpochsPaidOver uint64 // number of epochs distribution will be done +} +``` + +**State modifications:** + +- Validate `Owner` has enough tokens for rewards +- Generate new `Gauge` record +- Save the record inside the keeper's time basis unlock queue +- Transfer the tokens from the `Owner` to incentives `ModuleAccount`. + +### Adding balance to Gauge + +`MsgAddToGauge` can be submitted by any account to add more incentives +to a `Gauge`. + +``` go +type MsgAddToGauge struct { + GaugeID uint64 + Rewards sdk.Coins +} +``` + +**State modifications:** + +- Validate `Owner` has enough tokens for rewards +- Check if `Gauge` with specified `msg.GaugeID` is available +- Modify the `Gauge` record by adding `msg.Rewards` +- Transfer the tokens from the `Owner` to incentives `ModuleAccount`. + +## Events + +The incentives module emits the following events: + +### Handlers + +#### MsgCreateGauge + +| Type |Attribute Key | Attribute Value | +| ---------------| -------------------------| ---------------------| +| create\_gauge | gauge\_id | {gaugeID} | +| create\_gauge | distribute\_to | {owner} | +| create\_gauge | rewards | {rewards} | +| create\_gauge | start\_time | {startTime} | +| create\_gauge | num\_epochs\_paid\_over | {numEpochsPaidOver} | +| message | action | create\_gauge | +| message | sender | {owner} | +| transfer | recipient | {moduleAccount} | +| transfer | sender | {owner} | +| transfer | amount | {amount} | + +#### MsgAddToGauge + +| Type | Attribute Key | Attribute Value | +| ----------------| ---------------| -----------------| +| add\_to\_gauge | gauge\_id | {gaugeID} | +| create\_gauge | rewards | {rewards} | +| message | action | create\_gauge | +| message | sender | {owner} | +| transfer | recipient | {moduleAccount} | +| transfer | sender | {owner} | +| transfer | amount | {amount} | + +### EndBlockers + +#### Incentives distribution + +| Type |Attribute Key |Attribute Value | +| --------------| ---------------| -----------------| +| transfer\[\] | recipient | {receiver} | +| transfer\[\] | sender | {moduleAccount} | +| transfer\[\] | amount | {distrAmount} | + +## Hooks + +In this section we describe the "hooks" that `incentives` module provide +for other modules. + +If there's no usecase for this, we could ignore this. + +``` go + AfterCreateGauge(ctx sdk.Context, gaugeId uint64) + AfterAddToGauge(ctx sdk.Context, gaugeId uint64) + AfterStartDistribution(ctx sdk.Context, gaugeId uint64) + AfterFinishDistribution(ctx sdk.Context, gaugeId uint64) + AfterDistribute(ctx sdk.Context, gaugeId uint64) +``` +## Parameters + +The incentives module contains the following parameters: + +| Key | Type | Example | +| ----------------------| --------| ----------| +| DistrEpochIdentifier | string | "weekly" | + +Note: DistrEpochIdentifier is a epoch identifier, and module distribute +rewards at the end of epochs. As `epochs` module is handling multiple +epochs, the identifier is required to check if distribution should be +done at `AfterEpochEnd` hook

@@ -101,6 +293,32 @@ osmosisd tx incentives add-to-gauge 1914 500000000ibc/46B44899322F3CD854D2D46DEE ## Queries +In this section we describe the queries required on grpc server. + +```protobuf +// Query defines the gRPC querier service. +service Query { + // returns coins that is going to be distributed + rpc ModuleToDistributeCoins(ModuleToDistributeCoinsRequest) returns (ModuleToDistributeCoinsResponse) {} + // returns coins that are distributed by module so far + rpc ModuleDistributedCoins(ModuleDistributedCoinsRequest) returns (ModuleDistributedCoinsResponse) {} + // returns Gauge by id + rpc GaugeByID(GaugeByIDRequest) returns (GaugeByIDResponse) {} + // returns gauges both upcoming and active + rpc Gauges(GaugesRequest) returns (GaugesResponse) {} + // returns active gauges + rpc ActiveGauges(ActiveGaugesRequest) returns (ActiveGaugesResponse) {} + // returns scheduled gauges + rpc UpcomingGauges(UpcomingGaugesRequest) returns (UpcomingGaugesResponse) {} + // RewardsEst returns an estimate of the rewards at a future specific time. + // The querier either provides an address or a set of locks + // for which they want to find the associated rewards. + rpc RewardsEst(RewardsEstRequest) returns (RewardsEstResponse) {} + // returns lockable durations that are valid to give incentives + rpc LockableDurations(QueryLockableDurationsRequest) returns (QueryLockableDurationsResponse) {} +} +``` + ### active-gauges Query active gauges diff --git a/x/lockup/spec/01_concepts.md b/x/lockup/spec/01_concepts.md deleted file mode 100644 index d70a8b19367..00000000000 --- a/x/lockup/spec/01_concepts.md +++ /dev/null @@ -1,8 +0,0 @@ -# Concepts - -The purpose of `lockup` module is to provide the functionality to lock -tokens for specific period of time for LP token stakers to get -incentives. - -This module provides interfaces for other modules to iterate the locks -efficiently and grpc query to check the status of locked coins. diff --git a/x/lockup/spec/02_state.md b/x/lockup/spec/02_state.md deleted file mode 100644 index ea723a76d18..00000000000 --- a/x/lockup/spec/02_state.md +++ /dev/null @@ -1,160 +0,0 @@ -```{=html} - -``` - -State -===== - -Locked coins management ------------------------ - -Locked coins are all stored in module account for `lockup` module which -is called `LockPool`. When user lock coins within `lockup` module, it's -moved from user account to `LockPool` and a record (`PeriodLock` struct) -is created. - -Once the period is over, user can withdraw it at anytime from -`LockPool`. User can withdraw by PeriodLock ID or withdraw all -`UnlockableCoins` at a time. - -### Period Lock - -A `PeriodLock` is a single unit of lock by period. It's a record of -locked coin at a specific time. It stores owner, duration, unlock time -and the amount of coins locked. - -``` {.go} -type PeriodLock struct { - ID uint64 - Owner sdk.AccAddress - Duration time.Duration - UnlockTime time.Time - Coins sdk.Coins -} -``` - -All locks are stored on the KVStore as value at -`{KeyPrefixPeriodLock}{ID}` key. - -### Period lock reference queues - -To provide time efficient queries, several reference queues are managed -by denom, unlock time, and duration. There are two big queues to store -the lock references. (`a_prefix_key`) - -1. Lock references that hasn't started with unlocking yet has prefix of - `KeyPrefixNotUnlocking`. -2. Lock references that has started unlocking already has prefix of - `KeyPrefixUnlocking`. -3. Lock references that has withdrawn, it's removed from the store. - -Regardless the lock has started unlocking or not, it stores below -references. (`b_prefix_key`) - -1. `{KeyPrefixLockDuration}{Duration}` -2. `{KeyPrefixAccountLockDuration}{Owner}{Duration}` -3. `{KeyPrefixDenomLockDuration}{Denom}{Duration}` -4. `{KeyPrefixAccountDenomLockDuration}{Owner}{Denom}{Duration}` - -If the lock is unlocking, it also stores the below referneces. - -1. `{KeyPrefixLockTimestamp}{LockEndTime}` -2. `{KeyPrefixAccountLockTimestamp}{Owner}{LockEndTime}` -3. `{KeyPrefixDenomLockTimestamp}{Denom}{LockEndTime}` -4. `{KeyPrefixAccountDenomLockTimestamp}{Owner}{Denom}{LockEndTime}` - -For end time keys, they are converted to sortable string by using -`sdk.FormatTimeBytes` function. - -**Note:** Additionally, for locks that hasn't started unlocking yet, it -stores accumulation store for efficient rewards distribution mechanism. - -For reference management, `addLockRefByKey` function is used a lot. Here -key is the prefix key to be used for iteration. It is combination of two -prefix keys.(`{a_prefix_key}{b_prefix_key}`) - -``` {.go} -// addLockRefByKey make a lockID iterable with the prefix `key` -func (k Keeper) addLockRefByKey(ctx sdk.Context, key []byte, lockID uint64) error { - store := ctx.KVStore(k.storeKey) - lockIDBz := sdk.Uint64ToBigEndian(lockID) - endKey := combineKeys(key, lockIDBz) - if store.Has(endKey) { - return fmt.Errorf("lock with same ID exist: %d", lockID) - } - store.Set(endKey, lockIDBz) - return nil -} -``` - -### Synthetic Lockup - -Synthetic Lockups are a concept that serve the following roles: - -- Add "restrictions" to an underlying PeriodLock, so that its bond - status must be managed by a module rather than a BeginUnlockMessage -- Allow issuing of a locked, "synthetic" denom type -- Allow distribution of rewards to locked synthetic denominations. - -The first goal can eventually be pushed into a new data structure, as it -doesn't really relate to the synthetic component. - -This is then used for superfluid staking. (Old docs below): - -The goal of synthetic lockup is to support the querying of locks by -denom especially for delegated staking. By combining native denom and -synthetic suffix, lockup supports querying with synthetic denom with -existing denom querying functions. - -Synthetic lockup is creating virtual lockup where new denom is -combination of original denom and synthetic suffix. At the time of -synthetic lockup creation and deletion, accumulation store is also being -updated and on querier side, they can query as freely as native lockup. - -Note: The staking, distribution, slashing, superfluid module would be -refactored to use lockup module and synthetic lockup. The following -changes for synthetic lockup on native lockup change could be defined as -per use case. For now we assume this change is made on hook receiver -side which manages synthetic lockup, e.g. use cases are when user start -/ pause superfluid staking on a lockup, redelegation event, unbonding -event etc. - -External modules are managing synthetic locks to use it on their own -logic implementation. (e.g. delegated staking and superfluid staking) - -A `SyntheticLock` is a single unit of synthetic lockup. Each synthetic -lockup has reference `PeriodLock` ID, synthetic suffix (`Suffix`) and -synthetic lock's removal time (`EndTime`). - -``` {.go} -type SyntheticLock struct { - LockId uint64 - Suffix string - EndTime time.Time -} -``` - -All synthetic locks are stored on the KVStore as value at -`{KeyPrefixPeriodLock}{LockID}{Suffix}` key. - -### Synthetic lock reference queues - -To provide time efficient queries, several reference queues are managed -by denom, unlock time, and duration. - -1. `{KeyPrefixDenomLockTimestamp}{SyntheticDenom}{LockEndTime}` -2. `{KeyPrefixDenomLockDuration}{SyntheticDenom}{Duration}` -3. `{KeyPrefixAccountDenomLockTimestamp}{Owner}{SyntheticDenom}{LockEndTime}` -4. `{KeyPrefixAccountDenomLockDuration}{Owner}{SyntheticDenom}{Duration}` - -SyntheticDenom is expressed as `{Denom}{Suffix}`. (Note: we can change -this to `{Prefix}{Denom}` as per discussion with Dev) - -For end time keys, they are converted to sortable string by using -`sdk.FormatTimeBytes` function. - -**Note:** To implement the auto removal of synthetic lockups that is -already finished, we manage a separate time basis queue at -`{KeyPrefixSyntheticLockTimestamp}{EndTime}{LockId}{Suffix}` diff --git a/x/lockup/spec/03_messages.md b/x/lockup/spec/03_messages.md deleted file mode 100644 index d28babf239e..00000000000 --- a/x/lockup/spec/03_messages.md +++ /dev/null @@ -1,63 +0,0 @@ -# Messages - -## Lock Tokens - -`MsgLockTokens` can be submitted by any token holder via a -`MsgLockTokens` transaction. - -``` {.go} -type MsgLockTokens struct { - Owner sdk.AccAddress - Duration time.Duration - Coins sdk.Coins -} -``` - -**State modifications:** - -- Validate `Owner` has enough tokens -- Generate new `PeriodLock` record -- Save the record inside the keeper's time basis unlock queue -- Transfer the tokens from the `Owner` to lockup `ModuleAccount`. - -## Begin Unlock of all locks - -Once time is over, users can withdraw unlocked coins from lockup -`ModuleAccount`. - -``` {.go} -type MsgBeginUnlockingAll struct { - Owner string -} -``` - -**State modifications:** - -- Fetch all unlockable `PeriodLock`s that has not started unlocking - yet -- Set `PeriodLock`'s unlock time -- Remove lock references from `NotUnlocking` queue -- Add lock references to `Unlocking` queue - -## Begin unlock for a lock - -Once time is over, users can withdraw unlocked coins from lockup -`ModuleAccount`. - -``` {.go} -type MsgBeginUnlocking struct { - Owner string - ID uint64 -} -``` - -**State modifications:** - -- Check `PeriodLock` with `ID` specified by `MsgBeginUnlocking` is not - started unlocking yet -- Set `PeriodLock`'s unlock time -- Remove lock references from `NotUnlocking` queue -- Add lock references to `Unlocking` queue - -Note: If another module needs past `PeriodLock` item, it can log the -details themselves using the hooks. diff --git a/x/lockup/spec/04_events.md b/x/lockup/spec/04_events.md deleted file mode 100644 index 0703888ccb6..00000000000 --- a/x/lockup/spec/04_events.md +++ /dev/null @@ -1,64 +0,0 @@ -# Events - -The lockup module emits the following events: - -## Handlers - -### MsgLockTokens - - Type Attribute Key Attribute Value - -------------- ------------------ ----------------- - lock\_tokens period\_lock\_id {periodLockID} - lock\_tokens owner {owner} - lock\_tokens amount {amount} - lock\_tokens duration {duration} - lock\_tokens unlock\_time {unlockTime} - message action lock\_tokens - message sender {owner} - transfer recipient {moduleAccount} - transfer sender {owner} - transfer amount {amount} - -### MsgBeginUnlocking - - Type Attribute Key Attribute Value - ---------------; ------------------; ------------------; - begin\_unlock period\_lock\_id {periodLockID} - begin\_unlock owner {owner} - begin\_unlock amount {amount} - begin\_unlock duration {duration} - begin\_unlock unlock\_time {unlockTime} - message action begin\_unlocking - message sender {owner} - -### MsgBeginUnlockingAll - - Type Attribute Key Attribute Value - --------------------; ------------------; -----------------------; - begin\_unlock\_all owner {owner} - begin\_unlock\_all unlocked\_coins {unlockedCoins} - begin\_unlock period\_lock\_id {periodLockID} - begin\_unlock owner {owner} - begin\_unlock amount {amount} - begin\_unlock duration {duration} - begin\_unlock unlock\_time {unlockTime} - message action begin\_unlocking\_all - message sender {owner} - -## Endblocker - -### Automatic withdraw when unlock time mature - - Type Attribute Key Attribute Value - ----------------; ------------------; -----------------; - message action unlock\_tokens - message sender {owner} - transfer\[\] recipient {owner} - transfer\[\] sender {moduleAccount} - transfer\[\] amount {unlockAmount} - unlock\[\] period\_lock\_id {owner} - unlock\[\] owner {lockID} - unlock\[\] duration {lockDuration} - unlock\[\] unlock\_time {unlockTime} - unlock\_tokens owner {owner} - unlock\_tokens unlocked\_coins {totalAmount} diff --git a/x/lockup/spec/05_keeper.md b/x/lockup/spec/05_keeper.md deleted file mode 100644 index 8fbfc4c3bb3..00000000000 --- a/x/lockup/spec/05_keeper.md +++ /dev/null @@ -1,88 +0,0 @@ -```html - -``` - -Keepers - -Lockup Keeper - -Lockup keeper provides utility functions to store lock queues and query -locks. - -```go -// Keeper is the interface for lockup module keeper -type Keeper interface { - // GetModuleBalance Returns full balance of the module - GetModuleBalance(sdk.Context) sdk.Coins - // GetModuleLockedCoins Returns locked balance of the module - GetModuleLockedCoins(sdk.Context) sdk.Coins - // GetAccountUnlockableCoins Returns whole unlockable coins which are not withdrawn yet - GetAccountUnlockableCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins - // GetAccountUnlockingCoins Returns whole unlocking coins - GetAccountUnlockingCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins - // GetAccountLockedCoins Returns a locked coins that can't be withdrawn - GetAccountLockedCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins - // GetAccountLockedPastTime Returns the total locks of an account whose unlock time is beyond timestamp - GetAccountLockedPastTime(sdk.Context, addr sdk.AccAddress, timestamp time.Time) []types.PeriodLock - // GetAccountUnlockedBeforeTime Returns the total unlocks of an account whose unlock time is before timestamp - GetAccountUnlockedBeforeTime(sdk.Context, addr sdk.AccAddress, timestamp time.Time) []types.PeriodLock - // GetAccountLockedPastTimeDenom is equal to GetAccountLockedPastTime but denom specific - GetAccountLockedPastTimeDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, timestamp time.Time) []types.PeriodLock - - // GetAccountLockedLongerDuration Returns account locked with duration longer than specified - GetAccountLockedLongerDuration(sdk.Context, addr sdk.AccAddress, duration time.Duration) []types.PeriodLock - // GetAccountLockedLongerDurationDenom Returns account locked with duration longer than specified with specific denom - GetAccountLockedLongerDurationDenom(sdk.Context, addr sdk.AccAddress, denom string, duration time.Duration) []types.PeriodLock - // GetLocksPastTimeDenom Returns the locks whose unlock time is beyond timestamp - GetLocksPastTimeDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, timestamp time.Time) []types.PeriodLock - // GetLocksLongerThanDurationDenom Returns the locks whose unlock duration is longer than duration - GetLocksLongerThanDurationDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, duration time.Duration) []types.PeriodLock - // GetLockByID Returns lock from lockID - GetLockByID(sdk.Context, lockID uint64) (*types.PeriodLock, error) - // GetPeriodLocks Returns the period locks on pool - GetPeriodLocks(sdk.Context) ([]types.PeriodLock, error) - // UnlockAllUnlockableCoins Unlock all unlockable coins - UnlockAllUnlockableCoins(sdk.Context, account sdk.AccAddress) (sdk.Coins, error) - // LockTokens lock tokens from an account for specified duration - LockTokens(sdk.Context, owner sdk.AccAddress, coins sdk.Coins, duration time.Duration) (types.PeriodLock, error) - // AddTokensToLock locks more tokens into a lockup - AddTokensToLock(ctx sdk.Context, owner sdk.AccAddress, lockID uint64, coins sdk.Coins) (*types.PeriodLock, error) - // Lock is a utility to lock coins into module account - Lock(sdk.Context, lock types.PeriodLock) error - // Unlock is a utility to unlock coins from module account - Unlock(sdk.Context, lock types.PeriodLock) error - GetSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string) (*types.SyntheticLock, error) - GetAllSyntheticLockupsByLockup(ctx sdk.Context, lockID uint64) []types.SyntheticLock - GetAllSyntheticLockups(ctx sdk.Context) []types.SyntheticLock - // CreateSyntheticLockup create synthetic lockup with lock id and denom suffix - CreateSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string, unlockDuration time.Duration) error - // DeleteSyntheticLockup delete synthetic lockup with lock id and suffix - DeleteSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string) error - DeleteAllMaturedSyntheticLocks(ctx sdk.Context) -``` - -# Lock Admin Keeper - -Lockup admin keeper provides god privilege functions to remove tokens -from locks and create new locks. - -``` go -// AdminKeeper defines a god priviledge keeper functions to remove tokens from locks and create new locks -// For the governance system of token pools, we want a "ragequit" feature -// So governance changes will take 1 week to go into effect -// During that time, people can choose to "ragequit" which means they would leave the original pool -// and form a new pool with the old parameters but if they still had 2 months of lockup left, -// their liquidity still needs to be 2 month lockup-ed, just in the new pool -// And we need to replace their pool1 LP tokens with pool2 LP tokens with the same lock duration and end time - -type AdminKeeper interface { - Keeper - - // this unlock previous lockID and create a new lock with newCoins with same duration and endtime - Relock(sdk.Context, lockID uint64, newCoins sdk.Coins) error - // this unlock without time check with an admin priviledge - BreakLock(sdk.Context, lockID uint64) error -} -``` diff --git a/x/lockup/spec/06_hooks.md b/x/lockup/spec/06_hooks.md deleted file mode 100644 index 943be12b072..00000000000 --- a/x/lockup/spec/06_hooks.md +++ /dev/null @@ -1,20 +0,0 @@ -```html - -``` - -# Hooks - -In this section we describe the "hooks" that `lockup` module provide for -other modules. - -## Tokens Locked - -On lock/unlock events, lockup module execute hooks for other modules to -make following actions. - -``` go - OnTokenLocked(ctx sdk.Context, address sdk.AccAddress, lockID uint64, amount sdk.Coins, lockDuration time.Duration, unlockTime time.Time) - OnTokenUnlocked(ctx sdk.Context, address sdk.AccAddress, lockID uint64, amount sdk.Coins, lockDuration time.Duration, unlockTime time.Time) -``` diff --git a/x/lockup/spec/07_queries.md b/x/lockup/spec/07_queries.md deleted file mode 100644 index 36af63b9943..00000000000 --- a/x/lockup/spec/07_queries.md +++ /dev/null @@ -1,48 +0,0 @@ -```html - -``` - -# Queries - -In this section we describe the queries required on grpc server. - -``` protobuf -// Query defines the gRPC querier service. -service Query { - // Return full balance of the module - rpc ModuleBalance(ModuleBalanceRequest) returns (ModuleBalanceResponse); - // Return locked balance of the module - rpc ModuleLockedAmount(ModuleLockedAmountRequest) returns (ModuleLockedAmountResponse); - - // Returns unlockable coins which are not withdrawn yet - rpc AccountUnlockableCoins(AccountUnlockableCoinsRequest) returns (AccountUnlockableCoinsResponse); - // Returns unlocking coins - rpc AccountUnlockingCoins(AccountUnlockingCoinsRequest) returns (AccountUnlockingCoinsResponse) {} - // Return a locked coins that can't be withdrawn - rpc AccountLockedCoins(AccountLockedCoinsRequest) returns (AccountLockedCoinsResponse); - - // Returns locked records of an account with unlock time beyond timestamp - rpc AccountLockedPastTime(AccountLockedPastTimeRequest) returns (AccountLockedPastTimeResponse); - // Returns locked records of an account with unlock time beyond timestamp excluding tokens started unlocking - rpc AccountLockedPastTimeNotUnlockingOnly(AccountLockedPastTimeNotUnlockingOnlyRequest) returns (AccountLockedPastTimeNotUnlockingOnlyResponse) {} - // Returns unlocked records with unlock time before timestamp - rpc AccountUnlockedBeforeTime(AccountUnlockedBeforeTimeRequest) returns (AccountUnlockedBeforeTimeResponse); - - // Returns lock records by address, timestamp, denom - rpc AccountLockedPastTimeDenom(AccountLockedPastTimeDenomRequest) returns (AccountLockedPastTimeDenomResponse); - // Returns lock record by id - rpc LockedByID(LockedRequest) returns (LockedResponse); - - // Returns account locked records with longer duration - rpc AccountLockedLongerDuration(AccountLockedLongerDurationRequest) returns (AccountLockedLongerDurationResponse); - // Returns account locked records with longer duration excluding tokens started unlocking - rpc AccountLockedLongerDurationNotUnlockingOnly(AccountLockedLongerDurationNotUnlockingOnlyRequest) returns (AccountLockedLongerDurationNotUnlockingOnlyResponse) {} - // Returns account's locked records for a denom with longer duration - rpc AccountLockedLongerDurationDenom(AccountLockedLongerDurationDenomRequest) returns (AccountLockedLongerDurationDenomResponse); - - // Returns account locked records with a specific duration - rpc AccountLockedDuration(AccountLockedDurationRequest) returns (AccountLockedDurationResponse); -} -``` diff --git a/x/lockup/spec/08_params.md b/x/lockup/spec/08_params.md deleted file mode 100644 index 338fda9f2d1..00000000000 --- a/x/lockup/spec/08_params.md +++ /dev/null @@ -1,15 +0,0 @@ -```html - -``` - -# Parameters - -The lockup module contains the following parameters: - -| Key | Type | Example | -| ---------------------- | --------------- | ------- | - -Note: Currently no parameters are set for `lockup` module, we will need -to move lockable durations from incentives module to lockup module. diff --git a/x/lockup/spec/09_endblocker.md b/x/lockup/spec/09_endblocker.md deleted file mode 100644 index 14fbda554a2..00000000000 --- a/x/lockup/spec/09_endblocker.md +++ /dev/null @@ -1,31 +0,0 @@ -```html - -``` - -# Endblocker - -## Withdraw tokens after unlock time mature - -Once time is over, endblocker withdraw coins from matured locks and -coins are sent from lockup `ModuleAccount`. - -**State modifications:** - -- Fetch all unlockable `PeriodLock`s that `Owner` has not withdrawn - yet -- Remove `PeriodLock` records from the state -- Transfer the tokens from lockup `ModuleAccount` to the - `MsgUnlockTokens.Owner`. - -## Remove synthetic locks after removal time mature - -For synthetic lockups, no coin movement is made, but lockup record and -reference queues are removed. - -**State modifications:** - -- Fetch all synthetic lockups that is matured -- Remove `SyntheticLock` records from the state along with reference - queues diff --git a/x/lockup/spec/README.md b/x/lockup/spec/README.md index 9d4a9821b56..41ff5246204 100644 --- a/x/lockup/spec/README.md +++ b/x/lockup/spec/README.md @@ -12,17 +12,26 @@ This module provides interfaces for other modules to iterate the locks efficient ## Contents -1. **[Concept](01_concepts.md)** -2. **[State](02_state.md)** -3. **[Messages](03_messages.md)** -4. **[Events](04_events.md)** -5. **[Keeper](05_keeper.md)**\ -6. **[Hooks](06_hooks.md)**\ -7. **[Queries](07_queries.md)**\ -8. **[Params](08_params.md)** -9. **[Endblocker](09_endblocker.md)** - -## Overview +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Messages](#messages)** +4. **[Events](#events)** +5. **[Keeper](#keeper)** +6. **[Hooks](#hooks)** +7. **[Queries](#queries)** +8. **[Transactions](#transactions)** +9. **[Params](#params)** +10. **[Endblocker](#endblocker)** + +## Concepts + +The purpose of `lockup` module is to provide the functionality to lock +tokens for specific period of time for LP token stakers to get +incentives. + +To unlock these LP shares, users must trigger the unlock timer and wait for the unlock period that was set initially to be completed. After the unlock period is over, users can turn LP shares back into their respective share of tokens. + +This module provides interfaces for other modules to iterate the locks efficiently and grpc query to check the status of locked coins. There are currently three incentivize lockup periods; `1 day` (24h), `1 week` (168h), and `2 weeks` (336h). When locking tokens in the 2 week period, the liquidity provider is effectively earning rewards for a combination of the 1 day, 1 week, and 2 week bonding periods. @@ -40,6 +49,422 @@ After the first day passes, they will only receive rewards for the 1 day and 1 w

+## State + +### Locked coins management + +Locked coins are all stored in module account for `lockup` module which +is called `LockPool`. When user lock coins within `lockup` module, it's +moved from user account to `LockPool` and a record (`PeriodLock` struct) +is created. + +Once the period is over, user can withdraw it at anytime from +`LockPool`. User can withdraw by PeriodLock ID or withdraw all +`UnlockableCoins` at a time. + +### Period Lock + +A `PeriodLock` is a single unit of lock by period. It's a record of +locked coin at a specific time. It stores owner, duration, unlock time +and the amount of coins locked. + +``` {.go} +type PeriodLock struct { + ID uint64 + Owner sdk.AccAddress + Duration time.Duration + UnlockTime time.Time + Coins sdk.Coins +} +``` + +All locks are stored on the KVStore as value at +`{KeyPrefixPeriodLock}{ID}` key. + +### Period lock reference queues + +To provide time efficient queries, several reference queues are managed +by denom, unlock time, and duration. There are two big queues to store +the lock references. (`a_prefix_key`) + +1. Lock references that hasn't started with unlocking yet has prefix of + `KeyPrefixNotUnlocking`. +2. Lock references that has started unlocking already has prefix of + `KeyPrefixUnlocking`. +3. Lock references that has withdrawn, it's removed from the store. + +Regardless the lock has started unlocking or not, it stores below +references. (`b_prefix_key`) + +1. `{KeyPrefixLockDuration}{Duration}` +2. `{KeyPrefixAccountLockDuration}{Owner}{Duration}` +3. `{KeyPrefixDenomLockDuration}{Denom}{Duration}` +4. `{KeyPrefixAccountDenomLockDuration}{Owner}{Denom}{Duration}` + +If the lock is unlocking, it also stores the below referneces. + +1. `{KeyPrefixLockTimestamp}{LockEndTime}` +2. `{KeyPrefixAccountLockTimestamp}{Owner}{LockEndTime}` +3. `{KeyPrefixDenomLockTimestamp}{Denom}{LockEndTime}` +4. `{KeyPrefixAccountDenomLockTimestamp}{Owner}{Denom}{LockEndTime}` + +For end time keys, they are converted to sortable string by using +`sdk.FormatTimeBytes` function. + +**Note:** Additionally, for locks that hasn't started unlocking yet, it +stores accumulation store for efficient rewards distribution mechanism. + +For reference management, `addLockRefByKey` function is used a lot. Here +key is the prefix key to be used for iteration. It is combination of two +prefix keys.(`{a_prefix_key}{b_prefix_key}`) + +``` {.go} +// addLockRefByKey make a lockID iterable with the prefix `key` +func (k Keeper) addLockRefByKey(ctx sdk.Context, key []byte, lockID uint64) error { + store := ctx.KVStore(k.storeKey) + lockIDBz := sdk.Uint64ToBigEndian(lockID) + endKey := combineKeys(key, lockIDBz) + if store.Has(endKey) { + return fmt.Errorf("lock with same ID exist: %d", lockID) + } + store.Set(endKey, lockIDBz) + return nil +} +``` + +### Synthetic Lockup + +Synthetic Lockups are a concept that serve the following roles: + +- Add "restrictions" to an underlying PeriodLock, so that its bond + status must be managed by a module rather than a BeginUnlockMessage +- Allow issuing of a locked, "synthetic" denom type +- Allow distribution of rewards to locked synthetic denominations. + +The first goal can eventually be pushed into a new data structure, as it +doesn't really relate to the synthetic component. + +This is then used for superfluid staking. (Old docs below): + +The goal of synthetic lockup is to support the querying of locks by +denom especially for delegated staking. By combining native denom and +synthetic suffix, lockup supports querying with synthetic denom with +existing denom querying functions. + +Synthetic lockup is creating virtual lockup where new denom is +combination of original denom and synthetic suffix. At the time of +synthetic lockup creation and deletion, accumulation store is also being +updated and on querier side, they can query as freely as native lockup. + +Note: The staking, distribution, slashing, superfluid module would be +refactored to use lockup module and synthetic lockup. The following +changes for synthetic lockup on native lockup change could be defined as +per use case. For now we assume this change is made on hook receiver +side which manages synthetic lockup, e.g. use cases are when user start +/ pause superfluid staking on a lockup, redelegation event, unbonding +event etc. + +External modules are managing synthetic locks to use it on their own +logic implementation. (e.g. delegated staking and superfluid staking) + +A `SyntheticLock` is a single unit of synthetic lockup. Each synthetic +lockup has reference `PeriodLock` ID, synthetic suffix (`Suffix`) and +synthetic lock's removal time (`EndTime`). + +``` {.go} +type SyntheticLock struct { + LockId uint64 + Suffix string + EndTime time.Time +} +``` + +All synthetic locks are stored on the KVStore as value at +`{KeyPrefixPeriodLock}{LockID}{Suffix}` key. + +### Synthetic lock reference queues + +To provide time efficient queries, several reference queues are managed +by denom, unlock time, and duration. + +1. `{KeyPrefixDenomLockTimestamp}{SyntheticDenom}{LockEndTime}` +2. `{KeyPrefixDenomLockDuration}{SyntheticDenom}{Duration}` +3. `{KeyPrefixAccountDenomLockTimestamp}{Owner}{SyntheticDenom}{LockEndTime}` +4. `{KeyPrefixAccountDenomLockDuration}{Owner}{SyntheticDenom}{Duration}` + +SyntheticDenom is expressed as `{Denom}{Suffix}`. (Note: we can change +this to `{Prefix}{Denom}` as per discussion with Dev) + +For end time keys, they are converted to sortable string by using +`sdk.FormatTimeBytes` function. + +**Note:** To implement the auto removal of synthetic lockups that is +already finished, we manage a separate time basis queue at +`{KeyPrefixSyntheticLockTimestamp}{EndTime}{LockId}{Suffix}` + +## Messages + +### Lock Tokens + +`MsgLockTokens` can be submitted by any token holder via a +`MsgLockTokens` transaction. + +``` {.go} +type MsgLockTokens struct { + Owner sdk.AccAddress + Duration time.Duration + Coins sdk.Coins +} +``` + +**State modifications:** + +- Validate `Owner` has enough tokens +- Generate new `PeriodLock` record +- Save the record inside the keeper's time basis unlock queue +- Transfer the tokens from the `Owner` to lockup `ModuleAccount`. + +### Begin Unlock of all locks + +Once time is over, users can withdraw unlocked coins from lockup +`ModuleAccount`. + +``` {.go} +type MsgBeginUnlockingAll struct { + Owner string +} +``` + +**State modifications:** + +- Fetch all unlockable `PeriodLock`s that has not started unlocking + yet +- Set `PeriodLock`'s unlock time +- Remove lock references from `NotUnlocking` queue +- Add lock references to `Unlocking` queue + +### Begin unlock for a lock + +Once time is over, users can withdraw unlocked coins from lockup +`ModuleAccount`. + +``` {.go} +type MsgBeginUnlocking struct { + Owner string + ID uint64 +} +``` + +**State modifications:** + +- Check `PeriodLock` with `ID` specified by `MsgBeginUnlocking` is not + started unlocking yet +- Set `PeriodLock`'s unlock time +- Remove lock references from `NotUnlocking` queue +- Add lock references to `Unlocking` queue + +Note: If another module needs past `PeriodLock` item, it can log the +details themselves using the hooks. + +## Events + +The lockup module emits the following events: + +### Handlers + +#### MsgLockTokens + +| Type | Attribute Key | Attribute Value | +| --------------| ------------------| -----------------| +| lock\_tokens | period\_lock\_id | {periodLockID} | +| lock\_tokens | owner | {owner} | +| lock\_tokens | amount | {amount} | +| lock\_tokens | duration | {duration} | +| lock\_tokens | unlock\_time | {unlockTime} | +| message | action | lock\_tokens | +| message | sender | {owner} | +| transfer | recipient | {moduleAccount} | +| transfer | sender | {owner} | +| transfer | amount | {amount} | + +#### MsgBeginUnlocking + +| Type | Attribute Key | Attribute Value | +| ---------------| ------------------| ------------------| +| begin\_unlock | period\_lock\_id | {periodLockID} | +| begin\_unlock | owner | {owner} | +| begin\_unlock | amount | {amount} | +| begin\_unlock | duration | {duration} | +| begin\_unlock | unlock\_time | {unlockTime} | +| message | action | begin\_unlocking | +| message | sender | {owner} | + +#### MsgBeginUnlockingAll + +| Type | Attribute Key | Attribute Value | +| --------------------| ------------------| -----------------------| +| begin\_unlock\_all | owner | {owner} | +| begin\_unlock\_all | unlocked\_coins | {unlockedCoins} | +| begin\_unlock | period\_lock\_id | {periodLockID} | +| begin\_unlock | owner | {owner} | +| begin\_unlock | amount | {amount} | +| begin\_unlock | duration | {duration} | +| begin\_unlock | unlock\_time | {unlockTime} | +| message | action | begin\_unlocking\_all | +| message | sender | {owner} | + +### Endblocker + +#### Automatic withdraw when unlock time mature + +| Type | Attribute Key | Attribute Value | +| ----------------| ------------------| -----------------| +| message | action | unlock\_tokens | +| message | sender | {owner} | +| transfer\[\] | recipient | {owner} | +| transfer\[\] | sender | {moduleAccount} | +| transfer\[\] | amount | {unlockAmount} | +| unlock\[\] | period\_lock\_id | {owner} | +| unlock\[\] | owner | {lockID} | +| unlock\[\] | duration | {lockDuration} | +| unlock\[\] | unlock\_time | {unlockTime} | +| unlock\_tokens | owner | {owner} | +| unlock\_tokens | unlocked\_coins | {totalAmount} | + +## Keepers + +### Lockup Keeper + +Lockup keeper provides utility functions to store lock queues and query +locks. + +```go +// Keeper is the interface for lockup module keeper +type Keeper interface { + // GetModuleBalance Returns full balance of the module + GetModuleBalance(sdk.Context) sdk.Coins + // GetModuleLockedCoins Returns locked balance of the module + GetModuleLockedCoins(sdk.Context) sdk.Coins + // GetAccountUnlockableCoins Returns whole unlockable coins which are not withdrawn yet + GetAccountUnlockableCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins + // GetAccountUnlockingCoins Returns whole unlocking coins + GetAccountUnlockingCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins + // GetAccountLockedCoins Returns a locked coins that can't be withdrawn + GetAccountLockedCoins(sdk.Context, addr sdk.AccAddress) sdk.Coins + // GetAccountLockedPastTime Returns the total locks of an account whose unlock time is beyond timestamp + GetAccountLockedPastTime(sdk.Context, addr sdk.AccAddress, timestamp time.Time) []types.PeriodLock + // GetAccountUnlockedBeforeTime Returns the total unlocks of an account whose unlock time is before timestamp + GetAccountUnlockedBeforeTime(sdk.Context, addr sdk.AccAddress, timestamp time.Time) []types.PeriodLock + // GetAccountLockedPastTimeDenom is equal to GetAccountLockedPastTime but denom specific + GetAccountLockedPastTimeDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, timestamp time.Time) []types.PeriodLock + + // GetAccountLockedLongerDuration Returns account locked with duration longer than specified + GetAccountLockedLongerDuration(sdk.Context, addr sdk.AccAddress, duration time.Duration) []types.PeriodLock + // GetAccountLockedLongerDurationDenom Returns account locked with duration longer than specified with specific denom + GetAccountLockedLongerDurationDenom(sdk.Context, addr sdk.AccAddress, denom string, duration time.Duration) []types.PeriodLock + // GetLocksPastTimeDenom Returns the locks whose unlock time is beyond timestamp + GetLocksPastTimeDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, timestamp time.Time) []types.PeriodLock + // GetLocksLongerThanDurationDenom Returns the locks whose unlock duration is longer than duration + GetLocksLongerThanDurationDenom(ctx sdk.Context, addr sdk.AccAddress, denom string, duration time.Duration) []types.PeriodLock + // GetLockByID Returns lock from lockID + GetLockByID(sdk.Context, lockID uint64) (*types.PeriodLock, error) + // GetPeriodLocks Returns the period locks on pool + GetPeriodLocks(sdk.Context) ([]types.PeriodLock, error) + // UnlockAllUnlockableCoins Unlock all unlockable coins + UnlockAllUnlockableCoins(sdk.Context, account sdk.AccAddress) (sdk.Coins, error) + // LockTokens lock tokens from an account for specified duration + LockTokens(sdk.Context, owner sdk.AccAddress, coins sdk.Coins, duration time.Duration) (types.PeriodLock, error) + // AddTokensToLock locks more tokens into a lockup + AddTokensToLock(ctx sdk.Context, owner sdk.AccAddress, lockID uint64, coins sdk.Coins) (*types.PeriodLock, error) + // Lock is a utility to lock coins into module account + Lock(sdk.Context, lock types.PeriodLock) error + // Unlock is a utility to unlock coins from module account + Unlock(sdk.Context, lock types.PeriodLock) error + GetSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string) (*types.SyntheticLock, error) + GetAllSyntheticLockupsByLockup(ctx sdk.Context, lockID uint64) []types.SyntheticLock + GetAllSyntheticLockups(ctx sdk.Context) []types.SyntheticLock + // CreateSyntheticLockup create synthetic lockup with lock id and denom suffix + CreateSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string, unlockDuration time.Duration) error + // DeleteSyntheticLockup delete synthetic lockup with lock id and suffix + DeleteSyntheticLockup(ctx sdk.Context, lockID uint64, suffix string) error + DeleteAllMaturedSyntheticLocks(ctx sdk.Context) +``` + +### Lock Admin Keeper + +Lockup admin keeper provides god privilege functions to remove tokens +from locks and create new locks. + +```go +// AdminKeeper defines a god priviledge keeper functions to remove tokens from locks and create new locks +// For the governance system of token pools, we want a "ragequit" feature +// So governance changes will take 1 week to go into effect +// During that time, people can choose to "ragequit" which means they would leave the original pool +// and form a new pool with the old parameters but if they still had 2 months of lockup left, +// their liquidity still needs to be 2 month lockup-ed, just in the new pool +// And we need to replace their pool1 LP tokens with pool2 LP tokens with the same lock duration and end time + +type AdminKeeper interface { + Keeper + + // this unlock previous lockID and create a new lock with newCoins with same duration and endtime + Relock(sdk.Context, lockID uint64, newCoins sdk.Coins) error + // this unlock without time check with an admin priviledge + BreakLock(sdk.Context, lockID uint64) error +} +``` + +## Hooks + +In this section we describe the "hooks" that `lockup` module provide for +other modules. + +### Tokens Locked + +On lock/unlock events, lockup module execute hooks for other modules to +make following actions. + +``` go + OnTokenLocked(ctx sdk.Context, address sdk.AccAddress, lockID uint64, amount sdk.Coins, lockDuration time.Duration, unlockTime time.Time) + OnTokenUnlocked(ctx sdk.Context, address sdk.AccAddress, lockID uint64, amount sdk.Coins, lockDuration time.Duration, unlockTime time.Time) +``` + +## Parameters + +The lockup module contains the following parameters: + +| Key | Type | Example | +| ---------------------- | --------------- | ------- | + +Note: Currently no parameters are set for `lockup` module, we will need +to move lockable durations from incentives module to lockup module. + +## Endblocker + +### Withdraw tokens after unlock time mature + +Once time is over, endblocker withdraw coins from matured locks and +coins are sent from lockup `ModuleAccount`. + +**State modifications:** + +- Fetch all unlockable `PeriodLock`s that `Owner` has not withdrawn + yet +- Remove `PeriodLock` records from the state +- Transfer the tokens from lockup `ModuleAccount` to the + `MsgUnlockTokens.Owner`. + +### Remove synthetic locks after removal time mature + +For synthetic lockups, no coin movement is made, but lockup record and +reference queues are removed. + +**State modifications:** + +- Fetch all synthetic lockups that is matured +- Remove `SyntheticLock` records from the state along with reference + queues + ## Transactions ### lock-tokens @@ -112,6 +537,47 @@ osmosisd tx lockup begin-unlock-tokens --from=WALLET_NAME --chain-id=osmosis-1 - ## Queries +In this section we describe the queries required on grpc server. + +``` protobuf +// Query defines the gRPC querier service. +service Query { + // Return full balance of the module + rpc ModuleBalance(ModuleBalanceRequest) returns (ModuleBalanceResponse); + // Return locked balance of the module + rpc ModuleLockedAmount(ModuleLockedAmountRequest) returns (ModuleLockedAmountResponse); + + // Returns unlockable coins which are not withdrawn yet + rpc AccountUnlockableCoins(AccountUnlockableCoinsRequest) returns (AccountUnlockableCoinsResponse); + // Returns unlocking coins + rpc AccountUnlockingCoins(AccountUnlockingCoinsRequest) returns (AccountUnlockingCoinsResponse) {} + // Return a locked coins that can't be withdrawn + rpc AccountLockedCoins(AccountLockedCoinsRequest) returns (AccountLockedCoinsResponse); + + // Returns locked records of an account with unlock time beyond timestamp + rpc AccountLockedPastTime(AccountLockedPastTimeRequest) returns (AccountLockedPastTimeResponse); + // Returns locked records of an account with unlock time beyond timestamp excluding tokens started unlocking + rpc AccountLockedPastTimeNotUnlockingOnly(AccountLockedPastTimeNotUnlockingOnlyRequest) returns (AccountLockedPastTimeNotUnlockingOnlyResponse) {} + // Returns unlocked records with unlock time before timestamp + rpc AccountUnlockedBeforeTime(AccountUnlockedBeforeTimeRequest) returns (AccountUnlockedBeforeTimeResponse); + + // Returns lock records by address, timestamp, denom + rpc AccountLockedPastTimeDenom(AccountLockedPastTimeDenomRequest) returns (AccountLockedPastTimeDenomResponse); + // Returns lock record by id + rpc LockedByID(LockedRequest) returns (LockedResponse); + + // Returns account locked records with longer duration + rpc AccountLockedLongerDuration(AccountLockedLongerDurationRequest) returns (AccountLockedLongerDurationResponse); + // Returns account locked records with longer duration excluding tokens started unlocking + rpc AccountLockedLongerDurationNotUnlockingOnly(AccountLockedLongerDurationNotUnlockingOnlyRequest) returns (AccountLockedLongerDurationNotUnlockingOnlyResponse) {} + // Returns account's locked records for a denom with longer duration + rpc AccountLockedLongerDurationDenom(AccountLockedLongerDurationDenomRequest) returns (AccountLockedLongerDurationDenomResponse); + + // Returns account locked records with a specific duration + rpc AccountLockedDuration(AccountLockedDurationRequest) returns (AccountLockedDurationResponse); +} +``` + ### account-locked-beforetime Query an account's unlocked records after a specified time (UNIX) has passed @@ -640,4 +1106,4 @@ osmosisd query lockup total-locked-of-denom gamm/pool/2 --min-duration "336h" Which, at the time of this writing outputs `14106985399822075248947045` which is equivalent to `14106985.3998 gamm/pool/2` NOTE: As of this writing, there is a bug that defaults the min duration to days instead of seconds. Ensure you specify the time in seconds to get the correct response. -::: +::: \ No newline at end of file diff --git a/x/mint/spec/01_concepts.md b/x/mint/spec/01_concepts.md deleted file mode 100644 index 5ef8d53b5b0..00000000000 --- a/x/mint/spec/01_concepts.md +++ /dev/null @@ -1,25 +0,0 @@ -# Concepts - -The `x/mint` module is designed to handle the regular printing of new -tokens within a chain. The design taken within Osmosis is to - -- Mint new tokens once per epoch (default one week) -- To have a "Reductioning factor" every period, which reduces the - amount of rewards per epoch. (default: period is 3 years, where a - year is 52 epochs. The next period's rewards are 2/3 of the prior - period's rewards) - -## Reductioning factor - -This is a generalization over the Bitcoin style halvenings. Every year, -the amount of rewards issued per week will reduce by a governance -specified factor, instead of a fixed `1/2`. So -`RewardsPerEpochNextPeriod = ReductionFactor * CurrentRewardsPerEpoch)`. -When `ReductionFactor = 1/2`, the Bitcoin halvenings are recreated. We -default to having a reduction factor of `2/3`, and thus reduce rewards -at the end of every year by `33%`. - -The implication of this is that the total supply is finite, according to -the following formula: - -$$Total\ Supply = InitialSupply + EpochsPerPeriod * \frac{InitialRewardsPerEpoch}{1 - ReductionFactor} $$ diff --git a/x/mint/spec/02_state.md b/x/mint/spec/02_state.md deleted file mode 100644 index a0e3af4af37..00000000000 --- a/x/mint/spec/02_state.md +++ /dev/null @@ -1,44 +0,0 @@ -```{=html} - -``` - -# State - -## Minter - -The minter is a space for holding current rewards information. - -```go -type Minter struct { - EpochProvisions sdk.Dec // Rewards for the current epoch -} -``` - -## Params - -Minting params are held in the global params store. - -```go -type Params struct { - MintDenom string // type of coin to mint - GenesisEpochProvisions sdk.Dec // initial epoch provisions at genesis - EpochIdentifier string // identifier of epoch - ReductionPeriodInEpochs int64 // number of epochs between reward reductions - ReductionFactor sdk.Dec // reduction multiplier to execute on each period - DistributionProportions DistributionProportions // distribution_proportions defines the proportion of the minted denom - WeightedDeveloperRewardsReceivers []WeightedAddress // address to receive developer rewards - MintingRewardsDistributionStartEpoch int64 // start epoch to distribute minting rewards -} -``` - -## LastHalvenEpoch - -Last halven epoch stores the epoch number when the last reduction of -coin mint amount per epoch has happened. - -**TODO:** - -- Update the name to LastReductionEpoch as the reduction amount could - be set by governance. diff --git a/x/mint/spec/03_end_epoch.md b/x/mint/spec/03_end_epoch.md deleted file mode 100644 index 413ad85954a..00000000000 --- a/x/mint/spec/03_end_epoch.md +++ /dev/null @@ -1,34 +0,0 @@ -# Begin-Epoch - -Minting parameters are recalculated and inflation paid at the beginning -of each epoch. An epoch is signalled by x/epochs - -## NextEpochProvisions - -The target epoch provision is recalculated on each reduction period -(default 3 years). At the time of reduction, the current provision is -multiplied by reduction factor (default `2/3`), to calculate the -provisions for the next epoch. Consequently, the rewards of the next -period will be lowered by `1 - reduction factor`. - -``` go -func (m Minter) NextEpochProvisions(params Params) sdk.Dec { - return m.EpochProvisions.Mul(params.ReductionFactor) -} -``` - -## EpochProvision - -Calculate the provisions generated for each epoch based on current epoch -provisions. The provisions are then minted by the `mint` module's -`ModuleMinterAccount`. These rewards are transferred to a -`FeeCollector`, which handles distributing the rewards per the chains -needs. (See TODO.md for details) This fee collector is specified as the -`auth` module's `FeeCollector` `ModuleAccount`. - -``` go -func (m Minter) EpochProvision(params Params) sdk.Coin { - provisionAmt := m.EpochProvisions.QuoInt(sdk.NewInt(int64(params.EpochsPerYear))) - return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt()) -} -``` diff --git a/x/mint/spec/04_params.md b/x/mint/spec/04_params.md deleted file mode 100644 index 525aba33d69..00000000000 --- a/x/mint/spec/04_params.md +++ /dev/null @@ -1,42 +0,0 @@ -``` html - -``` - -# Parameters - -The minting module contains the following parameters: - -| Key | Type | Example | -| ------------------------------------------ | ------------ | -------------------------------------- | -| mint_denom | string | "uosmo" | -| genesis_epoch_provisions | string (dec) | "500000000" | -| epoch_identifier | string | "weekly" | -| reduction_period_in_epochs | int64 | 156 | -| reduction_factor | string (dec) | "0.6666666666666" | -| distribution_proportions.staking | string (dec) | "0.4" | -| distribution_proportions.pool_incentives | string (dec) | "0.3" | -| distribution_proportions.developer_rewards | string (dec) | "0.2" | -| distribution_proportions.community_pool | string (dec) | "0.1" | -| weighted_developer_rewards_receivers | array | [{"address": "osmoxx", "weight": "1"}] | -| minting_rewards_distribution_start_epoch | int64 | 10 - -**Notes** - -1. `mint_denom` defines denom for minting token - uosmo -2. `genesis_epoch_provisions` provides minting tokens per epoch at - genesis. -3. `epoch_identifier` defines the epoch identifier to be used for mint - module e.g. "weekly" -4. `reduction_period_in_epochs` defines the number of epochs to pass to - reduce mint amount -5. `reduction_factor` defines the reduction factor of tokens at every - `reduction_period_in_epochs` -6. `distribution_proportions` defines distribution rules for minted - tokens, when developer rewards address is empty, it distribute - tokens to community pool. -7. `weighted_developer_rewards_receivers` provides the addresses that - receives developer rewards by weight -8. `minting_rewards_distribution_start_epoch` defines the start epoch - of minting to make sure minting start after initial pools are set diff --git a/x/mint/spec/05_events.md b/x/mint/spec/05_events.md deleted file mode 100644 index c3f68284127..00000000000 --- a/x/mint/spec/05_events.md +++ /dev/null @@ -1,17 +0,0 @@ -```html - -``` - -# Events - -The minting module emits the following events: - -## End of Epoch - - Type Attribute Key Attribute Value - ------ ------------------- ------------------- - mint epoch\_number {epochNumber} - mint epoch\_provisions {epochProvisions} - mint amount {amount} diff --git a/x/mint/spec/README.md b/x/mint/spec/README.md index 4637aa61c22..c4b30dca5c1 100644 --- a/x/mint/spec/README.md +++ b/x/mint/spec/README.md @@ -9,15 +9,132 @@ Module uses time basis epochs supported by ```epochs``` module. ## Contents -1. **[Concept](01_concepts.md)** -2. **[State](02_state.md)** -3. **[End Epoch](03_end_epoch.md)** -4. **[Parameters](04_params.md)** -5. **[Events](05_events.md)** +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Begin Epoch](#begin-epoch)** +4. **[Parameters](#network-parameters)** +5. **[Events](#events)** +6. **[Transactions](#transaction)** +7. **[Queries](#queries)** -## Overview +## Concepts + +The `x/mint` module is designed to handle the regular printing of new +tokens within a chain. The design taken within Osmosis is to + +- Mint new tokens once per epoch (default one week) +- To have a "Reductioning factor" every period, which reduces the + amount of rewards per epoch. (default: period is 3 years, where a + year is 52 epochs. The next period's rewards are 2/3 of the prior + period's rewards) + +### Reductioning factor + +This is a generalization over the Bitcoin style halvenings. Every year, +the amount of rewards issued per week will reduce by a governance +specified factor, instead of a fixed `1/2`. So +`RewardsPerEpochNextPeriod = ReductionFactor * CurrentRewardsPerEpoch)`. +When `ReductionFactor = 1/2`, the Bitcoin halvenings are recreated. We +default to having a reduction factor of `2/3`, and thus reduce rewards +at the end of every year by `33%`. + +The implication of this is that the total supply is finite, according to +the following formula: + +`Total Supply = InitialSupply + EpochsPerPeriod * { {InitialRewardsPerEpoch} / {1 - ReductionFactor} }` + +## State + +### Minter + +The minter is a space for holding current rewards information. + +```go +type Minter struct { + EpochProvisions sdk.Dec // Rewards for the current epoch +} +``` + +### Params + +Minting params are held in the global params store. + +```go +type Params struct { + MintDenom string // type of coin to mint + GenesisEpochProvisions sdk.Dec // initial epoch provisions at genesis + EpochIdentifier string // identifier of epoch + ReductionPeriodInEpochs int64 // number of epochs between reward reductions + ReductionFactor sdk.Dec // reduction multiplier to execute on each period + DistributionProportions DistributionProportions // distribution_proportions defines the proportion of the minted denom + WeightedDeveloperRewardsReceivers []WeightedAddress // address to receive developer rewards + MintingRewardsDistributionStartEpoch int64 // start epoch to distribute minting rewards +} +``` + +### LastHalvenEpoch + +Last halven epoch stores the epoch number when the last reduction of +coin mint amount per epoch has happened. + +**TODO:** + +- Update the name to LastReductionEpoch as the reduction amount could + be set by governance. + +## Begin-Epoch + +Minting parameters are recalculated and inflation paid at the beginning +of each epoch. An epoch is signalled by x/epochs + +### NextEpochProvisions + +The target epoch provision is recalculated on each reduction period +(default 3 years). At the time of reduction, the current provision is +multiplied by reduction factor (default `2/3`), to calculate the +provisions for the next epoch. Consequently, the rewards of the next +period will be lowered by `1 - reduction factor`. + +``` go +func (m Minter) NextEpochProvisions(params Params) sdk.Dec { + return m.EpochProvisions.Mul(params.ReductionFactor) +} +``` + +### EpochProvision + +Calculate the provisions generated for each epoch based on current epoch +provisions. The provisions are then minted by the `mint` module's +`ModuleMinterAccount`. These rewards are transferred to a +`FeeCollector`, which handles distributing the rewards per the chains +needs. (See TODO.md for details) This fee collector is specified as the +`auth` module's `FeeCollector` `ModuleAccount`. + +``` go +func (m Minter) EpochProvision(params Params) sdk.Coin { + provisionAmt := m.EpochProvisions.QuoInt(sdk.NewInt(int64(params.EpochsPerYear))) + return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt()) +} +``` + +## Network Parameters + +The minting module contains the following parameters: + +| Key | Type | Example | +| ------------------------------------------ | ------------ | -------------------------------------- | +| mint_denom | string | "uosmo" | +| genesis_epoch_provisions | string (dec) | "500000000" | +| epoch_identifier | string | "weekly" | +| reduction_period_in_epochs | int64 | 156 | +| reduction_factor | string (dec) | "0.6666666666666" | +| distribution_proportions.staking | string (dec) | "0.4" | +| distribution_proportions.pool_incentives | string (dec) | "0.3" | +| distribution_proportions.developer_rewards | string (dec) | "0.2" | +| distribution_proportions.community_pool | string (dec) | "0.1" | +| weighted_developer_rewards_receivers | array | [{"address": "osmoxx", "weight": "1"}] | +| minting_rewards_distribution_start_epoch | int64 | 10 | -### Network Parameters Below are all the network parameters for the ```mint``` module: @@ -34,6 +151,37 @@ Below are all the network parameters for the ```mint``` module: - **```weighted_developer_rewards_receivers```** - Addresses that developer rewards will go to. The weight attached to an address is the percent of the developer rewards that the specific address will receive - **```minting_rewards_distribution_start_epoch```** - What epoch will start the rewards distribution to the aforementioned distribution categories +**Notes** + +1. `mint_denom` defines denom for minting token - uosmo +2. `genesis_epoch_provisions` provides minting tokens per epoch at + genesis. +3. `epoch_identifier` defines the epoch identifier to be used for mint + module e.g. "weekly" +4. `reduction_period_in_epochs` defines the number of epochs to pass to + reduce mint amount +5. `reduction_factor` defines the reduction factor of tokens at every + `reduction_period_in_epochs` +6. `distribution_proportions` defines distribution rules for minted + tokens, when developer rewards address is empty, it distribute + tokens to community pool. +7. `weighted_developer_rewards_receivers` provides the addresses that + receives developer rewards by weight +8. `minting_rewards_distribution_start_epoch` defines the start epoch + of minting to make sure minting start after initial pools are set + +## Events + +The minting module emits the following events: + +### End of Epoch + +| Type | Attribute Key | Attribute Value | +| ------ | ------------------- | -------------------| +| mint | epoch\_number | {epochNumber} | +| mint | epoch\_provisions | {epochProvisions}| +| mint | amount | {amount} | +

diff --git a/x/pool-incentives/spec/01_concepts.md b/x/pool-incentives/spec/01_concepts.md deleted file mode 100644 index 155315491ab..00000000000 --- a/x/pool-incentives/spec/01_concepts.md +++ /dev/null @@ -1,18 +0,0 @@ -```html - -``` - -# Concepts - -The purpose of the `pool incentives` module is to distribute incentives -to a pool's LPs. This assumes that pool's follow the interface from the -`x/gamm` module - -`Pool incentives` module doesn't directly distribute the rewards to the -LPs. When a pool is created, the `pool incentives` module creates a -`gauge` in the `incentives` module for every lock duration that exists. -Also, the `pool incentives` module takes a part of the minted inflation -from the mint module, and automatically distributes it to the various -selected gauges. diff --git a/x/pool-incentives/spec/02_state.md b/x/pool-incentives/spec/02_state.md deleted file mode 100644 index b54a4fa8f39..00000000000 --- a/x/pool-incentives/spec/02_state.md +++ /dev/null @@ -1,43 +0,0 @@ -```html - -``` - -# State - -### Genesis states - -```go -type GenesisState struct { - // params defines all the paramaters of the module. - Params Params - LockableDurations []time.Duration - DistrInfo *DistrInfo -} - -type Params struct { - // minted_denom is the denomination of the coin expected to be minted - // by the minting module. - // Pool-incentives module doesn’t actually mint the coin itself, - // but rather manages the distribution of coins that matches the defined minted_denom. - MintedDenom string - // allocation_ratio defines the proportion of the minted minted_denom - // that is to be allocated as pool incentives. - AllocationRatio github_com_cosmos_cosmos_sdk_types.Dec -} -``` - -Lockable durations can be set to the pool incentives module at genesis. -Every time a pool is created, the `pool incentives` module creates the -same amount of 'gauge' as there are lockable durations for the pool. - -Also in regards to the `Params`, when the mint module mints new tokens -to the fee collector at Begin Block, the `pool incentives` module takes -the token which matches the 'minted denom' from the fee collector. -Tokens are taken according to the 'allocationRatio', and are distributed -to each `DistrRecord` of the DistrInfo. For example, if the fee -collector holds 1000uatom and 2000 uosmo at Begin Block, and Params' -mintedDenom is set to uosmo, and AllocationRatio is set to 0.1, 200uosmo -will be taken from the fee collector and distributed to the -`DistrRecord`s. diff --git a/x/pool-incentives/spec/03_gov.md b/x/pool-incentives/spec/03_gov.md deleted file mode 100644 index 5c4ca056dea..00000000000 --- a/x/pool-incentives/spec/03_gov.md +++ /dev/null @@ -1,52 +0,0 @@ -```html - -``` - -# Gov - -`Pool Incentives` module uses the values set at genesis or values added -by chain governance to distribute part of the inflation minted by the -mint module to specified gauges. - -```go -type DistrInfo struct { - TotalWeight github_com_cosmos_cosmos_sdk_types.Int - Records []DistrRecord -} - -type DistrRecord struct { - GaugeId uint64 - Weight github_com_cosmos_cosmos_sdk_types.Int -} -``` - -`DistrInfo` internally manages the `DistrRecord` and total weight of all -`DistrRecord`. Governance can modify DistrInfo via -`UpdatePoolIncentivesProposal` proposal. - -### UpdatePoolIncentivesProposal - -```go -type UpdatePoolIncentivesProposal struct { - Title string - Description string - Records []DistrRecord -} -``` - -`UpdatePoolIncentivesProposal` can be used by governance to update -`DistrRecord`s. - -```shell -osmosisd tx gov submit-proposal update-pool-incentives [gaugeIds] [weights] -``` - -Proposals can be proposed in using the CLI command format above.\ -For example, to designate 100 weight to gauge id 2 and 200 weight to -gauge id 3, the following command can be used. - -```shell -osmosisd tx gov submit-proposal update-pool-incentives 2,3 100,200 -``` diff --git a/x/pool-incentives/spec/README.md b/x/pool-incentives/spec/README.md index 75a5ce0a5b0..dd8cd501560 100644 --- a/x/pool-incentives/spec/README.md +++ b/x/pool-incentives/spec/README.md @@ -7,9 +7,110 @@ The `pool-incentives` module is separate but related to the `incentives` module. ## Contents -1. **[Concept](01_concepts.md)** -2. **[State](02_state.md)** -3. **[Governance](03_gov.md)** +1. **[Concept](#concepts)** +2. **[State](#state)** +3. **[Governance](#gov)** +4. **[Transactions](#transactions)** +5. **[Queries](#queries)** + +## Concepts + +The purpose of the `pool incentives` module is to distribute incentives +to a pool's LPs. This assumes that pool's follow the interface from the +`x/gamm` module + +`Pool incentives` module doesn't directly distribute the rewards to the +LPs. When a pool is created, the `pool incentives` module creates a +`gauge` in the `incentives` module for every lock duration that exists. +Also, the `pool incentives` module takes a part of the minted inflation +from the mint module, and automatically distributes it to the various +selected gauges. + +## State + +#### Genesis states + +```go +type GenesisState struct { + // params defines all the paramaters of the module. + Params Params + LockableDurations []time.Duration + DistrInfo *DistrInfo +} + +type Params struct { + // minted_denom is the denomination of the coin expected to be minted + // by the minting module. + // Pool-incentives module doesn’t actually mint the coin itself, + // but rather manages the distribution of coins that matches the defined minted_denom. + MintedDenom string + // allocation_ratio defines the proportion of the minted minted_denom + // that is to be allocated as pool incentives. + AllocationRatio github_com_cosmos_cosmos_sdk_types.Dec +} +``` + +Lockable durations can be set to the pool incentives module at genesis. +Every time a pool is created, the `pool incentives` module creates the +same amount of 'gauge' as there are lockable durations for the pool. + +Also in regards to the `Params`, when the mint module mints new tokens +to the fee collector at Begin Block, the `pool incentives` module takes +the token which matches the 'minted denom' from the fee collector. +Tokens are taken according to the 'allocationRatio', and are distributed +to each `DistrRecord` of the DistrInfo. For example, if the fee +collector holds 1000uatom and 2000 uosmo at Begin Block, and Params' +mintedDenom is set to uosmo, and AllocationRatio is set to 0.1, 200uosmo +will be taken from the fee collector and distributed to the +`DistrRecord`s. + +## Gov + +`Pool Incentives` module uses the values set at genesis or values added +by chain governance to distribute part of the inflation minted by the +mint module to specified gauges. + +```go +type DistrInfo struct { + TotalWeight github_com_cosmos_cosmos_sdk_types.Int + Records []DistrRecord +} + +type DistrRecord struct { + GaugeId uint64 + Weight github_com_cosmos_cosmos_sdk_types.Int +} +``` + +`DistrInfo` internally manages the `DistrRecord` and total weight of all +`DistrRecord`. Governance can modify DistrInfo via +`UpdatePoolIncentivesProposal` proposal. + +#### UpdatePoolIncentivesProposal + +```go +type UpdatePoolIncentivesProposal struct { + Title string + Description string + Records []DistrRecord +} +``` + +`UpdatePoolIncentivesProposal` can be used by governance to update +`DistrRecord`s. + +```shell +osmosisd tx gov submit-proposal update-pool-incentives [gaugeIds] [weights] +``` + +Proposals can be proposed in using the CLI command format above.\ +For example, to designate 100 weight to gauge id 2 and 200 weight to +gauge id 3, the following command can be used. + +```shell +osmosisd tx gov submit-proposal update-pool-incentives 2,3 100,200 +``` + ## Transactions @@ -179,8 +280,6 @@ An example output: ``` ::: - - ### gauge-ids Query the gauge ids (by duration) by pool id From 4cf7a58ce8c13ebb476550226467f3854ce9184a Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Thu, 26 May 2022 12:23:41 -0400 Subject: [PATCH 19/26] chore: Increase CalcJoinPoolShares Test Coverage (#1430) --- x/gamm/pool-models/balancer/amm.go | 23 +++- x/gamm/pool-models/balancer/amm_test.go | 112 +++++++++++++++++-- x/gamm/pool-models/balancer/balancer_pool.go | 8 ++ x/gamm/pool-models/balancer/util_test.go | 14 ++- x/gamm/types/pool.go | 1 + 5 files changed, 139 insertions(+), 19 deletions(-) diff --git a/x/gamm/pool-models/balancer/amm.go b/x/gamm/pool-models/balancer/amm.go index a8e6c9a9b40..620ec50bf83 100644 --- a/x/gamm/pool-models/balancer/amm.go +++ b/x/gamm/pool-models/balancer/amm.go @@ -256,27 +256,34 @@ func (p *Pool) JoinPool(_ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) ( return numShares, nil } -func (p *Pool) CalcJoinPoolShares(_ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) { +func (p *Pool) CalcJoinPoolShares(_ sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) { poolAssets := p.GetAllPoolAssets() poolAssetsByDenom := make(map[string]PoolAsset) for _, poolAsset := range poolAssets { poolAssetsByDenom[poolAsset.Token.Denom] = poolAsset } + totalShares := p.GetTotalShares() if tokensIn.Len() == 1 { numShares, err = p.calcSingleAssetJoin(tokensIn[0], swapFee, poolAssetsByDenom[tokensIn[0].Denom], totalShares) + if err != nil { + return sdk.ZeroInt(), sdk.NewCoins(), err + } + newLiquidity = tokensIn - return numShares, newLiquidity, err + + return numShares, newLiquidity, nil } else if tokensIn.Len() != p.NumAssets() { - return sdk.ZeroInt(), sdk.NewCoins(), errors.New( - "balancer pool only supports LP'ing with one asset, or all assets in pool") + return sdk.ZeroInt(), sdk.NewCoins(), errors.New("balancer pool only supports LP'ing with one asset or all assets in pool") } - // Add all exact coins we can (no swap). ctx arg doesn't matter for Balancer + + // Add all exact coins we can (no swap). ctx arg doesn't matter for Balancer. numShares, remCoins, err := cfmm_common.MaximalExactRatioJoin(p, sdk.Context{}, tokensIn) if err != nil { return sdk.ZeroInt(), sdk.NewCoins(), err } + // update liquidity for accurate calcSingleAssetJoin calculation newLiquidity = tokensIn.Sub(remCoins) for _, coin := range newLiquidity { @@ -284,19 +291,23 @@ func (p *Pool) CalcJoinPoolShares(_ctx sdk.Context, tokensIn sdk.Coins, swapFee poolAsset.Token.Amount = poolAssetsByDenom[coin.Denom].Token.Amount.Add(coin.Amount) poolAssetsByDenom[coin.Denom] = poolAsset } + totalShares = totalShares.Add(numShares) - // if there are coins that couldn't be perfectly joined, do single asset joins for each of them. + // If there are coins that couldn't be perfectly joined, do single asset joins + // for each of them. if !remCoins.Empty() { for _, coin := range remCoins { newShares, err := p.calcSingleAssetJoin(coin, swapFee, poolAssetsByDenom[coin.Denom], totalShares) if err != nil { return sdk.ZeroInt(), sdk.NewCoins(), err } + newLiquidity = newLiquidity.Add(coin) numShares = numShares.Add(newShares) } } + return numShares, newLiquidity, nil } diff --git a/x/gamm/pool-models/balancer/amm_test.go b/x/gamm/pool-models/balancer/amm_test.go index 326f8dea838..c4ebe433723 100644 --- a/x/gamm/pool-models/balancer/amm_test.go +++ b/x/gamm/pool-models/balancer/amm_test.go @@ -184,13 +184,7 @@ func TestCalculateAmountOutAndIn_InverseRelationship(t *testing.T) { exitFeeDec, err := sdk.NewDecFromStr("0") require.NoError(t, err) - pool := createTestPool(t, []balancer.PoolAsset{ - poolAssetOut, - poolAssetIn, - }, - swapFeeDec, - exitFeeDec, - ) + pool := createTestPool(t, swapFeeDec, exitFeeDec, poolAssetOut, poolAssetIn) require.NotNil(t, pool) initialOut := sdk.NewInt64Coin(poolAssetOut.Token.Denom, tc.initialCalcOut) @@ -324,3 +318,107 @@ func TestCalcSingleAssetInAndOut_InverseRelationship(t *testing.T) { } } } + +func TestCalcJoinPoolShares(t *testing.T) { + testCases := []struct { + name string + swapFee sdk.Dec + poolAssets []balancer.PoolAsset + tokensIn sdk.Coins + expectErr bool + expectShares sdk.Int + expectLiq sdk.Coins + }{ + { + name: "equal weights with zero swap fee", + swapFee: sdk.MustNewDecFromStr("0"), + poolAssets: []balancer.PoolAsset{ + { + Token: sdk.NewInt64Coin("uosmo", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + { + Token: sdk.NewInt64Coin("uatom", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + }, + tokensIn: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + expectErr: false, + expectShares: sdk.NewInt(2499999968800), + expectLiq: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + }, + { + name: "equal weights with 0.001 swap fee", + swapFee: sdk.MustNewDecFromStr("0.001"), + poolAssets: []balancer.PoolAsset{ + { + Token: sdk.NewInt64Coin("uosmo", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + { + Token: sdk.NewInt64Coin("uatom", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + }, + tokensIn: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + expectErr: false, + expectShares: sdk.NewInt(2498749968800), + expectLiq: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + }, + { + name: "equal weights with 0.1 swap fee", + swapFee: sdk.MustNewDecFromStr("0.1"), + poolAssets: []balancer.PoolAsset{ + { + Token: sdk.NewInt64Coin("uosmo", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + { + Token: sdk.NewInt64Coin("uatom", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + }, + tokensIn: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + expectErr: false, + expectShares: sdk.NewInt(2374999971800), + expectLiq: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + }, + { + name: "equal weights with 0.99 swap fee", + swapFee: sdk.MustNewDecFromStr("0.99"), + poolAssets: []balancer.PoolAsset{ + { + Token: sdk.NewInt64Coin("uosmo", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + { + Token: sdk.NewInt64Coin("uatom", 1_000_000_000_000), + Weight: sdk.NewInt(100), + }, + }, + tokensIn: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + expectErr: false, + expectShares: sdk.NewInt(1262499992100), + expectLiq: sdk.NewCoins(sdk.NewInt64Coin("uosmo", 50_000)), + }, + } + + for _, tc := range testCases { + tc := tc + + t.Run(tc.name, func(t *testing.T) { + pool := createTestPool(t, tc.swapFee, sdk.MustNewDecFromStr("0"), tc.poolAssets...) + + shares, liquidity, err := pool.CalcJoinPoolShares(sdk.Context{}, tc.tokensIn, tc.swapFee) + if tc.expectErr { + require.Error(t, err) + require.Equal(t, sdk.ZeroInt(), shares) + require.Equal(t, sdk.NewCoins(), liquidity) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectShares, shares) + require.Equal(t, tc.expectLiq, liquidity) + } + }) + } +} diff --git a/x/gamm/pool-models/balancer/balancer_pool.go b/x/gamm/pool-models/balancer/balancer_pool.go index 8e6299164ed..f122fa630fb 100644 --- a/x/gamm/pool-models/balancer/balancer_pool.go +++ b/x/gamm/pool-models/balancer/balancer_pool.go @@ -470,6 +470,14 @@ func (pa Pool) IsActive(ctx sdk.Context) bool { return true } +func NewPoolParams(swapFee, exitFee sdk.Dec, params *SmoothWeightChangeParams) PoolParams { + return PoolParams{ + SwapFee: swapFee, + ExitFee: exitFee, + SmoothWeightChangeParams: params, + } +} + func (params PoolParams) Validate(poolWeights []PoolAsset) error { if params.ExitFee.IsNegative() { return types.ErrNegativeExitFee diff --git a/x/gamm/pool-models/balancer/util_test.go b/x/gamm/pool-models/balancer/util_test.go index 5a3de172476..e114e8421b2 100644 --- a/x/gamm/pool-models/balancer/util_test.go +++ b/x/gamm/pool-models/balancer/util_test.go @@ -15,12 +15,14 @@ import ( "github.com/osmosis-labs/osmosis/v7/x/gamm/types" ) -func createTestPool(t *testing.T, poolAssets []balancer.PoolAsset, swapFee, exitFee sdk.Dec) types.PoolI { - pool, err := balancer.NewBalancerPool(1, balancer.PoolParams{ - SwapFee: swapFee, - ExitFee: exitFee, - }, poolAssets, "", time.Now()) - +func createTestPool(t *testing.T, swapFee, exitFee sdk.Dec, poolAssets ...balancer.PoolAsset) types.PoolI { + pool, err := balancer.NewBalancerPool( + 1, + balancer.NewPoolParams(swapFee, exitFee, nil), + poolAssets, + "", + time.Now(), + ) require.NoError(t, err) return &pool diff --git a/x/gamm/types/pool.go b/x/gamm/types/pool.go index 0b5f2a48308..9f8081784a4 100644 --- a/x/gamm/types/pool.go +++ b/x/gamm/types/pool.go @@ -58,6 +58,7 @@ type PoolI interface { // It is up to pool implementation if they support LP'ing at arbitrary ratios, or a subset of ratios. // Pools are expected to guarantee LP'ing at the exact ratio, and single sided LP'ing. JoinPool(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, err error) + // CalcJoinPoolShares returns how many LP shares JoinPool would return on these arguments. // This does not mutate the pool, or state. CalcJoinPoolShares(ctx sdk.Context, tokensIn sdk.Coins, swapFee sdk.Dec) (numShares sdk.Int, newLiquidity sdk.Coins, err error) From b206663df2b8d13877a591a9061870ef83d719a3 Mon Sep 17 00:00:00 2001 From: Niccolo Raspa Date: Thu, 26 May 2022 19:30:04 +0200 Subject: [PATCH 20/26] Add osmobuilder image to create binaries for amd64 and arm64 for a release (#1584) Closes: #1550 ## What is the purpose of the change This PR proposes a new release process that: - creates the `osmosisd` binaries for `arm64` and `amd64` statically linked agains `libcosmwasm` - creates a `releases/` folder with the binaries, archives, source code and sha256checksum To use it, make sure to be in the root of the repository and run: ```bash git checkout make -f contrib/images/osmobuilder/Makefile release ``` This will create a `release/` folder with all the appropriate files. To statically link the binary for different architecture the trick is to build the binary while building the docker image via `docker buildx`. This process however it's slow when building `arm64` from `amd64` (or viceversa) ## Brief Changelog - Add `osmobuilder` image to build the images ## Testing and Verifying To use it, make sure to be in the root of the repository and run: ```bash git checkout make -f contrib/images/osmobuilder/Makefile release ``` ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no - How is the feature or change documented? not applicable --- contrib/images/osmobuilder/Dockerfile | 39 +++++++++++ contrib/images/osmobuilder/Makefile | 97 +++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 contrib/images/osmobuilder/Dockerfile create mode 100644 contrib/images/osmobuilder/Makefile diff --git a/contrib/images/osmobuilder/Dockerfile b/contrib/images/osmobuilder/Dockerfile new file mode 100644 index 00000000000..db30f9a4e8f --- /dev/null +++ b/contrib/images/osmobuilder/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1 + +# -------------------------------------------------------- +# Builder +# -------------------------------------------------------- + +FROM golang:1.18.2-alpine3.15 as build + +ARG NAME="osmosis" +ARG APP_NAME="osmosisd" +ARG VERSION +ARG COMMIT +ARG COSMWASM_VERSION="v1.0.0" +ARG BUILD_TAGS="netgo ledger muslc" + +RUN set -eux; apk add --no-cache ca-certificates build-base; +RUN apk add git +# Needed by github.com/zondax/hid +RUN apk add linux-headers + +WORKDIR /osmosis +COPY . /osmosis + +# CosmWasm: see https://github.com/CosmWasm/wasmvm/releases +ADD https://github.com/CosmWasm/wasmvm/releases/download/$COSMWASM_VERSION/libwasmvm_muslc.aarch64.a /lib/libwasmvm_muslc.aarch64.a +ADD https://github.com/CosmWasm/wasmvm/releases/download/$COSMWASM_VERSION/libwasmvm_muslc.x86_64.a /lib/libwasmvm_muslc.x86_64.a + +# CosmWasm: copy the right library according to architecture. The final location will be found by the linker flag `-lwasmvm_muslc` +RUN cp /lib/libwasmvm_muslc.$(uname -m).a /lib/libwasmvm_muslc.a + +RUN go build \ + -mod=readonly \ + -tags "$BUILD_TAGS" \ + -ldflags "-X github.com/osmosis-labs/osmosis/version.Name=$NAME -X github.com/osmosis-labs/osmosis/version.AppName=$APP_NAME -X github.com/osmosis-labs/osmosis/version.Version=$VERSION -X github.com/osmosis-labs/osmosis/version.Commit=$COMMIT -X github.com/osmosis-labs/osmosis/version.BuildTags='netgo,ledger,muslc' -w -s -linkmode=external -extldflags '-Wl,-z,muldefs -static'" \ + -trimpath \ + -o /osmosis/build/ \ + ./... + +ENTRYPOINT ["ash"] \ No newline at end of file diff --git a/contrib/images/osmobuilder/Makefile b/contrib/images/osmobuilder/Makefile new file mode 100644 index 00000000000..c38c057b5a9 --- /dev/null +++ b/contrib/images/osmobuilder/Makefile @@ -0,0 +1,97 @@ +# Script to automatically build binaries +# for linux/amd64, linux/arm64 + +# It builds a osmosisd binary while build the docker image +# in order to create a single binary statically linked to cosmwasm + +# Usage: +# +# Create a new release by running from the root of the repository: +# make -f contrib/images/osmobuilder/Makefile release +# +# A release/ folder will be created with the appropriate files + +VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') +COMMIT := $(shell git log -1 --format='%H') +COSMWASM_VERSION := "v1.0.0" +BUILD_TAGS := "netgo ledger muslc" + +IMAGE:=osmobuilder:$(VERSION) + +.PHONY: create-dockerx-builder build-binary-amd64 get-binary-amd64 + +release: get-binary-amd64 get-binary-arm64 git sha + +# Run `create-dockerx-builder` to create the builder first +create-dockerx-builder: + docker buildx create --name osmobuilder || true + +# Build image for amd64 architecture +build-binary-amd64: create-dockerx-builder + docker buildx use osmobuilder + docker buildx build \ + --platform linux/amd64 \ + --build-arg VERSION=$(VERSION) \ + --build-arg COMMIT=$(COMMIT) \ + --build-arg COSMWASM_VERSION=$(COSMWASM_VERSION) \ + --build-arg BUILD_TAGS=$(BUILD_TAGS) \ + -t $(IMAGE)-amd64 \ + --load \ + --no-cache \ + --progress plain \ + -f contrib/images/osmobuilder/Dockerfile . + +# Get binary from image for amd64 architecture +get-binary-amd64: build-binary-amd64 + mkdir -p release/ + docker rm -f osmobinary || true + docker create -ti --name osmobinary $(IMAGE)-amd64 + docker cp osmobinary:/osmosis/build/osmosisd release/osmosis-$(VERSION)-linux-amd64 + tar -zcvf release/osmosis-$(VERSION)-linux-amd64.tar.gz release/osmosis-$(VERSION)-linux-amd64 + docker rm -f osmobinary + +# Build image for arm64 architecture +build-binary-arm64: create-dockerx-builder + docker buildx use osmobuilder + docker buildx build \ + --platform linux/arm64 \ + --build-arg VERSION=$(VERSION) \ + --build-arg COMMIT=$(COMMIT) \ + --build-arg COSMWASM_VERSION=$(COSMWASM_VERSION) \ + --build-arg BUILD_TAGS=$(BUILD_TAGS) \ + -t $(IMAGE)-arm64 \ + --load \ + --no-cache \ + --progress plain \ + -f contrib/images/osmobuilder/Dockerfile . + +# Get binary from image for arm64 architecture +get-binary-arm64: build-binary-arm64 + mkdir -p release/ + docker rm -f osmobinary || true + docker create -ti --name osmobinary $(IMAGE)-arm64 + docker cp osmobinary:/osmosis/build/osmosisd release/osmosis-$(VERSION)-linux-arm64 + tar -zcvf release/osmosis-$(VERSION)-linux-arm64.tar.gz release/osmosis-$(VERSION)-linux-arm64 + docker rm -f osmobinary + +# Calculate sha +sha: + mkdir -p release/ + rm -f release/sha256sum.txt + sha256sum release/* | sed 's#release/##g' > release/sha256sum.txt + +# Create git archive +git: + mkdir -p release/ + git archive \ + --format zip \ + --prefix "osmosis-$(VERSION)/" \ + -o "release/Source code.zip" \ + HEAD + + git archive \ + --format tar.gz \ + --prefix "osmosis-$(VERSION)/" \ + -o "release/Source code.tar.gz" \ + HEAD + From d0b92518021153b3d1f1f2ba94c4fffdf982ea5c Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 26 May 2022 21:45:28 +0200 Subject: [PATCH 21/26] Remove CodeQL from CI (#1592) ## What is the purpose of the change Removes CodeQL. It takes forever, and I'm unconvinced its adding anything of value. We should just get Informal's gosec fixed, and use that imo. If folks feel like CodeQL is useful, we can investigate speedup strategies in #1578 , but atm I'm not sure its helping us any. ## Documentation and Release Note - Does this pull request introduce a new feature or user-facing behavior changes? no - Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`? no, its CI - How is the feature or change documented? not applicable --- .github/workflows/codeql-analysis.yml | 62 --------------------------- 1 file changed, 62 deletions(-) delete mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 065a58dafe9..00000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: "CodeQL" - -on: - push: - branches: - - main - - v[0-9]** - pull_request: - # The branches below must be a subset of the branches above - branches: - - main - schedule: - - cron: '43 20 * * 5' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'go', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://git.io/codeql-language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 From 74c9bd5688a3057601567c77607fd7dcaa5f5474 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Thu, 26 May 2022 22:39:29 +0200 Subject: [PATCH 22/26] Add back balances_from_state_export_command (#1594) Co-authored-by: Aleksandr Bezobchuk --- ..._state_export.go.history => balances_from_state_export.go} | 4 ++-- cmd/osmosisd/cmd/root.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename cmd/osmosisd/cmd/{balances_from_state_export.go.history => balances_from_state_export.go} (99%) diff --git a/cmd/osmosisd/cmd/balances_from_state_export.go.history b/cmd/osmosisd/cmd/balances_from_state_export.go similarity index 99% rename from cmd/osmosisd/cmd/balances_from_state_export.go.history rename to cmd/osmosisd/cmd/balances_from_state_export.go index ec95377ffaa..e9fbf225728 100644 --- a/cmd/osmosisd/cmd/balances_from_state_export.go.history +++ b/cmd/osmosisd/cmd/balances_from_state_export.go @@ -270,7 +270,7 @@ Example: } // convert balances to underlying coins and sum up balances to total balance - for addr, account := range snapshotAccs { + for _, account := range snapshotAccs { // All pool shares are in liquid balances OR bonded balances (locked), // therefore underlyingCoinsForSelectPools on liquidBalances + bondedBalances // will include everything that is in one of those two pools. @@ -282,7 +282,7 @@ Example: Add(account.LiquidBalances...). Add(sdk.NewCoin(appparams.BaseCoinUnit, account.Staked)). Add(sdk.NewCoin(appparams.BaseCoinUnit, account.UnbondingStake)). - Add(account.Bonded...). + Add(account.Bonded...) } snapshot := DeriveSnapshot{ diff --git a/cmd/osmosisd/cmd/root.go b/cmd/osmosisd/cmd/root.go index a626f0c564b..2304d159b5e 100644 --- a/cmd/osmosisd/cmd/root.go +++ b/cmd/osmosisd/cmd/root.go @@ -148,6 +148,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) { InitCmd(osmosis.ModuleBasics, osmosis.DefaultNodeHome), genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, osmosis.DefaultNodeHome), genutilcli.MigrateGenesisCmd(), + ExportDeriveBalancesCmd(), AddGenesisAccountCmd(osmosis.DefaultNodeHome), AddGenesisWasmMsgCmd(osmosis.DefaultNodeHome), genutilcli.GenTxCmd(osmosis.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, osmosis.DefaultNodeHome), From 92db85c11d674ee1f062f6bbb360fc9d6db755b3 Mon Sep 17 00:00:00 2001 From: Niccolo Raspa Date: Fri, 27 May 2022 15:42:19 +0200 Subject: [PATCH 23/26] [e2e] Add GitHub cache to docker build (#1602) --- .github/workflows/test.yml | 72 ++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9dfe7f4f18e..d290ac523bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,7 @@ on: branches: - "main" - "v[0-9]**" + workflow_dispatch: jobs: should_run_go_test: @@ -15,7 +16,8 @@ jobs: outputs: should_skip: ${{ steps.skip_check.outputs.should_skip }} steps: - - id: skip_check + - + id: skip_check uses: fkirc/skip-duplicate-actions@master with: cancel_others: "true" # workflow-runs from outdated commits will be cancelled. @@ -23,7 +25,8 @@ jobs: skip_after_successful_duplicate: "true" paths: '["**/*.go", "**/*.mod", "**/*.sum"]' do_not_skip: '["pull_request", "workflow_dispatch", "schedule"]' - - name: Skipping test + - + name: Skipping test run: echo Should I skip tests? ${{ steps.skip_check.outputs.should_skip }} go_test: @@ -31,15 +34,19 @@ jobs: if: ${{ needs.should_run_test.outputs.should_skip != 'true' }} runs-on: ubuntu-latest steps: - - name: Check out repository code + - + name: Check out repository code uses: actions/checkout@v2 - - name: Setup Golang + - + name: Setup Golang uses: actions/setup-go@v2.1.4 with: go-version: 1.18 - - name: Display go version + - + name: Display go version run: go version - - name: Get data from build cache + - + name: Get data from build cache uses: actions/cache@v2 with: # In order: @@ -55,27 +62,35 @@ jobs: key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go-${{ matrix.go-version }}- - - name: Run all tests - run: | - make test-cover - - name: Codecov + - + name: Run all tests + run: make test-cover + - + name: Codecov uses: codecov/codecov-action@v1.5.2 e2e_test: runs-on: ubuntu-latest timeout-minutes: 25 steps: - - uses: actions/setup-go@v2.2.0 + - + name: Setup Go + uses: actions/setup-go@v2.2.0 with: go-version: 1.18 - - uses: actions/checkout@v2 - - uses: technote-space/get-diff-action@v6.0.1 + - + name: Check out repository code + uses: actions/checkout@v2 + - + name: Get git diff + uses: technote-space/get-diff-action@v6.0.1 with: PATTERNS: | **/**.go go.mod go.sum - - name: Get data from build cache + - + name: Get data from build cache uses: actions/cache@v2 with: # In order: @@ -91,11 +106,24 @@ jobs: key: ${{ runner.os }}-go-docker-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go-docker-${{ matrix.go-version }}- - - name: Build Docker Image - run: | - make docker-build-debug - if: env.GIT_DIFF - - name: Test E2E and Upgrade - run: | - make test-e2e - if: env.GIT_DIFF \ No newline at end of file + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Build e2e image + uses: docker/build-push-action@v3 + with: + load: true + context: . + tags: osmosis:debug + # Use experimental Cache backend API: https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#cache-backend-api + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + BASE_IMG_TAG=debug + - + name: Test e2e and Upgrade + run: make test-e2e From e538f27ba1092a26c0b14151ca321d2cbe20b04e Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 27 May 2022 16:26:58 +0200 Subject: [PATCH 24/26] Tokenfactory updates (#1585) * Start on rename of nonce -> subdenom, adding a 64 byte nonce limitation, and moving README * Document max subdenom length * Correct main proto errors for stableswap * Enforce maximum lengths * Finish subdenom rename * Fix compilation * Update structs (tests still need updates) * Delete accidentally committed code * Fix tests (still need to fix the bindings contract tests) * Update osmosis bindings * Update bindings wasm * Working except for create denom not setting the correct admin * Improve error message, fix some tests * Update app/wasm/bindings/msg.go * Burn logic in backend * Fix panic that was occuring * Fix 0 check * Add temporary safety check until IBC bug is resolved --- app/wasm/bindings/msg.go | 45 +++++-- app/wasm/bindings/query.go | 4 +- app/wasm/message_plugin.go | 59 ++++++--- app/wasm/query_plugin.go | 6 +- app/wasm/test/custom_msg_test.go | 43 +++--- app/wasm/test/custom_query_test.go | 4 +- app/wasm/test/messages_test.go | 77 +++++------ app/wasm/test/queries_test.go | 14 +- app/wasm/testdata/osmo_reflect.wasm | Bin 281948 -> 286792 bytes app/wasm/testdata/version.txt | 2 +- docs/core/proto-docs.md | 67 +++++++++- go.sum | 122 ++++++++++++++++++ .../gamm/pool-models/stableswap/tx.proto | 6 +- proto/osmosis/tokenfactory/v1beta1/tx.proto | 11 +- x/gamm/pool-models/stableswap/msgs.go | 10 +- x/gamm/pool-models/stableswap/tx.pb.go | 103 +++++++-------- x/tokenfactory/{spec => }/README.md | 42 +++++- x/tokenfactory/client/cli/tx.go | 2 +- x/tokenfactory/keeper/createdenom.go | 12 +- x/tokenfactory/keeper/createdenom_test.go | 2 +- x/tokenfactory/keeper/genesis.go | 4 +- x/tokenfactory/keeper/msg_server.go | 8 +- x/tokenfactory/types/denoms.go | 37 ++++-- x/tokenfactory/types/denoms_test.go | 6 +- x/tokenfactory/types/errors.go | 5 + x/tokenfactory/types/events.go | 2 +- x/tokenfactory/types/expected_keepers.go | 2 + x/tokenfactory/types/msgs.go | 12 +- x/tokenfactory/types/tx.pb.go | 95 +++++++------- 29 files changed, 557 insertions(+), 245 deletions(-) rename x/tokenfactory/{spec => }/README.md (55%) diff --git a/app/wasm/bindings/msg.go b/app/wasm/bindings/msg.go index 182c0607283..483b479523f 100644 --- a/app/wasm/bindings/msg.go +++ b/app/wasm/bindings/msg.go @@ -4,28 +4,49 @@ import sdk "github.com/cosmos/cosmos-sdk/types" type OsmosisMsg struct { /// Contracts can create denoms, namespaced under the contract's address. - //A contract may create any number of independent sub-denoms. + /// A contract may create any number of independent sub-denoms. CreateDenom *CreateDenom `json:"create_denom,omitempty"` - /// Contracts can mint native tokens for an existing denom - /// namespaced under the contract's address. + /// Contracts can change the admin of a denom that they are the admin of. + ChangeAdmin *ChangeAdmin `json:"change_admin,omitempty"` + /// Contracts can mint native tokens for an existing factory denom + /// that they are the admin of. MintTokens *MintTokens `json:"mint_tokens,omitempty"` + /// Contracts can burn native tokens for an existing factory denom + /// that they are the admin of. + /// Currently, the burn from address must be the admin contract. + BurnTokens *BurnTokens `json:"burn_tokens,omitempty"` /// Swap over one or more pools Swap *SwapMsg `json:"swap,omitempty"` } +/// CreateDenom creates a new factory denom, of denomination: +/// factory/{creating contract address}/{Subdenom} +/// Subdenom can be of length at most 44 characters, in [0-9a-zA-Z./] +/// The (creating contract address, subdenom) pair must be unique. +/// The created denom's admin is the creating contract address, +/// but this admin can be changed using the ChangeAdmin binding. type CreateDenom struct { - /// Sub_denoms (nonces) are validated as part of the full denomination. - /// Can be up to 128 - prefix length (currently 7) - bech32 address length (4 (osmo) + 39) - number of separators (2) = - /// 76 "alphanumeric" (https://github.com/cosmos/cosmos-sdk/blob/2646b474c7beb0c93d4fafd395ef345f41afc251/types/coin.go#L677) - /// characters long. - /// Empty sub-denoms are valid. The token will then be prefix + contract address, i.e. "factory//" - SubDenom string `json:"sub_denom"` + Subdenom string `json:"subdenom"` +} + +/// ChangeAdmin changes the admin for a factory denom. +/// If the NewAdminAddress is empty, the denom has no admin. +type ChangeAdmin struct { + Denom string `json:"denom"` + NewAdminAddress string `json:"new_admin_address"` } type MintTokens struct { - SubDenom string `json:"sub_denom"` - Amount sdk.Int `json:"amount"` - Recipient string `json:"recipient"` + Denom string `json:"denom"` + Amount sdk.Int `json:"amount"` + MintToAddress string `json:"mint_to_address"` +} + +type BurnTokens struct { + Denom string `json:"denom"` + Amount sdk.Int `json:"amount"` + // BurnFromAddress must be set to "" for now. + BurnFromAddress string `json:"burn_from_address"` } type SwapMsg struct { diff --git a/app/wasm/bindings/query.go b/app/wasm/bindings/query.go index 738fe08395d..406cc94868e 100644 --- a/app/wasm/bindings/query.go +++ b/app/wasm/bindings/query.go @@ -22,8 +22,8 @@ type OsmosisQuery struct { } type FullDenom struct { - Contract string `json:"contract"` - SubDenom string `json:"sub_denom"` + CreatorAddr string `json:"creator_addr"` + Subdenom string `json:"subdenom"` } type PoolState struct { diff --git a/app/wasm/message_plugin.go b/app/wasm/message_plugin.go index 609c260a9e2..fc8c49e9412 100644 --- a/app/wasm/message_plugin.go +++ b/app/wasm/message_plugin.go @@ -51,6 +51,9 @@ func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre if contractMsg.MintTokens != nil { return m.mintTokens(ctx, contractAddr, contractMsg.MintTokens) } + if contractMsg.BurnTokens != nil { + return m.burnTokens(ctx, contractAddr, contractMsg.BurnTokens) + } if contractMsg.Swap != nil { return m.swapTokens(ctx, contractAddr, contractMsg.Swap) } @@ -74,7 +77,9 @@ func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) // Create denom - _, err := msgServer.CreateDenom(sdk.WrapSDKContext(ctx), tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.SubDenom)) + _, err := msgServer.CreateDenom( + sdk.WrapSDKContext(ctx), + tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom)) if err != nil { return sdkerrors.Wrap(err, "creating denom") } @@ -93,29 +98,20 @@ func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk if mint == nil { return wasmvmtypes.InvalidRequest{Err: "mint token null mint"} } - rcpt, err := parseAddress(mint.Recipient) + rcpt, err := parseAddress(mint.MintToAddress) if err != nil { return err } - // Check if denom is valid - denom, err := GetFullDenom(contractAddr.String(), mint.SubDenom) - if err != nil { + coin := sdk.Coin{Denom: mint.Denom, Amount: mint.Amount} + sdkMsg := tokenfactorytypes.NewMsgMint(contractAddr.String(), coin) + if err = sdkMsg.ValidateBasic(); err != nil { return err } - if mint.Amount.IsZero() { - return wasmvmtypes.InvalidRequest{Err: "mint token zero amount"} - } - if mint.Amount.IsNegative() { - return wasmvmtypes.InvalidRequest{Err: "mint token negative amount"} - } - coin := sdk.NewCoin(denom, mint.Amount) - - msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) - // Mint through token factory / message server - _, err = msgServer.Mint(sdk.WrapSDKContext(ctx), tokenfactorytypes.NewMsgMint(contractAddr.String(), coin)) + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err = msgServer.Mint(sdk.WrapSDKContext(ctx), sdkMsg) if err != nil { return sdkerrors.Wrap(err, "minting coins from message") } @@ -126,6 +122,37 @@ func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk return nil } +func (m *CustomMessenger) burnTokens(ctx sdk.Context, contractAddr sdk.AccAddress, burn *wasmbindings.BurnTokens) ([]sdk.Event, [][]byte, error) { + err := PerformBurn(m.tokenFactory, ctx, contractAddr, burn) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "perform mint") + } + return nil, nil, nil +} + +func PerformBurn(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, burn *wasmbindings.BurnTokens) error { + if burn == nil { + return wasmvmtypes.InvalidRequest{Err: "burn token null mint"} + } + if burn.BurnFromAddress != "" && burn.BurnFromAddress != contractAddr.String() { + return wasmvmtypes.InvalidRequest{Err: "BurnFromAddress must be \"\""} + } + + coin := sdk.Coin{Denom: burn.Denom, Amount: burn.Amount} + sdkMsg := tokenfactorytypes.NewMsgBurn(contractAddr.String(), coin) + if err := sdkMsg.ValidateBasic(); err != nil { + return err + } + + // Burn through token factory / message server + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err := msgServer.Burn(sdk.WrapSDKContext(ctx), sdkMsg) + if err != nil { + return sdkerrors.Wrap(err, "burning coins from message") + } + return nil +} + func (m *CustomMessenger) swapTokens(ctx sdk.Context, contractAddr sdk.AccAddress, swap *wasmbindings.SwapMsg) ([]sdk.Event, [][]byte, error) { _, err := PerformSwap(m.gammKeeper, ctx, contractAddr, swap) if err != nil { diff --git a/app/wasm/query_plugin.go b/app/wasm/query_plugin.go index 2eacedd1b38..bdfebe7d25d 100644 --- a/app/wasm/query_plugin.go +++ b/app/wasm/query_plugin.go @@ -19,10 +19,10 @@ func CustomQuerier(osmoKeeper *QueryPlugin) func(ctx sdk.Context, request json.R } if contractQuery.FullDenom != nil { - contract := contractQuery.FullDenom.Contract - subDenom := contractQuery.FullDenom.SubDenom + creator := contractQuery.FullDenom.CreatorAddr + subdenom := contractQuery.FullDenom.Subdenom - fullDenom, err := GetFullDenom(contract, subDenom) + fullDenom, err := GetFullDenom(creator, subdenom) if err != nil { return nil, sdkerrors.Wrap(err, "osmo full denom query") } diff --git a/app/wasm/test/custom_msg_test.go b/app/wasm/test/custom_msg_test.go index 57297e15274..8f92b13fff2 100644 --- a/app/wasm/test/custom_msg_test.go +++ b/app/wasm/test/custom_msg_test.go @@ -3,9 +3,10 @@ package wasm import ( "encoding/json" "fmt" - "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" "testing" + "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" + "github.com/stretchr/testify/require" "github.com/CosmWasm/wasmd/x/wasm/keeper" @@ -29,7 +30,7 @@ func TestCreateDenomMsg(t *testing.T) { fundAccount(t, ctx, osmosis, reflect, reflectAmount) msg := wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ - SubDenom: "SUN", + Subdenom: "SUN", }} err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) @@ -37,8 +38,8 @@ func TestCreateDenomMsg(t *testing.T) { // query the denom and see if it matches query := wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "SUN", + CreatorAddr: reflect.String(), + Subdenom: "SUN", }, } resp := wasmbindings.FullDenomResponse{} @@ -65,17 +66,18 @@ func TestMintMsg(t *testing.T) { // Create denom for minting msg := wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ - SubDenom: "SUN", + Subdenom: "SUN", }} err := executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) + sunDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) amount, ok := sdk.NewIntFromString("808010808") require.True(t, ok) msg = wasmbindings.OsmosisMsg{MintTokens: &wasmbindings.MintTokens{ - SubDenom: "SUN", - Amount: amount, - Recipient: lucky.String(), + Denom: sunDenom, + Amount: amount, + MintToAddress: lucky.String(), }} err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) @@ -89,8 +91,8 @@ func TestMintMsg(t *testing.T) { // query the denom and see if it matches query := wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "SUN", + CreatorAddr: reflect.String(), + Subdenom: "SUN", }, } resp := wasmbindings.FullDenomResponse{} @@ -111,8 +113,8 @@ func TestMintMsg(t *testing.T) { // query the denom and see if it matches query = wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "SUN", + CreatorAddr: reflect.String(), + Subdenom: "SUN", }, } resp = wasmbindings.FullDenomResponse{} @@ -123,16 +125,17 @@ func TestMintMsg(t *testing.T) { // now mint another amount / denom // create it first msg = wasmbindings.OsmosisMsg{CreateDenom: &wasmbindings.CreateDenom{ - SubDenom: "MOON", + Subdenom: "MOON", }} err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) + moonDenom := fmt.Sprintf("factory/%s/%s", reflect.String(), msg.CreateDenom.Subdenom) amount = amount.SubRaw(1) msg = wasmbindings.OsmosisMsg{MintTokens: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: amount, - Recipient: lucky.String(), + Denom: moonDenom, + Amount: amount, + MintToAddress: lucky.String(), }} err = executeCustom(t, ctx, osmosis, reflect, lucky, msg, sdk.Coin{}) require.NoError(t, err) @@ -146,8 +149,8 @@ func TestMintMsg(t *testing.T) { // query the denom and see if it matches query = wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "MOON", + CreatorAddr: reflect.String(), + Subdenom: "MOON", }, } resp = wasmbindings.FullDenomResponse{} @@ -163,8 +166,8 @@ func TestMintMsg(t *testing.T) { // query the denom and see if it matches query = wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "SUN", + CreatorAddr: reflect.String(), + Subdenom: "SUN", }, } resp = wasmbindings.FullDenomResponse{} diff --git a/app/wasm/test/custom_query_test.go b/app/wasm/test/custom_query_test.go index fa16e6811c6..1fb88e354a4 100644 --- a/app/wasm/test/custom_query_test.go +++ b/app/wasm/test/custom_query_test.go @@ -52,8 +52,8 @@ func TestQueryFullDenom(t *testing.T) { // query full denom query := wasmbindings.OsmosisQuery{ FullDenom: &wasmbindings.FullDenom{ - Contract: reflect.String(), - SubDenom: "ustart", + CreatorAddr: reflect.String(), + Subdenom: "ustart", }, } resp := wasmbindings.FullDenomResponse{} diff --git a/app/wasm/test/messages_test.go b/app/wasm/test/messages_test.go index c652f9a360c..d55473599d8 100644 --- a/app/wasm/test/messages_test.go +++ b/app/wasm/test/messages_test.go @@ -1,10 +1,12 @@ package wasm import ( + "fmt" "math" "testing" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/osmosis-labs/osmosis/v7/app/wasm" wasmbindings "github.com/osmosis-labs/osmosis/v7/app/wasm/bindings" "github.com/osmosis-labs/osmosis/v7/x/tokenfactory/types" @@ -27,18 +29,18 @@ func TestCreateDenom(t *testing.T) { }{ "valid sub-denom": { createDenom: &wasmbindings.CreateDenom{ - SubDenom: "MOON", + Subdenom: "MOON", }, }, "empty sub-denom": { createDenom: &wasmbindings.CreateDenom{ - SubDenom: "", + Subdenom: "", }, expErr: false, }, "invalid sub-denom": { createDenom: &wasmbindings.CreateDenom{ - SubDenom: "sub-denom_2", + Subdenom: "sub-denom_2", }, expErr: true, }, @@ -63,26 +65,29 @@ func TestCreateDenom(t *testing.T) { } func TestMint(t *testing.T) { - actor := RandomAccountAddress() - osmosis, ctx := SetupCustomApp(t, actor) + creator := RandomAccountAddress() + osmosis, ctx := SetupCustomApp(t, creator) // Fund actor with 100 base denom creation fees - actorAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) - fundAccount(t, ctx, osmosis, actor, actorAmount) + tokenCreationFeeAmt := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, creator, tokenCreationFeeAmt) // Create denoms for valid mint tests validDenom := wasmbindings.CreateDenom{ - SubDenom: "MOON", + Subdenom: "MOON", } - err := wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, &validDenom) + err := wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, creator, &validDenom) require.NoError(t, err) emptyDenom := wasmbindings.CreateDenom{ - SubDenom: "", + Subdenom: "", } - err = wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, &emptyDenom) + err = wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, creator, &emptyDenom) require.NoError(t, err) + validDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), validDenom.Subdenom) + emptyDenomStr := fmt.Sprintf("factory/%s/%s", creator.String(), emptyDenom.Subdenom) + lucky := RandomAccountAddress() // lucky was broke @@ -98,64 +103,64 @@ func TestMint(t *testing.T) { }{ "valid mint": { mint: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: amount, - Recipient: lucky.String(), + Denom: validDenomStr, + Amount: amount, + MintToAddress: lucky.String(), }, }, "empty sub-denom": { mint: &wasmbindings.MintTokens{ - SubDenom: "", - Amount: amount, - Recipient: lucky.String(), + Denom: emptyDenomStr, + Amount: amount, + MintToAddress: lucky.String(), }, expErr: false, }, "nonexistent sub-denom": { mint: &wasmbindings.MintTokens{ - SubDenom: "SUN", - Amount: amount, - Recipient: lucky.String(), + Denom: fmt.Sprintf("factory/%s/%s", creator.String(), "SUN"), + Amount: amount, + MintToAddress: lucky.String(), }, expErr: true, }, "invalid sub-denom": { mint: &wasmbindings.MintTokens{ - SubDenom: "sub-denom_2", - Amount: amount, - Recipient: lucky.String(), + Denom: "sub-denom_2", + Amount: amount, + MintToAddress: lucky.String(), }, expErr: true, }, "zero amount": { mint: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: sdk.ZeroInt(), - Recipient: lucky.String(), + Denom: validDenomStr, + Amount: sdk.ZeroInt(), + MintToAddress: lucky.String(), }, expErr: true, }, "negative amount": { mint: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: amount.Neg(), - Recipient: lucky.String(), + Denom: validDenomStr, + Amount: amount.Neg(), + MintToAddress: lucky.String(), }, expErr: true, }, "empty recipient": { mint: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: amount, - Recipient: "", + Denom: validDenomStr, + Amount: amount, + MintToAddress: "", }, expErr: true, }, "invalid recipient": { mint: &wasmbindings.MintTokens{ - SubDenom: "MOON", - Amount: amount, - Recipient: "invalid", + Denom: validDenomStr, + Amount: amount, + MintToAddress: "invalid", }, expErr: true, }, @@ -167,7 +172,7 @@ func TestMint(t *testing.T) { for name, spec := range specs { t.Run(name, func(t *testing.T) { // when - gotErr := wasm.PerformMint(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, actor, spec.mint) + gotErr := wasm.PerformMint(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, creator, spec.mint) // then if spec.expErr { require.Error(t, gotErr) diff --git a/app/wasm/test/queries_test.go b/app/wasm/test/queries_test.go index e0b72c3ffec..829871ceb76 100644 --- a/app/wasm/test/queries_test.go +++ b/app/wasm/test/queries_test.go @@ -18,40 +18,40 @@ func TestFullDenom(t *testing.T) { specs := map[string]struct { addr string - subDenom string + subdenom string expFullDenom string expErr bool }{ "valid address": { addr: actor.String(), - subDenom: "subDenom1", + subdenom: "subDenom1", expFullDenom: fmt.Sprintf("factory/%s/subDenom1", actor.String()), }, "empty address": { addr: "", - subDenom: "subDenom1", + subdenom: "subDenom1", expErr: true, }, "invalid address": { addr: "invalid", - subDenom: "subDenom1", + subdenom: "subDenom1", expErr: true, }, "empty sub-denom": { addr: actor.String(), - subDenom: "", + subdenom: "", expFullDenom: fmt.Sprintf("factory/%s/", actor.String()), }, "invalid sub-denom (contains underscore)": { addr: actor.String(), - subDenom: "sub_denom", + subdenom: "sub_denom", expErr: true, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { // when - gotFullDenom, gotErr := wasm.GetFullDenom(spec.addr, spec.subDenom) + gotFullDenom, gotErr := wasm.GetFullDenom(spec.addr, spec.subdenom) // then if spec.expErr { require.Error(t, gotErr) diff --git a/app/wasm/testdata/osmo_reflect.wasm b/app/wasm/testdata/osmo_reflect.wasm index 7315ce0614767527e1d2dd681320721537fd5258..f71b7fcfe5aed18384ff3c8a1e1a5a474c60842c 100644 GIT binary patch delta 78129 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 R@dTmXil+omJ{}19KLL>kraAxs diff --git a/app/wasm/testdata/version.txt b/app/wasm/testdata/version.txt index 195fba2f9a7..dc6b1f0b600 100644 --- a/app/wasm/testdata/version.txt +++ b/app/wasm/testdata/version.txt @@ -1 +1 @@ -v0.6.0 23016dce +v0.6.0 7414d1b diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index ad89b36579e..abb1a4f9445 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -225,6 +225,7 @@ - [SuperfluidAsset](#osmosis.superfluid.SuperfluidAsset) - [SuperfluidDelegationRecord](#osmosis.superfluid.SuperfluidDelegationRecord) - [SuperfluidIntermediaryAccount](#osmosis.superfluid.SuperfluidIntermediaryAccount) + - [UnpoolWhitelistedPools](#osmosis.superfluid.UnpoolWhitelistedPools) - [SuperfluidAssetType](#osmosis.superfluid.SuperfluidAssetType) @@ -276,6 +277,8 @@ - [MsgSuperfluidUnbondLockResponse](#osmosis.superfluid.MsgSuperfluidUnbondLockResponse) - [MsgSuperfluidUndelegate](#osmosis.superfluid.MsgSuperfluidUndelegate) - [MsgSuperfluidUndelegateResponse](#osmosis.superfluid.MsgSuperfluidUndelegateResponse) + - [MsgUnPoolWhitelistedPool](#osmosis.superfluid.MsgUnPoolWhitelistedPool) + - [MsgUnPoolWhitelistedPoolResponse](#osmosis.superfluid.MsgUnPoolWhitelistedPoolResponse) - [Msg](#osmosis.superfluid.Msg) @@ -3267,6 +3270,21 @@ and OSMO tokens for superfluid staking + + + +### UnpoolWhitelistedPools + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `ids` | [uint64](#uint64) | repeated | | + + + + + @@ -3935,6 +3953,44 @@ specified validator addr. + + + +### MsgUnPoolWhitelistedPool +MsgUnPoolWhitelistedPool Unpools every lock the sender has, that is +associated with pool pool_id. If pool_id is not approved for unpooling by +governance, this is a no-op. Unpooling takes the locked gamm shares, and runs +"ExitPool" on it, to get the constituent tokens. e.g. z gamm/pool/1 tokens +ExitPools into constituent tokens x uatom, y uosmo. Then it creates a new +lock for every constituent token, with the duration associated with the lock. +If the lock was unbonding, the new lockup durations should be the time left +until unbond completion. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `sender` | [string](#string) | | | +| `pool_id` | [uint64](#uint64) | | | + + + + + + + + +### MsgUnPoolWhitelistedPoolResponse + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `exitedLockIds` | [uint64](#uint64) | repeated | | + + + + + @@ -3955,6 +4011,7 @@ Msg defines the Msg service. Execute superfluid redelegation for a lockup rpc SuperfluidRedelegate(MsgSuperfluidRedelegate) returns (MsgSuperfluidRedelegateResponse); | | | `SuperfluidUnbondLock` | [MsgSuperfluidUnbondLock](#osmosis.superfluid.MsgSuperfluidUnbondLock) | [MsgSuperfluidUnbondLockResponse](#osmosis.superfluid.MsgSuperfluidUnbondLockResponse) | For a given lock that is being superfluidly undelegated, also unbond the underlying lock. | | | `LockAndSuperfluidDelegate` | [MsgLockAndSuperfluidDelegate](#osmosis.superfluid.MsgLockAndSuperfluidDelegate) | [MsgLockAndSuperfluidDelegateResponse](#osmosis.superfluid.MsgLockAndSuperfluidDelegateResponse) | Execute lockup lock and superfluid delegation in a single msg | | +| `UnPoolWhitelistedPool` | [MsgUnPoolWhitelistedPool](#osmosis.superfluid.MsgUnPoolWhitelistedPool) | [MsgUnPoolWhitelistedPoolResponse](#osmosis.superfluid.MsgUnPoolWhitelistedPoolResponse) | | | @@ -4251,14 +4308,18 @@ adminship of a denom to a new account ### MsgCreateDenom MsgCreateDenom is the sdk.Msg type for allowing an account to create -a new denom. It requires a sender address and a unique nonce -(to allow accounts to create multiple denoms) +a new denom. It requires a sender address and a subdenomination. +The (sender_address, sub_denomination) pair must be unique and cannot be +re-used. The resulting denom created is `factory/{creator +address}/{subdenom}`. The resultant denom's admin is originally set to be the +creator, but this can be changed later. The token denom does not indicate the +current admin. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `sender` | [string](#string) | | | -| `nonce` | [string](#string) | | | +| `subdenom` | [string](#string) | | subdenom can be up to 44 "alphanumeric" characters long. | diff --git a/go.sum b/go.sum index 4a26cd5e017..c7393316c93 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ 4d63.com/gochecknoglobals v0.1.0 h1:zeZSRqj5yCg28tCkIV/z/lWbwvNm5qnKVS15PI8nhD0= 4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -31,6 +32,7 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -64,6 +66,7 @@ filippo.io/edwards25519 v1.0.0-beta.2 h1:/BZRNzm8N4K4eWfK28dL4yescorxtO7YG1yun8f filippo.io/edwards25519 v1.0.0-beta.2/go.mod h1:X+pm78QAUPtFLi1z9PYIlS/bdDnvbCOGKtZ+ACWEf7o= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= +github.com/99designs/keyring v1.1.6/go.mod h1:16e0ds7LGQQcT59QqkTg72Hh5ShM51Byv5PEmW6uoRU= github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o= github.com/99designs/keyring v1.2.1/go.mod h1:fc+wB5KTk9wQ9sDx0kFXB3A0MaeGHM9AwRStKOQ5vOA= github.com/Antonboom/errname v0.1.6 h1:LzIJZlyLOCSu51o3/t2n9Ck7PcoP9wdbrdaW6J8fX24= @@ -90,6 +93,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -116,6 +120,7 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0 github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= @@ -126,6 +131,7 @@ github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.1.0 h1:pjK9nLPS1FwQYGGpPxoMYpe7qACHOhAWQMQzV71i49o= github.com/OpenPeeDeeP/depguard v1.1.0/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -136,6 +142,7 @@ github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3 github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Zilliqa/gozilliqa-sdk v1.2.1-0.20201201074141-dd0ecada1be6/go.mod h1:eSYp2T6f0apnuW8TzhV3f6Aff2SE8Dwio++U4ha4yEM= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/adlio/schema v1.1.13/go.mod h1:L5Z7tw+7lRK1Fnpi/LT/ooCP1elkXn0krMWBQHUhEDE= github.com/adlio/schema v1.3.0 h1:eSVYLxYWbm/6ReZBCkLw4Fz7uqC+ZNoPvA39bOwi52A= github.com/adlio/schema v1.3.0/go.mod h1:51QzxkpeFs6lRY11kPye26IaFPOV+HqEj01t5aXXKfs= @@ -150,6 +157,9 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= @@ -164,11 +174,14 @@ github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8 github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBlWTShc= github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/avast/retry-go v2.6.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/avast/retry-go/v4 v4.0.3/go.mod h1:HqmLvS2VLdStPCGDFjSuZ9pzlTqVRldCI4w2dO4m1Ms= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -195,6 +208,7 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= +github.com/bradleyfalzon/ghinstallation/v2 v2.0.4/go.mod h1:B40qPqJxWE0jDZgOR1JmaMy+4AY1eBP+IByOvqyAKp0= github.com/breml/bidichk v0.2.3 h1:qe6ggxpTfA8E75hdjWPZ581sY3a2lnl0IRxLQFelECI= github.com/breml/bidichk v0.2.3/go.mod h1:8u2C6DnAy0g2cEq+k/A2+tr9O1s+vHGxWn0LTc70T2A= github.com/breml/errchkjson v0.3.0 h1:YdDqhfqMT+I1vIxPSas44P+9Z9HzJwCeAzjB8PxP1xw= @@ -227,6 +241,7 @@ github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRt github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -268,6 +283,7 @@ github.com/confio/ics23/go v0.7.0/go.mod h1:E45NqnlpxGnpfTWL/xauN7MRwEE28T4Dd4ur github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.1/go.mod h1:wCYX+dRqZdImhGucXOqTQn05AhX6EUDaGEMUzTFFpLg= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= @@ -293,10 +309,12 @@ github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw github.com/cosmos/ibc-go/v3 v3.0.0 h1:XUNplHVS51Q2gMnTFsFsH9QJ7flsovMamnltKbEgPQ4= github.com/cosmos/ibc-go/v3 v3.0.0/go.mod h1:Mb+1NXiPOLd+CPFlOC6BKeAUaxXlhuWenMmRiUiSmwY= github.com/cosmos/interchain-accounts v0.1.0 h1:QmuwNsf1Hxl3P5GSGt7Z+JeuHPiZw4Z34R/038P5T6s= +github.com/cosmos/interchain-accounts v0.1.0/go.mod h1:Fv6LXDs+0ng4mIDVWwEJMXbAIMxY4kiq+A7Bw1Fb9AY= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= +github.com/cosmos/relayer/v2 v2.0.0-beta7/go.mod h1:zgTKDDGM5vLAbyUIJwfxVu8dzLjIDjjVzNtSmUH2Alw= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -309,6 +327,7 @@ github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2 github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/daixiang0/gci v0.3.3 h1:55xJKH7Gl9Vk6oQ1cMkwrDWjAkT1D+D1G9kNmRcAIY4= github.com/daixiang0/gci v0.3.3/go.mod h1:1Xr2bxnQbDxCqqulUOv8qpGqkgRw9RSCGGjEC2LjF8o= +github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -336,6 +355,7 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUn github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/docker/cli v20.10.11+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.14+incompatible h1:dSBKJOVesDgHo7rbxlYjYsXe7gPzrTT+/cKQgpDAazg= github.com/docker/cli v20.10.14+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= @@ -350,6 +370,7 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg= github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dvsekhvalnov/jose2go v0.0.0-20200901110807-248326c1351b/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM= github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU= github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= @@ -358,6 +379,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -370,10 +392,12 @@ github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -388,6 +412,7 @@ github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/firefart/nonamedreturns v1.0.1 h1:fSvcq6ZpK/uBAgJEGMvzErlzyM4NELLqqdTofVjVNag= github.com/firefart/nonamedreturns v1.0.1/go.mod h1:D3dpIBojGGNh5UfElmwPu73SwDCm+VKhHYqwlNOk2uQ= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= @@ -395,7 +420,9 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= @@ -410,8 +437,15 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-critic/go-critic v0.6.3 h1:abibh5XYBTASawfTQ0rA7dVtQT+6KzpGqb/J+DxRDaw= github.com/go-critic/go-critic v0.6.3/go.mod h1:c6b3ZP1MQ7o6lPR7Rv3lEf7pYQUmAcx8ABHgdZCQt/k= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -429,6 +463,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -436,6 +471,7 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -564,6 +600,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg= +github.com/google/go-github/v43 v43.0.0/go.mod h1:ZkTvvmCXBvsfPpTHXnH/d2hP9Y0cTbvN9kr5xqyXOIc= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -631,6 +671,7 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= +github.com/gostaticanalysis/analysisutil v0.4.1/go.mod h1:18U/DLpRgIUd459wGxVHE0fRgmo1UgHDcbw7F5idXu0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= @@ -643,6 +684,7 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -668,6 +710,7 @@ github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIv github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -746,13 +789,16 @@ github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZ github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= github.com/jhump/protoreflect v1.9.0 h1:npqHz788dryJiR/l6K/RUQAyh2SwV91+d1dnh4RjO9w= +github.com/jhump/protoreflect v1.9.0/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= @@ -765,6 +811,7 @@ github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/txtarfs v0.0.0-20210218200122-0702f000015a/go.mod h1:izVPOvVRsHiKkeGCT6tYBNWyDVuzj9wAaBb5R9qamfw= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -777,6 +824,7 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jsternberg/zap-logfmt v1.2.0/go.mod h1:kz+1CUmCutPWABnNkOu9hOHKdT2q3TDYCcsFy9hpqb0= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -787,6 +835,8 @@ github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSX github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.6.0 h1:YTDO4pNy7AUN/021p+JGHycQyYNIyMoenM1YDVK6RlY= github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -796,6 +846,7 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -807,6 +858,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -827,10 +879,12 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg= github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/letsencrypt/pkcs11key/v4 v4.0.0/go.mod h1:EFUvBDay26dErnNb70Nd0/VW3tJiIbETBPTl9ATXQag= +github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= @@ -840,6 +894,7 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lucasjones/reggen v0.0.0-20180717132126-cdb49ff09d77/go.mod h1:5ELEyG+X8f+meRWHuqUOewBOhvHkl7M76pdGEansxW4= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -850,6 +905,7 @@ github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKo github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -906,12 +962,14 @@ github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFW github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -920,6 +978,7 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -968,6 +1027,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nishanths/exhaustive v0.7.11 h1:xV/WU3Vdwh5BUH4N06JNUznb6d5zhRPOnlgCrpNYNKA= github.com/nishanths/exhaustive v0.7.11/go.mod h1:gX+MP7DWMKJmNa1HfMozK+u04hQd3na9i0hyqf3/dOI= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= +github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt3d53pc1VYcphSCIaYAJtnPYnr3Zyn8fMq2wvPGPso= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -1028,6 +1088,7 @@ github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.8.1/go.mod h1:wSRQ3wmkz+uSARYMk7kVJFDBGm8x5gSxIhI7NDc+BAQ= github.com/ory/dockertest/v3 v3.9.0 h1:U7M9FfYEwF4uqEE6WUSFs7K+Hvb31CsCX5uZUZD3olI= github.com/ory/dockertest/v3 v3.9.0/go.mod h1:jgm0rnguArPXsVduy+oUjzFtD0Na+DDNbUl8W5v+ez8= github.com/osmosis-labs/bech32-ibc v0.3.0-rc1 h1:frHKHEdPfzoK2iMF2GeWKudLLzUXz+6GJcdZ/TMcs2k= @@ -1040,10 +1101,12 @@ github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58 h1:15l3I github.com/osmosis-labs/wasmd v0.27.0-rc2.0.20220517191021-59051aa18d58/go.mod h1:0h8WBsFhyingomsrN+34JVZe/qRySHYHICyTEokCkYU= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= @@ -1068,6 +1131,7 @@ github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -1084,6 +1148,7 @@ github.com/polyfloyd/go-errorlint v1.0.0 h1:pDrQG0lrh68e602Wfp68BlUTRFoHn8PZYAjL github.com/polyfloyd/go-errorlint v1.0.0/go.mod h1:KZy4xxPJyy88/gldCe5OdW6OQRtNO3EZE7hXzmnebgA= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -1113,6 +1178,7 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8 github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.33.0 h1:rHgav/0a6+uYgGdNt3jwz8FNSesO/Hsang3O0T9A5SE= @@ -1137,6 +1203,7 @@ github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a h1:sWFav github.com/quasilyte/go-ruleguard v0.3.16-0.20220213074421-6aa060fab41a/go.mod h1:VMX+OnnSw4LicdiEGtRSD/1X8kW7GuEscjYNr4cOIT4= github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/go-ruleguard/dsl v0.3.16/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/go-ruleguard/dsl v0.3.19/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= github.com/quasilyte/gogrep v0.0.0-20220120141003-628d8b3623b5 h1:PDWGei+Rf2bBiuZIbZmM20J2ftEy9IeUCHA8HbQqed8= @@ -1154,13 +1221,18 @@ github.com/regen-network/cosmos-proto v0.3.1 h1:rV7iM4SSFAagvy8RiyhiACbWEGotmqzy github.com/regen-network/cosmos-proto v0.3.1/go.mod h1:jO0sVX6a1B36nmE8C9xBFXpNwWejXC7QqCOnH3O0+YM= github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= +github.com/remyoudompheng/go-dbus v0.0.0-20121104212943-b7232d34b1d5/go.mod h1:+u151txRmLpwxBmpYn9z3d1sdJdjRPQpsXuYeY9jNls= +github.com/remyoudompheng/go-liblzma v0.0.0-20190506200333-81bf2d431b96/go.mod h1:90HvCY7+oHHUKkbeMCiHt1WuFR2/hPJ9QrljDG+v6ls= +github.com/remyoudompheng/go-misc v0.0.0-20190427085024-2d6ac652a50e/go.mod h1:80FQABjoFzZ2M5uEa6FUaJYEmqU2UOKojlFVak1UAwI= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= @@ -1168,7 +1240,9 @@ github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1179,6 +1253,7 @@ github.com/ryancurrah/gomodguard v1.2.3/go.mod h1:rYbA/4Tg5c54mV1sv4sQTP5WOPBcoL github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -1196,6 +1271,7 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.22.4/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -1235,6 +1311,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -1248,6 +1325,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU= github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= @@ -1258,6 +1336,7 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/strangelove-ventures/lens v0.3.1-0.20220407203447-bcb1fa2e7b3a/go.mod h1:17n4M/9F6YWAvmaqJLh0/8dZgUz23grtImvd58gBe28= 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/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1265,6 +1344,7 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1313,6 +1393,8 @@ github.com/tidwall/sjson v1.1.4/go.mod h1:wXpKXu8CtDjKAZ+3DrKY5ROCorDFahq8l0tey/ github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -1340,12 +1422,17 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/uudashr/gocognit v1.0.5 h1:rrSex7oHr3/pPLQ0xoWq108XMU8s678FJcQ+aSfOHa4= github.com/uudashr/gocognit v1.0.5/go.mod h1:wgYz0mitoKOTysqxTDMOUXg+Jb5SvtihkfmugIZYpEA= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus= +github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vmihailenco/msgpack/v5 v5.1.4/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI= github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= @@ -1370,6 +1457,7 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= gitlab.com/bosi/decorder v0.2.1 h1:ehqZe8hI4w7O4b1vgsDZw1YU1PE7iJXrQWFMsocbQ1w= @@ -1409,21 +1497,26 @@ go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180501155221-613d6eafa307/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1444,12 +1537,15 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1551,10 +1647,12 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1565,6 +1663,7 @@ golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qx 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.0.0-20211208012354-db4efeb81f4b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -1623,9 +1722,11 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1660,6 +1761,7 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1667,6 +1769,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1677,11 +1780,13 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1703,6 +1808,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1711,14 +1817,20 @@ golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220403020550-483a9cbc67c0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/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.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1742,6 +1854,7 @@ golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190228203856-589c23e65e65/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -1798,6 +1911,7 @@ golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1806,6 +1920,7 @@ golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200630154851-b2d8b0336632/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200706234117-b22de6825cf7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -1818,6 +1933,7 @@ golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201028025901-8cd080b735b3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -1884,6 +2000,7 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= @@ -1969,6 +2086,8 @@ google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= @@ -1994,6 +2113,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= @@ -2040,7 +2160,9 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I= +gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/proto/osmosis/gamm/pool-models/stableswap/tx.proto b/proto/osmosis/gamm/pool-models/stableswap/tx.proto index f94ce805a72..7327ce31d13 100644 --- a/proto/osmosis/gamm/pool-models/stableswap/tx.proto +++ b/proto/osmosis/gamm/pool-models/stableswap/tx.proto @@ -33,9 +33,9 @@ message MsgCreateStableswapPoolResponse { } message MsgStableSwapAdjustScalingFactors { - // Sender must be the pool's scaling_factor_governor in order for the tx to succeed - string sender = 1 - [ (gogoproto.moretags) = "yaml:\"sender\"" ]; + // Sender must be the pool's scaling_factor_governor in order for the tx to + // succeed + string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; uint64 pool_id = 2 [ (gogoproto.customname) = "PoolID" ]; repeated uint64 scaling_factors = 3 [ diff --git a/proto/osmosis/tokenfactory/v1beta1/tx.proto b/proto/osmosis/tokenfactory/v1beta1/tx.proto index 4ead8e92512..f7bb7d4ca11 100644 --- a/proto/osmosis/tokenfactory/v1beta1/tx.proto +++ b/proto/osmosis/tokenfactory/v1beta1/tx.proto @@ -18,11 +18,16 @@ service Msg { } // MsgCreateDenom is the sdk.Msg type for allowing an account to create -// a new denom. It requires a sender address and a unique nonce -// (to allow accounts to create multiple denoms) +// a new denom. It requires a sender address and a subdenomination. +// The (sender_address, sub_denomination) pair must be unique and cannot be +// re-used. The resulting denom created is `factory/{creator +// address}/{subdenom}`. The resultant denom's admin is originally set to be the +// creator, but this can be changed later. The token denom does not indicate the +// current admin. message MsgCreateDenom { string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - string nonce = 2 [ (gogoproto.moretags) = "yaml:\"nonce\"" ]; + // subdenom can be up to 44 "alphanumeric" characters long. + string subdenom = 2 [ (gogoproto.moretags) = "yaml:\"subdenom\"" ]; } // MsgCreateDenomResponse is the return value of MsgCreateDenom diff --git a/x/gamm/pool-models/stableswap/msgs.go b/x/gamm/pool-models/stableswap/msgs.go index 2e6322128a6..20c25466d79 100644 --- a/x/gamm/pool-models/stableswap/msgs.go +++ b/x/gamm/pool-models/stableswap/msgs.go @@ -107,8 +107,8 @@ func NewMsgStableSwapAdjustScalingFactors( poolID uint64, ) MsgStableSwapAdjustScalingFactors { return MsgStableSwapAdjustScalingFactors{ - ScalingFactorGovernor: sender, - PoolID: poolID, + Sender: sender, + PoolID: poolID, } } @@ -118,11 +118,11 @@ func (msg MsgStableSwapAdjustScalingFactors) Route() string { func (msg MsgStableSwapAdjustScalingFactors) Type() string { return TypeMsgCreateStableswapPool } func (msg MsgStableSwapAdjustScalingFactors) ValidateBasic() error { - if msg.ScalingFactorGovernor == "" { + if msg.Sender == "" { return nil } - _, err := sdk.AccAddressFromBech32(msg.ScalingFactorGovernor) + _, err := sdk.AccAddressFromBech32(msg.Sender) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) } @@ -135,7 +135,7 @@ func (msg MsgStableSwapAdjustScalingFactors) GetSignBytes() []byte { } func (msg MsgStableSwapAdjustScalingFactors) GetSigners() []sdk.AccAddress { - scalingFactorGovernor, err := sdk.AccAddressFromBech32(msg.ScalingFactorGovernor) + scalingFactorGovernor, err := sdk.AccAddressFromBech32(msg.Sender) if err != nil { panic(err) } diff --git a/x/gamm/pool-models/stableswap/tx.pb.go b/x/gamm/pool-models/stableswap/tx.pb.go index e04bd1480e3..4df92b60d4e 100644 --- a/x/gamm/pool-models/stableswap/tx.pb.go +++ b/x/gamm/pool-models/stableswap/tx.pb.go @@ -143,9 +143,11 @@ func (m *MsgCreateStableswapPoolResponse) GetPoolID() uint64 { } type MsgStableSwapAdjustScalingFactors struct { - ScalingFactorGovernor string `protobuf:"bytes,1,opt,name=scaling_factor_governor,json=scalingFactorGovernor,proto3" json:"scaling_factor_governor,omitempty" yaml:"scaling_factor_governor"` - PoolID uint64 `protobuf:"varint,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` - ScalingFactors []uint64 `protobuf:"varint,3,rep,packed,name=scaling_factors,json=scalingFactors,proto3" json:"scaling_factors,omitempty" yaml:"stableswap_scaling_factor"` + // Sender must be the pool's scaling_factor_governor in order for the tx to + // succeed + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` + PoolID uint64 `protobuf:"varint,2,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + ScalingFactors []uint64 `protobuf:"varint,3,rep,packed,name=scaling_factors,json=scalingFactors,proto3" json:"scaling_factors,omitempty" yaml:"stableswap_scaling_factor"` } func (m *MsgStableSwapAdjustScalingFactors) Reset() { *m = MsgStableSwapAdjustScalingFactors{} } @@ -181,9 +183,9 @@ func (m *MsgStableSwapAdjustScalingFactors) XXX_DiscardUnknown() { var xxx_messageInfo_MsgStableSwapAdjustScalingFactors proto.InternalMessageInfo -func (m *MsgStableSwapAdjustScalingFactors) GetScalingFactorGovernor() string { +func (m *MsgStableSwapAdjustScalingFactors) GetSender() string { if m != nil { - return m.ScalingFactorGovernor + return m.Sender } return "" } @@ -254,45 +256,44 @@ func init() { } var fileDescriptor_46b7c8a0f24de97c = []byte{ - // 607 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0x8d, 0x9b, 0x2a, 0x9f, 0xbe, 0xa9, 0x00, 0x61, 0x85, 0x36, 0x04, 0xc9, 0x0e, 0x66, 0x93, - 0x02, 0xf5, 0xd0, 0x22, 0x81, 0x60, 0x05, 0x2e, 0x2a, 0xaa, 0x20, 0x52, 0xeb, 0x8a, 0x4d, 0x37, - 0xd1, 0x38, 0x9e, 0x9a, 0x01, 0xdb, 0x63, 0x7c, 0x27, 0xfd, 0x59, 0xb2, 0x66, 0xc3, 0x63, 0x20, - 0x1e, 0x04, 0x75, 0xd9, 0x25, 0x2b, 0x83, 0xd2, 0x37, 0xc8, 0x06, 0xb1, 0x43, 0xe3, 0xb1, 0x93, - 0x54, 0x6a, 0xfa, 0x23, 0x75, 0x95, 0xc9, 0xf1, 0xb9, 0xe7, 0xdc, 0x7b, 0x8f, 0x3d, 0xe8, 0x21, - 0x87, 0x88, 0x03, 0x03, 0x1c, 0x90, 0x28, 0xc2, 0x09, 0xe7, 0xe1, 0x52, 0xc4, 0x7d, 0x1a, 0x02, - 0x06, 0x41, 0xbc, 0x90, 0xc2, 0x1e, 0x49, 0xb0, 0xd8, 0xb7, 0x93, 0x94, 0x0b, 0xae, 0xdf, 0x2f, - 0xd8, 0xb6, 0x64, 0xdb, 0x92, 0xad, 0xc8, 0xf6, 0x98, 0x6c, 0xef, 0x2e, 0x7b, 0x54, 0x90, 0xe5, - 0xa6, 0xd1, 0xcb, 0xc9, 0xd8, 0x23, 0x40, 0x71, 0x01, 0xe2, 0x1e, 0x67, 0xb1, 0xd2, 0x6a, 0xd6, - 0x03, 0x1e, 0xf0, 0xfc, 0x88, 0xe5, 0xa9, 0x40, 0x9f, 0x5d, 0xa4, 0x9f, 0xf1, 0xb1, 0x2b, 0x19, - 0xaa, 0xd4, 0xfa, 0x52, 0x45, 0x0b, 0x1d, 0x08, 0x56, 0x53, 0x4a, 0x04, 0xdd, 0x1a, 0x51, 0x36, - 0x38, 0x0f, 0xf5, 0x45, 0x54, 0x03, 0x1a, 0xfb, 0x34, 0x6d, 0x68, 0x2d, 0xad, 0xfd, 0xbf, 0x73, - 0x73, 0x98, 0x99, 0xd7, 0x0e, 0x48, 0x14, 0x3e, 0xb7, 0x14, 0x6e, 0xb9, 0x05, 0x41, 0x8f, 0x11, - 0x92, 0xa2, 0x1b, 0x24, 0x25, 0x11, 0x34, 0x66, 0x5a, 0x5a, 0x7b, 0x6e, 0xe5, 0x89, 0x7d, 0xf1, - 0xc1, 0xed, 0x8d, 0x51, 0xb5, 0x33, 0x3f, 0xcc, 0x4c, 0x5d, 0xd9, 0xc8, 0x9a, 0x6e, 0x92, 0xc3, - 0x96, 0x3b, 0xe1, 0xa0, 0x7f, 0xd6, 0xd0, 0x3c, 0x8b, 0x99, 0x60, 0x24, 0xcc, 0xa7, 0xe9, 0x86, - 0xec, 0x53, 0x9f, 0xf9, 0x4c, 0x1c, 0x34, 0xaa, 0xad, 0x6a, 0x7b, 0x6e, 0xe5, 0xb6, 0xad, 0x36, - 0x69, 0xcb, 0x4d, 0x8e, 0x5c, 0x56, 0x39, 0x8b, 0x9d, 0x47, 0x87, 0x99, 0x59, 0xf9, 0xfe, 0xcb, - 0x6c, 0x07, 0x4c, 0xbc, 0xef, 0x7b, 0x76, 0x8f, 0x47, 0xb8, 0x58, 0xbb, 0xfa, 0x59, 0x02, 0xff, - 0x23, 0x16, 0x07, 0x09, 0x85, 0xbc, 0x00, 0xdc, 0x7a, 0x61, 0x25, 0x9b, 0x7c, 0x5b, 0x1a, 0xe9, - 0x9b, 0xa8, 0xbe, 0xd3, 0x17, 0xfd, 0x94, 0xaa, 0x0e, 0x02, 0xbe, 0x4b, 0xd3, 0x98, 0xa7, 0x8d, - 0xd9, 0x7c, 0x59, 0xe6, 0x30, 0x33, 0xef, 0xa8, 0x29, 0x4e, 0x63, 0x59, 0xae, 0xae, 0x60, 0xa9, - 0xf9, 0xba, 0x04, 0xd7, 0x90, 0x39, 0x25, 0x0c, 0x97, 0x42, 0xc2, 0x63, 0xa0, 0xfa, 0x3d, 0xf4, - 0x5f, 0x2e, 0xc4, 0xfc, 0x3c, 0x95, 0x59, 0x07, 0x0d, 0x32, 0xb3, 0x26, 0x29, 0xeb, 0xaf, 0xdc, - 0x9a, 0x7c, 0xb4, 0xee, 0x5b, 0x7f, 0x34, 0x74, 0xb7, 0x03, 0x81, 0x92, 0xd8, 0xda, 0x23, 0xc9, - 0x4b, 0xff, 0x43, 0x1f, 0xc4, 0x56, 0x8f, 0x84, 0x2c, 0x0e, 0xd6, 0x48, 0x4f, 0xf0, 0x14, 0xf4, - 0x6d, 0xb4, 0x00, 0x0a, 0xe9, 0xee, 0xe4, 0xd0, 0x78, 0x06, 0x15, 0xb8, 0x35, 0xcc, 0x4c, 0xa3, - 0x08, 0xfc, 0x74, 0xa2, 0xe5, 0xde, 0x82, 0x49, 0xd1, 0x72, 0x92, 0xc9, 0x36, 0x67, 0xa6, 0xb5, - 0xa9, 0x6f, 0xa2, 0x1b, 0x27, 0x75, 0x21, 0x4f, 0x6f, 0xd6, 0x69, 0xcb, 0x88, 0x86, 0x99, 0xd9, - 0x2a, 0xcc, 0xc7, 0xaf, 0xee, 0x49, 0xbe, 0xe5, 0x5e, 0x3f, 0x61, 0x0f, 0xd6, 0x03, 0xb4, 0x78, - 0xee, 0xe0, 0xe5, 0x2e, 0x57, 0xfe, 0xce, 0xa0, 0x6a, 0x07, 0x02, 0xfd, 0x9b, 0x86, 0xea, 0xa7, - 0x7e, 0x01, 0xab, 0x97, 0x79, 0x85, 0xa7, 0x24, 0xd7, 0x7c, 0x73, 0x05, 0x22, 0xa3, 0xf8, 0x7f, - 0x68, 0xc8, 0x38, 0x27, 0xd6, 0xce, 0x25, 0xfd, 0xce, 0x96, 0x6b, 0xbe, 0xbb, 0x52, 0xb9, 0x72, - 0x10, 0x67, 0xfb, 0x70, 0x60, 0x68, 0x47, 0x03, 0x43, 0xfb, 0x3d, 0x30, 0xb4, 0xaf, 0xc7, 0x46, - 0xe5, 0xe8, 0xd8, 0xa8, 0xfc, 0x3c, 0x36, 0x2a, 0xdb, 0x2f, 0x26, 0xbe, 0xcb, 0xc2, 0x7a, 0x29, - 0x24, 0x1e, 0x94, 0x7f, 0xf0, 0xee, 0x53, 0xbc, 0x7f, 0xd6, 0x55, 0xe7, 0xd5, 0xf2, 0xbb, 0xed, - 0xf1, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x42, 0x58, 0xce, 0xa8, 0x05, 0x00, 0x00, + // 583 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x4f, 0x6f, 0xd3, 0x4e, + 0x10, 0x8d, 0x93, 0x28, 0x3f, 0xfd, 0xb6, 0x02, 0x84, 0x15, 0x95, 0x10, 0x24, 0x3b, 0x98, 0x4b, + 0x0a, 0xc4, 0x4b, 0x83, 0x04, 0x82, 0x13, 0x38, 0xa8, 0xa8, 0x82, 0x48, 0xa9, 0x23, 0x2e, 0xbd, + 0x44, 0xeb, 0x78, 0x6b, 0x16, 0x6c, 0xaf, 0xf1, 0x6e, 0xd2, 0xe6, 0xc8, 0x99, 0x0b, 0x1f, 0x03, + 0xf1, 0x41, 0xaa, 0x1e, 0x7b, 0xe4, 0x64, 0x50, 0xf2, 0x0d, 0x72, 0xe3, 0x86, 0xd6, 0xeb, 0xfc, + 0xa9, 0xd4, 0xb4, 0x8d, 0xd4, 0x93, 0xd7, 0xe3, 0x37, 0xef, 0xcd, 0xcc, 0x1b, 0x2f, 0x78, 0x4c, + 0x59, 0x40, 0x19, 0x61, 0xd0, 0x43, 0x41, 0x00, 0x23, 0x4a, 0xfd, 0x46, 0x40, 0x5d, 0xec, 0x33, + 0xc8, 0x38, 0x72, 0x7c, 0xcc, 0x0e, 0x51, 0x04, 0xf9, 0x91, 0x19, 0xc5, 0x94, 0x53, 0xf5, 0x61, + 0x86, 0x36, 0x05, 0xda, 0x14, 0x68, 0x09, 0x36, 0x17, 0x60, 0x73, 0xb8, 0xed, 0x60, 0x8e, 0xb6, + 0xab, 0x5a, 0x3f, 0x05, 0x43, 0x07, 0x31, 0x0c, 0xb3, 0x20, 0xec, 0x53, 0x12, 0x4a, 0xae, 0x6a, + 0xd9, 0xa3, 0x1e, 0x4d, 0x8f, 0x50, 0x9c, 0xb2, 0xe8, 0x8b, 0xab, 0xd4, 0xb3, 0x38, 0xf6, 0x04, + 0x42, 0xa6, 0x1a, 0xdf, 0x0a, 0xe0, 0x4e, 0x9b, 0x79, 0xad, 0x18, 0x23, 0x8e, 0xbb, 0x73, 0x48, + 0x87, 0x52, 0x5f, 0xdd, 0x02, 0x25, 0x86, 0x43, 0x17, 0xc7, 0x15, 0xa5, 0xa6, 0xd4, 0xff, 0xb7, + 0x6e, 0x4f, 0x13, 0xfd, 0xc6, 0x08, 0x05, 0xfe, 0x4b, 0x43, 0xc6, 0x0d, 0x3b, 0x03, 0xa8, 0x21, + 0x00, 0x82, 0xb4, 0x83, 0x62, 0x14, 0xb0, 0x4a, 0xbe, 0xa6, 0xd4, 0x37, 0x9a, 0xcf, 0xcc, 0xab, + 0x37, 0x6e, 0x76, 0xe6, 0xd9, 0xd6, 0xe6, 0x34, 0xd1, 0x55, 0x29, 0x23, 0x72, 0x7a, 0x51, 0x1a, + 0x36, 0xec, 0x25, 0x05, 0xf5, 0xab, 0x02, 0x36, 0x49, 0x48, 0x38, 0x41, 0x7e, 0xda, 0x4d, 0xcf, + 0x27, 0x5f, 0x06, 0xc4, 0x25, 0x7c, 0x54, 0x29, 0xd4, 0x0a, 0xf5, 0x8d, 0xe6, 0x5d, 0x53, 0x4e, + 0xd2, 0x14, 0x93, 0x9c, 0xab, 0xb4, 0x28, 0x09, 0xad, 0x27, 0x27, 0x89, 0x9e, 0xfb, 0xf9, 0x5b, + 0xaf, 0x7b, 0x84, 0x7f, 0x1c, 0x38, 0x66, 0x9f, 0x06, 0x30, 0x1b, 0xbb, 0x7c, 0x34, 0x98, 0xfb, + 0x19, 0xf2, 0x51, 0x84, 0x59, 0x9a, 0xc0, 0xec, 0x72, 0x26, 0x25, 0x8a, 0x7c, 0x3f, 0x13, 0x52, + 0xf7, 0x40, 0xf9, 0x60, 0xc0, 0x07, 0x31, 0x96, 0x15, 0x78, 0x74, 0x88, 0xe3, 0x90, 0xc6, 0x95, + 0x62, 0x3a, 0x2c, 0x7d, 0x9a, 0xe8, 0xf7, 0x64, 0x17, 0xe7, 0xa1, 0x0c, 0x5b, 0x95, 0x61, 0xc1, + 0xf9, 0x76, 0x16, 0xdc, 0x01, 0xfa, 0x0a, 0x33, 0x6c, 0xcc, 0x22, 0x1a, 0x32, 0xac, 0x3e, 0x00, + 0xff, 0xa5, 0x44, 0xc4, 0x4d, 0x5d, 0x29, 0x5a, 0x60, 0x9c, 0xe8, 0x25, 0x01, 0xd9, 0x7d, 0x63, + 0x97, 0xc4, 0xa7, 0x5d, 0xd7, 0x38, 0x56, 0xc0, 0xfd, 0x36, 0xf3, 0x24, 0x45, 0xf7, 0x10, 0x45, + 0xaf, 0xdd, 0x4f, 0x03, 0xc6, 0xbb, 0x7d, 0xe4, 0x93, 0xd0, 0xdb, 0x41, 0x7d, 0x4e, 0x63, 0xb6, + 0x8e, 0xbf, 0x4b, 0xaa, 0xf9, 0x55, 0xaa, 0xea, 0x1e, 0xb8, 0xc5, 0xa4, 0x42, 0xef, 0x40, 0x4a, + 0xa4, 0x66, 0x14, 0xad, 0xba, 0x98, 0xf8, 0x34, 0xd1, 0x6b, 0x19, 0xf9, 0x62, 0x13, 0xcf, 0xe2, + 0x0d, 0xfb, 0x26, 0x3b, 0x53, 0xa2, 0xf1, 0x08, 0x6c, 0x5d, 0xda, 0xc7, 0x6c, 0x34, 0xcd, 0xbf, + 0x79, 0x50, 0x68, 0x33, 0x4f, 0xfd, 0xa1, 0x80, 0xf2, 0xb9, 0x0b, 0xdd, 0x5a, 0x67, 0x23, 0x57, + 0x18, 0x51, 0x7d, 0x77, 0x0d, 0x24, 0x73, 0x37, 0x8f, 0x15, 0xa0, 0x5d, 0xe2, 0x52, 0x7b, 0x4d, + 0xbd, 0x8b, 0xe9, 0xaa, 0x1f, 0xae, 0x95, 0x6e, 0xd6, 0x88, 0xb5, 0x7f, 0x32, 0xd6, 0x94, 0xd3, + 0xb1, 0xa6, 0xfc, 0x19, 0x6b, 0xca, 0xf7, 0x89, 0x96, 0x3b, 0x9d, 0x68, 0xb9, 0x5f, 0x13, 0x2d, + 0xb7, 0xff, 0x6a, 0xe9, 0x37, 0xcb, 0xa4, 0x1b, 0x3e, 0x72, 0xd8, 0xec, 0x05, 0x0e, 0x9f, 0xc3, + 0xa3, 0x8b, 0x6e, 0x2e, 0xa7, 0x94, 0x5e, 0x55, 0x4f, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x2a, + 0xa4, 0xbb, 0x04, 0x77, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -545,10 +546,10 @@ func (m *MsgStableSwapAdjustScalingFactors) MarshalToSizedBuffer(dAtA []byte) (i i-- dAtA[i] = 0x10 } - if len(m.ScalingFactorGovernor) > 0 { - i -= len(m.ScalingFactorGovernor) - copy(dAtA[i:], m.ScalingFactorGovernor) - i = encodeVarintTx(dAtA, i, uint64(len(m.ScalingFactorGovernor))) + if len(m.Sender) > 0 { + i -= len(m.Sender) + copy(dAtA[i:], m.Sender) + i = encodeVarintTx(dAtA, i, uint64(len(m.Sender))) i-- dAtA[i] = 0xa } @@ -634,7 +635,7 @@ func (m *MsgStableSwapAdjustScalingFactors) Size() (n int) { } var l int _ = l - l = len(m.ScalingFactorGovernor) + l = len(m.Sender) if l > 0 { n += 1 + l + sovTx(uint64(l)) } @@ -950,7 +951,7 @@ func (m *MsgStableSwapAdjustScalingFactors) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ScalingFactorGovernor", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -978,7 +979,7 @@ func (m *MsgStableSwapAdjustScalingFactors) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ScalingFactorGovernor = string(dAtA[iNdEx:postIndex]) + m.Sender = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 0 { diff --git a/x/tokenfactory/spec/README.md b/x/tokenfactory/README.md similarity index 55% rename from x/tokenfactory/spec/README.md rename to x/tokenfactory/README.md index 5a95dab8ecb..c976498431e 100644 --- a/x/tokenfactory/spec/README.md +++ b/x/tokenfactory/README.md @@ -1,10 +1,10 @@ # Token Factory The tokenfactory module allows any account to create a new token with -the name `factory/{creator address}/{nonce}`. Because tokens are +the name `factory/{creator address}/{subdenom}`. Because tokens are namespaced by creator address, this allows token minting to be permissionless, due to not needing to resolve name collisions. A single -account can create multiple denoms, by providing a unique nonce for each +account can create multiple denoms, by providing a unique subdenom for each created denom. Once a denom is created, the original creator is given "admin" privileges over the asset. This allows them to: @@ -21,18 +21,18 @@ created denom. Once a denom is created, the original creator is given ## Messages ### CreateDenom -- Creates a denom of `factory/{creator address}/{nonce}` given the denom creator address and the denom nonce. The case a denom has a slash in its nonce is handled within the module. +- Creates a denom of `factory/{creator address}/{subdenom}` given the denom creator address and the subdenom. Subdenoms can contain `[a-zA-Z0-9./]`. ``` {.go} message MsgCreateDenom { string sender = 1 [ (gogoproto.moretags) = "yaml:\"sender\"" ]; - string nonce = 2 [ (gogoproto.moretags) = "yaml:\"nonce\"" ]; + string subdenom = 2 [ (gogoproto.moretags) = "yaml:\"subdenom\"" ]; } ``` **State Modifications:** - Fund community pool with the denom creation fee from the creator address, set in `Params` - Set `DenomMetaData` via bank keeper -- Set `AuthorityMetadata` for the given denom to store the admin for the created denom `factory/{creator address}/{nonce}`. Admin is automatically set as the Msg sender +- Set `AuthorityMetadata` for the given denom to store the admin for the created denom `factory/{creator address}/{subdenom}`. Admin is automatically set as the Msg sender - Add denom to the `CreatorPrefixStore`, where a state of denoms created per creator is kept ### Mint @@ -48,7 +48,7 @@ message MsgMint { ``` **State Modifications:** -- Saftey check the following +- Safety check the following - Check that the denom minting is created via `tokenfactory` module - Check that the sender of the message is the admin of the denom - Mint designated amount of tokens for the denom via `bank` module @@ -86,4 +86,32 @@ message MsgChangeAdmin { **State Modifications:** - Check that sender of the message is the admin of denom -- Modify `AuthorityMetadata` state entry to change the admin of the denom \ No newline at end of file +- Modify `AuthorityMetadata` state entry to change the admin of the denom + +## Expectations from the chain + +The chain's bech32 prefix for addresses can be at most 16 characters long. + +This comes from denoms having a 128 byte maximum length, enforced from the SDK, and us setting longest_subdenom to be 44 bytes. +A token factory token's denom is: +`factory/{creator address}/{subdenom}` +Splitting up into sub-components, this has: +* `len(factory) = 7` +* `2 * len("/") = 2` +* `len(longest_subdenom)` +* `len(creator_address) = len(bech32(longest_addr_length, chain_addr_prefix))`. +Longest addr length at the moment is `32 bytes`. +Due to SDK error correction settings, this means `len(bech32(32, chain_addr_prefix)) = len(chain_addr_prefix) + 1 + 58`. +Adding this all, we have a total length constraint of `128 = 7 + 2 + len(longest_subdenom) + len(longest_chain_addr_prefix) + 1 + 58`. +Therefore `len(longest_subdenom) + len(longest_chain_addr_prefix) = 128 - (7 + 2 + 1 + 58) = 60`. + +The choice between how we standardized the split these 60 bytes between maxes from longest_subdenom and longest_chain_addr_prefix is somewhat arbitrary. Considerations going into this: +* Per [BIP-0173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32) the technically longest HRP for a 32 byte address ('data field') is 31 bytes. (Comes from encode(data) = 59 bytes, and max length = 90 bytes) +* subdenom should be at least 32 bytes so hashes can go into it +* longer subdenoms are very helpful for creating human readable denoms +* chain addresses should prefer being smaller. The longest HRP in cosmos to date is 11 bytes. (`persistence`) + +For explicitness, its currently set to `len(longest_subdenom) = 44` and `len(longest_chain_addr_prefix) = 16`. + +Please note, if the SDK increases the maximum length of a denom from 128 bytes, these caps should increase. +So please don't make code rely on these max lengths for parsing. \ No newline at end of file diff --git a/x/tokenfactory/client/cli/tx.go b/x/tokenfactory/client/cli/tx.go index 8fc8dd97abb..a1c22e8c118 100644 --- a/x/tokenfactory/client/cli/tx.go +++ b/x/tokenfactory/client/cli/tx.go @@ -38,7 +38,7 @@ func GetTxCmd() *cobra.Command { // NewCreateDenomCmd broadcast MsgCreateDenom func NewCreateDenomCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create-denom [nonce] [flags]", + Use: "create-denom [subdenom] [flags]", Short: "create a new denom from an account", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/tokenfactory/keeper/createdenom.go b/x/tokenfactory/keeper/createdenom.go index e669893ea3a..66e0374608c 100644 --- a/x/tokenfactory/keeper/createdenom.go +++ b/x/tokenfactory/keeper/createdenom.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -8,7 +10,13 @@ import ( ) // ConvertToBaseToken converts a fee amount in a whitelisted fee token to the base fee token amount -func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, denomNonce string) (newTokenDenom string, err error) { +func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, subdenom string) (newTokenDenom string, err error) { + // Temporary check until IBC bug is sorted out + if k.bankKeeper.HasSupply(ctx, subdenom) { + return "", fmt.Errorf("Temporary error until IBC bug is sorted out, " + + "can't create subdenoms that are the same as a native denom.") + } + // Send creation fee to community pool creationFee := k.GetParams(ctx).DenomCreationFee accAddr, err := sdk.AccAddressFromBech32(creatorAddr) @@ -21,7 +29,7 @@ func (k Keeper) CreateDenom(ctx sdk.Context, creatorAddr string, denomNonce stri } } - denom, err := types.GetTokenDenom(creatorAddr, denomNonce) + denom, err := types.GetTokenDenom(creatorAddr, subdenom) if err != nil { return "", err } diff --git a/x/tokenfactory/keeper/createdenom_test.go b/x/tokenfactory/keeper/createdenom_test.go index a05141c5f6b..2bd03607fd0 100644 --- a/x/tokenfactory/keeper/createdenom_test.go +++ b/x/tokenfactory/keeper/createdenom_test.go @@ -49,7 +49,7 @@ func (suite *KeeperTestSuite) TestMsgCreateDenom() { suite.Require().NoError(err) suite.Require().Len(queryRes2.Denoms, 2) - // Make sure that a second account can create a denom with the same nonce + // Make sure that a second account can create a denom with the same subdenom res, err = msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[1].String(), "bitcoin")) suite.Require().NoError(err) suite.Require().NotEmpty(res.GetNewTokenDenom()) diff --git a/x/tokenfactory/keeper/genesis.go b/x/tokenfactory/keeper/genesis.go index bace751ceab..6dde765c279 100644 --- a/x/tokenfactory/keeper/genesis.go +++ b/x/tokenfactory/keeper/genesis.go @@ -17,11 +17,11 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState types.GenesisState) { k.SetParams(ctx, genState.Params) for _, genDenom := range genState.GetFactoryDenoms() { - creator, nonce, err := types.DeconstructDenom(genDenom.GetDenom()) + creator, subdenom, err := types.DeconstructDenom(genDenom.GetDenom()) if err != nil { panic(err) } - _, err = k.CreateDenom(ctx, creator, nonce) + _, err = k.CreateDenom(ctx, creator, subdenom) if err != nil { panic(err) } diff --git a/x/tokenfactory/keeper/msg_server.go b/x/tokenfactory/keeper/msg_server.go index 1ad741736a2..1327cdd6946 100644 --- a/x/tokenfactory/keeper/msg_server.go +++ b/x/tokenfactory/keeper/msg_server.go @@ -23,7 +23,7 @@ var _ types.MsgServer = msgServer{} func (server msgServer) CreateDenom(goCtx context.Context, msg *types.MsgCreateDenom) (*types.MsgCreateDenomResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - denom, err := server.Keeper.CreateDenom(ctx, msg.Sender, msg.Nonce) + denom, err := server.Keeper.CreateDenom(ctx, msg.Sender, msg.Subdenom) if err != nil { return nil, err } @@ -44,6 +44,12 @@ func (server msgServer) CreateDenom(goCtx context.Context, msg *types.MsgCreateD func (server msgServer) Mint(goCtx context.Context, msg *types.MsgMint) (*types.MsgMintResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // pay some extra gas cost to give a better error here. + _, denomExists := server.bankKeeper.GetDenomMetaData(ctx, msg.Amount.Denom) + if !denomExists { + return nil, types.ErrDenomDoesNotExist + } + authorityMetadata, err := server.Keeper.GetAuthorityMetadata(ctx, msg.Amount.GetDenom()) if err != nil { return nil, err diff --git a/x/tokenfactory/types/denoms.go b/x/tokenfactory/types/denoms.go index 69cfbb0c237..a93bb594208 100644 --- a/x/tokenfactory/types/denoms.go +++ b/x/tokenfactory/types/denoms.go @@ -11,23 +11,36 @@ import ( const ( ModuleDenomPrefix = "factory" + // See the TokenFactory readme for a derivation of these. + // TL;DR, MaxSubdenomLength + MaxHrpLength = 60 comes from SDK max denom length = 128 + // and the structure of tokenfactory denoms. + MaxSubdenomLength = 44 + MaxHrpLength = 16 + // MaxCreatorLength = 59 + MaxHrpLength + MaxCreatorLength = 59 + MaxHrpLength ) // GetTokenDenom constructs a denom string for tokens created by tokenfactory -// based on an input creator address and a nonce -// The denom constructed is factory/{creator}/{nonce} -func GetTokenDenom(creator, nonce string) (string, error) { +// based on an input creator address and a subdenom +// The denom constructed is factory/{creator}/{subdenom} +func GetTokenDenom(creator, subdenom string) (string, error) { + if len(subdenom) > MaxSubdenomLength { + return "", ErrSubdenomTooLong + } + if len(subdenom) > MaxCreatorLength { + return "", ErrCreatorTooLong + } if strings.Contains(creator, "/") { return "", ErrInvalidCreator } - denom := strings.Join([]string{ModuleDenomPrefix, creator, nonce}, "/") + denom := strings.Join([]string{ModuleDenomPrefix, creator, subdenom}, "/") return denom, sdk.ValidateDenom(denom) } // DeconstructDenom takes a token denom string and verifies that it is a valid -// denom of the tokenfactory module, and is of the form `factory/{creator}/{nonce}` -// If valid, it returns the creator address and nonce -func DeconstructDenom(denom string) (creator string, nonce string, err error) { +// denom of the tokenfactory module, and is of the form `factory/{creator}/{subdenom}` +// If valid, it returns the creator address and subdenom +func DeconstructDenom(denom string) (creator string, subdenom string, err error) { err = sdk.ValidateDenom(denom) if err != nil { return "", "", err @@ -48,12 +61,12 @@ func DeconstructDenom(denom string) (creator string, nonce string, err error) { return "", "", sdkerrors.Wrapf(ErrInvalidDenom, "Invalid creator address (%s)", err) } - // Handle the case where a denom has a slash in its nonce. For example, - // when we did the split, we'd turn factory/sunnyaddr/atomderivative/sikka into ["factory", "sunnyaddr", "atomderivative", "sikka"] - // So we have to join [2:] with a "/" as the delimiter to get back the correct nonce which should be "atomderivative/sikka" - nonce = strings.Join(strParts[2:], "/") + // Handle the case where a denom has a slash in its subdenom. For example, + // when we did the split, we'd turn factory/accaddr/atomderivative/sikka into ["factory", "accaddr", "atomderivative", "sikka"] + // So we have to join [2:] with a "/" as the delimiter to get back the correct subdenom which should be "atomderivative/sikka" + subdenom = strings.Join(strParts[2:], "/") - return creator, nonce, nil + return creator, subdenom, nil } // NewTokenFactoryDenomMintCoinsRestriction creates and returns a BankMintingRestrictionFn that only allows minting of diff --git a/x/tokenfactory/types/denoms_test.go b/x/tokenfactory/types/denoms_test.go index b7f0fc01067..31234344753 100644 --- a/x/tokenfactory/types/denoms_test.go +++ b/x/tokenfactory/types/denoms_test.go @@ -27,12 +27,12 @@ func TestDecomposeDenoms(t *testing.T) { valid: true, }, { - desc: "multiple slashes in nonce", + desc: "multiple slashes in subdenom", denom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/bitcoin/1", valid: true, }, { - desc: "no nonce", + desc: "no subdenom", denom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/", valid: true, }, @@ -42,7 +42,7 @@ func TestDecomposeDenoms(t *testing.T) { valid: false, }, { - desc: "nonce of only slashes", + desc: "subdenom of only slashes", denom: "factory/osmo1t7egva48prqmzl59x5ngv4zx0dtrwewc9m7z44/////", valid: true, }, diff --git a/x/tokenfactory/types/errors.go b/x/tokenfactory/types/errors.go index 89f8f3e355d..db1552e8f2d 100644 --- a/x/tokenfactory/types/errors.go +++ b/x/tokenfactory/types/errors.go @@ -3,6 +3,8 @@ package types // DONTCOVER import ( + fmt "fmt" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -14,4 +16,7 @@ var ( ErrInvalidCreator = sdkerrors.Register(ModuleName, 5, "invalid creator") ErrInvalidAuthorityMetadata = sdkerrors.Register(ModuleName, 6, "invalid authority metadata") ErrInvalidGenesis = sdkerrors.Register(ModuleName, 7, "invalid genesis") + ErrSubdenomTooLong = sdkerrors.Register(ModuleName, 8, fmt.Sprintf("subdenom too long, max length is %d bytes", MaxSubdenomLength)) + ErrCreatorTooLong = sdkerrors.Register(ModuleName, 9, fmt.Sprintf("creator too long, max length is %d bytes", MaxCreatorLength)) + ErrDenomDoesNotExist = sdkerrors.Register(ModuleName, 10, "denom does not exist") ) diff --git a/x/tokenfactory/types/events.go b/x/tokenfactory/types/events.go index 24742983950..1b3a9a160ce 100644 --- a/x/tokenfactory/types/events.go +++ b/x/tokenfactory/types/events.go @@ -4,7 +4,7 @@ package types const ( AttributeAmount = "amount" AttributeCreator = "creator" - AttributeNonce = "nonce" + AttributeSubdenom = "subdenom" AttributeNewTokenDenom = "new_token_denom" AttributeMintToAddress = "mint_to_address" AttributeBurnFromAddress = "burn_from_address" diff --git a/x/tokenfactory/types/expected_keepers.go b/x/tokenfactory/types/expected_keepers.go index 62a7ad7c0d0..1f750fd8d32 100644 --- a/x/tokenfactory/types/expected_keepers.go +++ b/x/tokenfactory/types/expected_keepers.go @@ -11,6 +11,8 @@ type BankKeeper interface { GetDenomMetaData(ctx sdk.Context, denom string) (banktypes.Metadata, bool) SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata) + HasSupply(ctx sdk.Context, denom string) bool + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error diff --git a/x/tokenfactory/types/msgs.go b/x/tokenfactory/types/msgs.go index 77356c278fa..026dac1b0c3 100644 --- a/x/tokenfactory/types/msgs.go +++ b/x/tokenfactory/types/msgs.go @@ -17,10 +17,10 @@ const ( var _ sdk.Msg = &MsgCreateDenom{} // NewMsgCreateDenom creates a msg to create a new denom -func NewMsgCreateDenom(sender, nonce string) *MsgCreateDenom { +func NewMsgCreateDenom(sender, subdenom string) *MsgCreateDenom { return &MsgCreateDenom{ - Sender: sender, - Nonce: nonce, + Sender: sender, + Subdenom: subdenom, } } @@ -32,7 +32,7 @@ func (m MsgCreateDenom) ValidateBasic() error { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) } - _, err = GetTokenDenom(m.Sender, m.Nonce) + _, err = GetTokenDenom(m.Sender, m.Subdenom) if err != nil { return sdkerrors.Wrap(ErrInvalidDenom, err.Error()) } @@ -67,7 +67,7 @@ func (m MsgMint) ValidateBasic() error { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) } - if !m.Amount.IsValid() { + if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) } @@ -101,7 +101,7 @@ func (m MsgBurn) ValidateBasic() error { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "Invalid sender address (%s)", err) } - if !m.Amount.IsValid() { + if !m.Amount.IsValid() || m.Amount.Amount.Equal(sdk.ZeroInt()) { return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String()) } diff --git a/x/tokenfactory/types/tx.pb.go b/x/tokenfactory/types/tx.pb.go index 4210bdae47f..b6f0201e391 100644 --- a/x/tokenfactory/types/tx.pb.go +++ b/x/tokenfactory/types/tx.pb.go @@ -30,11 +30,16 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // MsgCreateDenom is the sdk.Msg type for allowing an account to create -// a new denom. It requires a sender address and a unique nonce -// (to allow accounts to create multiple denoms) +// a new denom. It requires a sender address and a subdenomination. +// The (sender_address, sub_denomination) pair must be unique and cannot be +// re-used. The resulting denom created is `factory/{creator +// address}/{subdenom}`. The resultant denom's admin is originally set to be the +// creator, but this can be changed later. The token denom does not indicate the +// current admin. type MsgCreateDenom struct { Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty" yaml:"sender"` - Nonce string `protobuf:"bytes,2,opt,name=nonce,proto3" json:"nonce,omitempty" yaml:"nonce"` + // subdenom can be up to 44 "alphanumeric" characters long. + Subdenom string `protobuf:"bytes,2,opt,name=subdenom,proto3" json:"subdenom,omitempty" yaml:"subdenom"` } func (m *MsgCreateDenom) Reset() { *m = MsgCreateDenom{} } @@ -77,9 +82,9 @@ func (m *MsgCreateDenom) GetSender() string { return "" } -func (m *MsgCreateDenom) GetNonce() string { +func (m *MsgCreateDenom) GetSubdenom() string { if m != nil { - return m.Nonce + return m.Subdenom } return "" } @@ -424,39 +429,39 @@ func init() { } var fileDescriptor_283b6c9a90a846b4 = []byte{ - // 504 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x63, 0x02, 0x01, 0xb6, 0x94, 0xb6, 0x56, 0xa9, 0x82, 0x85, 0x6c, 0xb4, 0x52, 0x11, - 0x48, 0x74, 0x4d, 0x0b, 0x52, 0x25, 0x6e, 0xb8, 0x1c, 0xb8, 0xe4, 0x80, 0xc5, 0x09, 0x55, 0xaa, - 0xd6, 0xce, 0xe2, 0x5a, 0xd4, 0xb3, 0xc1, 0xbb, 0x69, 0x9a, 0x0b, 0xcf, 0xc0, 0x85, 0x67, 0xe0, - 0x55, 0x7a, 0xec, 0x91, 0x93, 0x85, 0x92, 0x37, 0xc8, 0x13, 0xa0, 0xfd, 0x93, 0xc4, 0x51, 0x25, - 0x9a, 0x9c, 0xb8, 0x25, 0x3b, 0xbf, 0xf9, 0x66, 0x76, 0xbe, 0xf1, 0xa2, 0x5d, 0x2e, 0x0a, 0x2e, - 0x72, 0x11, 0x4a, 0xfe, 0x95, 0xc1, 0x17, 0x9a, 0x4a, 0x5e, 0x0e, 0xc3, 0xf3, 0xfd, 0x84, 0x49, - 0xba, 0x1f, 0xca, 0x0b, 0xd2, 0x2b, 0xb9, 0xe4, 0xee, 0x13, 0x8b, 0x91, 0x3a, 0x46, 0x2c, 0xe6, - 0x6d, 0x67, 0x3c, 0xe3, 0x1a, 0x0c, 0xd5, 0x2f, 0x93, 0xe3, 0xf9, 0xa9, 0x4e, 0x0a, 0x13, 0x2a, - 0xd8, 0x4c, 0x31, 0xe5, 0x39, 0x98, 0x38, 0x4e, 0xd1, 0xc3, 0x8e, 0xc8, 0x8e, 0x4a, 0x46, 0x25, - 0x7b, 0xcf, 0x80, 0x17, 0xee, 0x0b, 0xd4, 0x12, 0x0c, 0xba, 0xac, 0x6c, 0x3b, 0x4f, 0x9d, 0xe7, - 0xf7, 0xa3, 0xad, 0x49, 0x15, 0xac, 0x0f, 0x69, 0x71, 0xf6, 0x16, 0x9b, 0x73, 0x1c, 0x5b, 0xc0, - 0x7d, 0x86, 0xee, 0x00, 0x87, 0x94, 0xb5, 0x6f, 0x69, 0x72, 0x73, 0x52, 0x05, 0x0f, 0x0c, 0xa9, - 0x8f, 0x71, 0x6c, 0xc2, 0xf8, 0x18, 0xed, 0x2c, 0x16, 0x89, 0x99, 0xe8, 0x71, 0x10, 0xcc, 0x8d, - 0xd0, 0x06, 0xb0, 0xc1, 0x89, 0xbe, 0xd0, 0x49, 0x57, 0x85, 0x6c, 0x55, 0x6f, 0x52, 0x05, 0x3b, - 0x56, 0x6b, 0x11, 0xc0, 0xf1, 0x3a, 0xb0, 0xc1, 0x27, 0x75, 0xa0, 0xb5, 0xf0, 0x77, 0x74, 0xb7, - 0x23, 0xb2, 0x4e, 0x0e, 0x72, 0x95, 0xde, 0x3f, 0xa0, 0x16, 0x2d, 0x78, 0x1f, 0xa4, 0x6e, 0x7e, - 0xed, 0xe0, 0x31, 0x31, 0x93, 0x22, 0x6a, 0x52, 0xd3, 0xa1, 0x92, 0x23, 0x9e, 0x43, 0xf4, 0xe8, - 0xb2, 0x0a, 0x1a, 0x73, 0x25, 0x93, 0x86, 0x63, 0x9b, 0x8f, 0xb7, 0xd0, 0x86, 0xad, 0x3f, 0xbd, - 0x96, 0x6d, 0x29, 0xea, 0x97, 0xf0, 0x3f, 0x5b, 0x52, 0xf5, 0x67, 0x2d, 0xfd, 0x74, 0x8c, 0xd3, - 0xa7, 0x14, 0x32, 0xf6, 0xae, 0x5b, 0xe4, 0xb0, 0xa2, 0xd3, 0xc6, 0x9d, 0x6b, 0x4e, 0x5b, 0x4f, - 0x4c, 0xd8, 0x7d, 0x85, 0xee, 0x01, 0x1b, 0x68, 0xf9, 0x76, 0x53, 0xa3, 0xdb, 0x93, 0x2a, 0xd8, - 0x9c, 0x1b, 0x49, 0x55, 0x08, 0xc7, 0x33, 0x0a, 0xb7, 0xcd, 0x6e, 0xcc, 0xdb, 0x9a, 0x76, 0x7c, - 0xf0, 0xab, 0x89, 0x9a, 0x1d, 0x91, 0xb9, 0xdf, 0xd0, 0x5a, 0x7d, 0x3f, 0x5f, 0x92, 0x7f, 0x7d, - 0x06, 0x64, 0x71, 0xd1, 0xbc, 0x37, 0xab, 0xd0, 0xb3, 0xb5, 0x3c, 0x46, 0xb7, 0xf5, 0x3e, 0xed, - 0xde, 0x98, 0xad, 0x30, 0x6f, 0x6f, 0x29, 0xac, 0xae, 0xae, 0x57, 0xe3, 0x66, 0x75, 0x85, 0x2d, - 0xa1, 0x5e, 0x37, 0x5a, 0x8f, 0xab, 0x66, 0xf2, 0x12, 0xe3, 0x9a, 0xd3, 0xcb, 0x8c, 0xeb, 0xba, - 0x53, 0xd1, 0xc7, 0xcb, 0x91, 0xef, 0x5c, 0x8d, 0x7c, 0xe7, 0xcf, 0xc8, 0x77, 0x7e, 0x8c, 0xfd, - 0xc6, 0xd5, 0xd8, 0x6f, 0xfc, 0x1e, 0xfb, 0x8d, 0xcf, 0x87, 0x59, 0x2e, 0x4f, 0xfb, 0x09, 0x49, - 0x79, 0x11, 0x5a, 0xe5, 0xbd, 0x33, 0x9a, 0x88, 0xe9, 0x9f, 0xf0, 0xfc, 0x30, 0xbc, 0x58, 0x7c, - 0xf6, 0xe4, 0xb0, 0xc7, 0x44, 0xd2, 0xd2, 0xcf, 0xd3, 0xeb, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, - 0xd0, 0x56, 0x14, 0xa0, 0x1b, 0x05, 0x00, 0x00, + // 510 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0x4f, 0x6e, 0xd3, 0x40, + 0x14, 0xc6, 0x63, 0x02, 0xa1, 0x4c, 0x29, 0x69, 0x4d, 0xa9, 0x82, 0x85, 0x6c, 0x34, 0x52, 0x11, + 0x48, 0x74, 0x86, 0x16, 0xa4, 0x4a, 0xec, 0x70, 0x59, 0xb0, 0xc9, 0x02, 0x8b, 0x15, 0xaa, 0x54, + 0x8d, 0x93, 0xc1, 0xb5, 0x88, 0x67, 0x82, 0x67, 0xd2, 0x34, 0x1b, 0xce, 0xc0, 0x86, 0x33, 0x70, + 0x95, 0x2e, 0xbb, 0x64, 0x65, 0xa1, 0xe4, 0x06, 0x3e, 0x01, 0xf2, 0xcc, 0xc4, 0x71, 0x40, 0x22, + 0xce, 0x8a, 0x5d, 0xe2, 0xf7, 0x7b, 0xdf, 0xfb, 0xf3, 0x3d, 0x1b, 0xec, 0x73, 0x91, 0x70, 0x11, + 0x0b, 0x2c, 0xf9, 0x67, 0xca, 0x3e, 0x91, 0x9e, 0xe4, 0xe9, 0x04, 0x5f, 0x1c, 0x86, 0x54, 0x92, + 0x43, 0x2c, 0x2f, 0xd1, 0x30, 0xe5, 0x92, 0xdb, 0x8f, 0x0c, 0x86, 0xaa, 0x18, 0x32, 0x98, 0xb3, + 0x1b, 0xf1, 0x88, 0x2b, 0x10, 0x17, 0xbf, 0x74, 0x8e, 0xe3, 0xf6, 0x54, 0x12, 0x0e, 0x89, 0xa0, + 0xa5, 0x62, 0x8f, 0xc7, 0x4c, 0xc7, 0xe1, 0x00, 0xdc, 0xeb, 0x8a, 0xe8, 0x24, 0xa5, 0x44, 0xd2, + 0xb7, 0x94, 0xf1, 0xc4, 0x7e, 0x06, 0x5a, 0x82, 0xb2, 0x3e, 0x4d, 0x3b, 0xd6, 0x63, 0xeb, 0xe9, + 0x1d, 0x7f, 0x27, 0xcf, 0xbc, 0xad, 0x09, 0x49, 0x06, 0xaf, 0xa1, 0x7e, 0x0e, 0x03, 0x03, 0xd8, + 0x18, 0x6c, 0x88, 0x51, 0xd8, 0x2f, 0xd2, 0x3a, 0x37, 0x14, 0x7c, 0x3f, 0xcf, 0xbc, 0xb6, 0x81, + 0x4d, 0x04, 0x06, 0x25, 0x04, 0x4f, 0xc1, 0xde, 0x72, 0xb5, 0x80, 0x8a, 0x21, 0x67, 0x82, 0xda, + 0x3e, 0x68, 0x33, 0x3a, 0x3e, 0x53, 0x93, 0x9d, 0x69, 0x45, 0x5d, 0xde, 0xc9, 0x33, 0x6f, 0x4f, + 0x2b, 0xfe, 0x01, 0xc0, 0x60, 0x8b, 0xd1, 0xf1, 0x87, 0xe2, 0x81, 0xd2, 0x82, 0x5f, 0xc1, 0xed, + 0xae, 0x88, 0xba, 0x31, 0x93, 0xeb, 0x0c, 0xf1, 0x0e, 0xb4, 0x48, 0xc2, 0x47, 0x4c, 0xaa, 0x11, + 0x36, 0x8f, 0x1e, 0x22, 0xbd, 0x32, 0x54, 0xac, 0x6c, 0xbe, 0x5d, 0x74, 0xc2, 0x63, 0xe6, 0x3f, + 0xb8, 0xca, 0xbc, 0xc6, 0x42, 0x49, 0xa7, 0xc1, 0xc0, 0xe4, 0xc3, 0x1d, 0xd0, 0x36, 0xf5, 0xe7, + 0x63, 0x99, 0x96, 0xfc, 0x51, 0xca, 0xfe, 0x67, 0x4b, 0x45, 0xfd, 0xb2, 0xa5, 0xef, 0x96, 0xb6, + 0xfc, 0x9c, 0xb0, 0x88, 0xbe, 0xe9, 0x27, 0xf1, 0x5a, 0xad, 0x3d, 0x01, 0xb7, 0xaa, 0x7e, 0x6f, + 0xe7, 0x99, 0x77, 0x57, 0x93, 0xc6, 0x13, 0x1d, 0xb6, 0x5f, 0x80, 0x0d, 0x46, 0xc7, 0x4a, 0xbe, + 0xd3, 0x54, 0xe8, 0x6e, 0x9e, 0x79, 0xdb, 0x0b, 0x23, 0x49, 0x11, 0x82, 0x41, 0x49, 0xc1, 0x8e, + 0xbe, 0x8d, 0x45, 0x5b, 0xf3, 0x8e, 0x8f, 0x7e, 0x34, 0x41, 0xb3, 0x2b, 0x22, 0xfb, 0x0b, 0xd8, + 0xac, 0x1e, 0xea, 0x73, 0xf4, 0xaf, 0xf7, 0x01, 0x2d, 0x1f, 0x9a, 0xf3, 0x6a, 0x1d, 0xba, 0x3c, + 0xcb, 0x53, 0x70, 0x53, 0xdd, 0xd3, 0xfe, 0xca, 0xec, 0x02, 0x73, 0x0e, 0x6a, 0x61, 0x55, 0x75, + 0x75, 0x1a, 0xab, 0xd5, 0x0b, 0xac, 0x86, 0x7a, 0xd5, 0x68, 0xb5, 0xae, 0x8a, 0xc9, 0x35, 0xd6, + 0xb5, 0xa0, 0xeb, 0xac, 0xeb, 0x6f, 0xa7, 0xfc, 0xf7, 0x57, 0x53, 0xd7, 0xba, 0x9e, 0xba, 0xd6, + 0xaf, 0xa9, 0x6b, 0x7d, 0x9b, 0xb9, 0x8d, 0xeb, 0x99, 0xdb, 0xf8, 0x39, 0x73, 0x1b, 0x1f, 0x8f, + 0xa3, 0x58, 0x9e, 0x8f, 0x42, 0xd4, 0xe3, 0x09, 0x36, 0xca, 0x07, 0x03, 0x12, 0x8a, 0xf9, 0x1f, + 0x7c, 0x71, 0x8c, 0x2f, 0x97, 0xbf, 0x7f, 0x72, 0x32, 0xa4, 0x22, 0x6c, 0xa9, 0xef, 0xd4, 0xcb, + 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x53, 0x09, 0xe1, 0xf2, 0x24, 0x05, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -673,10 +678,10 @@ func (m *MsgCreateDenom) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Nonce) > 0 { - i -= len(m.Nonce) - copy(dAtA[i:], m.Nonce) - i = encodeVarintTx(dAtA, i, uint64(len(m.Nonce))) + if len(m.Subdenom) > 0 { + i -= len(m.Subdenom) + copy(dAtA[i:], m.Subdenom) + i = encodeVarintTx(dAtA, i, uint64(len(m.Subdenom))) i-- dAtA[i] = 0x12 } @@ -934,7 +939,7 @@ func (m *MsgCreateDenom) Size() (n int) { if l > 0 { n += 1 + l + sovTx(uint64(l)) } - l = len(m.Nonce) + l = len(m.Subdenom) if l > 0 { n += 1 + l + sovTx(uint64(l)) } @@ -1101,7 +1106,7 @@ func (m *MsgCreateDenom) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Subdenom", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1129,7 +1134,7 @@ func (m *MsgCreateDenom) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Nonce = string(dAtA[iNdEx:postIndex]) + m.Subdenom = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex From 2d8b9bd023f80b027e8dca988ac250bf00e5a1bb Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 27 May 2022 07:48:41 -0700 Subject: [PATCH 25/26] feat: receiver code and tests for change admin in token factory (#1600) --- app/wasm/message_plugin.go | 42 ++++++++++++- app/wasm/test/messages_test.go | 104 +++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/app/wasm/message_plugin.go b/app/wasm/message_plugin.go index fc8c49e9412..d66c4cab0d3 100644 --- a/app/wasm/message_plugin.go +++ b/app/wasm/message_plugin.go @@ -51,6 +51,9 @@ func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddre if contractMsg.MintTokens != nil { return m.mintTokens(ctx, contractAddr, contractMsg.MintTokens) } + if contractMsg.ChangeAdmin != nil { + return m.changeAdmin(ctx, contractAddr, contractMsg.ChangeAdmin) + } if contractMsg.BurnTokens != nil { return m.burnTokens(ctx, contractAddr, contractMsg.BurnTokens) } @@ -76,10 +79,17 @@ func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + msgCreateDenom := tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom) + + if err := msgCreateDenom.ValidateBasic(); err != nil { + return sdkerrors.Wrap(err, "failed validating MsgCreateDenom") + } + // Create denom _, err := msgServer.CreateDenom( sdk.WrapSDKContext(ctx), - tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom)) + msgCreateDenom, + ) if err != nil { return sdkerrors.Wrap(err, "creating denom") } @@ -122,6 +132,36 @@ func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk return nil } +func (m *CustomMessenger) changeAdmin(ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *wasmbindings.ChangeAdmin) ([]sdk.Event, [][]byte, error) { + err := ChangeAdmin(m.tokenFactory, ctx, contractAddr, changeAdmin) + if err != nil { + return nil, nil, sdkerrors.Wrap(err, "failed to change admin") + } + return nil, nil, nil +} + +func ChangeAdmin(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *wasmbindings.ChangeAdmin) error { + if changeAdmin == nil { + return wasmvmtypes.InvalidRequest{Err: "changeAdmin is nil"} + } + newAdminAddr, err := parseAddress(changeAdmin.NewAdminAddress) + if err != nil { + return err + } + + changeAdminMsg := tokenfactorytypes.NewMsgChangeAdmin(contractAddr.String(), changeAdmin.Denom, newAdminAddr.String()) + if err := changeAdminMsg.ValidateBasic(); err != nil { + return err + } + + msgServer := tokenfactorykeeper.NewMsgServerImpl(*f) + _, err = msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), changeAdminMsg) + if err != nil { + return sdkerrors.Wrap(err, "failed changing admin from message") + } + return nil +} + func (m *CustomMessenger) burnTokens(ctx sdk.Context, contractAddr sdk.AccAddress, burn *wasmbindings.BurnTokens) ([]sdk.Event, [][]byte, error) { err := PerformBurn(m.tokenFactory, ctx, contractAddr, burn) if err != nil { diff --git a/app/wasm/test/messages_test.go b/app/wasm/test/messages_test.go index d55473599d8..63278346058 100644 --- a/app/wasm/test/messages_test.go +++ b/app/wasm/test/messages_test.go @@ -64,6 +64,110 @@ func TestCreateDenom(t *testing.T) { } +func TestChangeAdmin(t *testing.T) { + const validDenom = "validdenom" + + tokenCreator := RandomAccountAddress() + + specs := map[string]struct { + actor sdk.AccAddress + changeAdmin *wasmbindings.ChangeAdmin + + expErrMsg string + }{ + "valid": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", tokenCreator.String(), validDenom), + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: tokenCreator, + }, + "typo in factory in denom name": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("facory/%s/%s", tokenCreator.String(), validDenom), + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: tokenCreator, + expErrMsg: "denom prefix is incorrect. Is: facory. Should be: factory: invalid denom", + }, + "invalid address in denom": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", RandomBech32AccountAddress(), validDenom), + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: tokenCreator, + expErrMsg: "failed changing admin from message: unauthorized account", + }, + "other denom name in 3 part name": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", tokenCreator.String(), "invalid denom"), + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: tokenCreator, + expErrMsg: fmt.Sprintf("invalid denom: factory/%s/invalid denom", tokenCreator.String()), + }, + "empty denom": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: "", + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: tokenCreator, + expErrMsg: "invalid denom: ", + }, + "empty address": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", tokenCreator.String(), validDenom), + NewAdminAddress: "", + }, + actor: tokenCreator, + expErrMsg: "address from bech32: empty address string is not allowed", + }, + "creator is a different address": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", tokenCreator.String(), validDenom), + NewAdminAddress: RandomBech32AccountAddress(), + }, + actor: RandomAccountAddress(), + expErrMsg: "failed changing admin from message: unauthorized account", + }, + "change to the same address": { + changeAdmin: &wasmbindings.ChangeAdmin{ + Denom: fmt.Sprintf("factory/%s/%s", tokenCreator.String(), validDenom), + NewAdminAddress: tokenCreator.String(), + }, + actor: tokenCreator, + }, + "nil binding": { + actor: tokenCreator, + expErrMsg: "invalid request: changeAdmin is nil - original request: ", + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + // Setup + osmosis, ctx := SetupCustomApp(t, tokenCreator) + + // Fund actor with 100 base denom creation fees + actorAmount := sdk.NewCoins(sdk.NewCoin(types.DefaultParams().DenomCreationFee[0].Denom, types.DefaultParams().DenomCreationFee[0].Amount.MulRaw(100))) + fundAccount(t, ctx, osmosis, tokenCreator, actorAmount) + + err := wasm.PerformCreateDenom(osmosis.TokenFactoryKeeper, osmosis.BankKeeper, ctx, tokenCreator, &wasmbindings.CreateDenom{ + Subdenom: validDenom, + }) + require.NoError(t, err) + + err = wasm.ChangeAdmin(osmosis.TokenFactoryKeeper, ctx, spec.actor, spec.changeAdmin) + if len(spec.expErrMsg) > 0 { + require.Error(t, err) + actualErrMsg := err.Error() + require.Equal(t, spec.expErrMsg, actualErrMsg) + return + } + require.NoError(t, err) + }) + } +} + func TestMint(t *testing.T) { creator := RandomAccountAddress() osmosis, ctx := SetupCustomApp(t, creator) From e9062fcd694a48da02ee8d92c0479751535f3db1 Mon Sep 17 00:00:00 2001 From: Joe Abbey Date: Fri, 27 May 2022 14:49:43 -0400 Subject: [PATCH 26/26] fix: Only apply v8 fork logic on osmosis-1 (#1610) --- app/upgrades/v8/forks.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/upgrades/v8/forks.go b/app/upgrades/v8/forks.go index 3ecb72526b8..025ef17e77f 100644 --- a/app/upgrades/v8/forks.go +++ b/app/upgrades/v8/forks.go @@ -9,6 +9,12 @@ import ( // RunForkLogic executes height-gated on-chain fork logic for the Osmosis v8 // upgrade. func RunForkLogic(ctx sdk.Context, appKeepers *keepers.AppKeepers) { + // Only proceed with v8 for mainnet, testnets need not adjust their pool incentives or unbonding. + // https://github.com/osmosis-labs/osmosis/issues/1609 + if ctx.ChainID() != "osmosis-1" { + return + } + for i := 0; i < 100; i++ { ctx.Logger().Info("I am upgrading to v8") }