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 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/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), 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 + 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/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/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/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) 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