x: add spec for remaining modules (#494)

[R4R] Add spec for remaining modules
This commit is contained in:
Federico Kunze 2020-05-08 17:42:39 -04:00 committed by GitHub
commit 5e931863fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 650 additions and 93 deletions

View File

@ -22,7 +22,7 @@ const (
EventTypeCdpLiquidation = types.EventTypeCdpLiquidation
EventTypeBeginBlockerFatal = types.EventTypeBeginBlockerFatal
AttributeKeyCdpID = types.AttributeKeyCdpID
AttributeKeyDepositor = types.AttributeKeyDepositor
AttributeKeyDeposit = types.AttributeKeyDeposit
AttributeValueCategory = types.AttributeValueCategory
AttributeKeyError = types.AttributeKeyError
ModuleName = types.ModuleName

View File

@ -40,7 +40,7 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
types.EventTypeCdpLiquidation,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
sdk.NewAttribute(types.AttributeKeyDeposit, dep.String()),
),
)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(dep.Amount))

View File

@ -18,10 +18,12 @@ The cdp module emits the following events:
### MsgWithdraw
| Type | Attribute Key | Attribute Value |
|---------|---------------|------------------|
| message | module | cdp |
| message | sender | {sender address} |
| Type | Attribute Key | Attribute Value |
|---------|--------------- |-----------------------|
| message | cdp_withdrawal | {collateral amount} |
| message | cdp_id | {cdp_id} |
| message | module | cdp |
| message | sender | {sender address} |
### MsgDeposit
@ -43,10 +45,13 @@ The cdp module emits the following events:
### MsgRepayDebt
| Type | Attribute Key | Attribute Value |
|---------|---------------|------------------|
| message | module | cdp |
| message | sender | {sender address} |
| Type | Attribute Key | Attribute Value |
|---------------|---------------|--------------------|
| cdp_repayment | amount | {repayment amount} |
| cdp_repayment | cdp_id | {cdp id} |
| cdp_close | cdp_id | {cdp id} |
| message | module | cdp |
| message | sender | {sender address} |
## BeginBlock
@ -54,6 +59,6 @@ The cdp module emits the following events:
|-------------------------|---------------|---------------------|
| cdp_liquidation | module | cdp |
| cdp_liquidation | cdp_id | {cdp id} |
| cdp_liquidation | depositor | {depositor address} |
| cdp_liquidation | deposit | {deposit} |
| cdp_begin_blocker_error | module | cdp |
| cdp_begin_blocker_error | error_message | {error} |

View File

@ -12,7 +12,7 @@ const (
EventTypeBeginBlockerFatal = "cdp_begin_block_error"
AttributeKeyCdpID = "cdp_id"
AttributeKeyDepositor = "depositor"
AttributeKeyDeposit = "deposit"
AttributeValueCategory = "cdp"
AttributeKeyError = "error_message"
)

View File

@ -12,6 +12,7 @@ const (
AttributeKeyCommitteeID = types.AttributeKeyCommitteeID
AttributeKeyProposalCloseStatus = types.AttributeKeyProposalCloseStatus
AttributeKeyProposalID = types.AttributeKeyProposalID
AttributeKeyVoter = types.AttributeKeyVoter
AttributeValueCategory = types.AttributeValueCategory
AttributeValueProposalFailed = types.AttributeValueProposalFailed
AttributeValueProposalPassed = types.AttributeValueProposalPassed

View File

@ -74,6 +74,7 @@ func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress
types.EventTypeProposalVote,
sdk.NewAttribute(types.AttributeKeyCommitteeID, fmt.Sprintf("%d", com.ID)),
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", pr.ID)),
sdk.NewAttribute(types.AttributeKeyVoter, voter.String()),
),
)
return nil

View File

@ -0,0 +1,5 @@
# Concepts
For a general introduction to governance using the Comsos-SDK, see [x/gov](https://github.com/cosmos/cosmos-sdk/blob/v0.38.3/x/gov/spec/01_concepts.md).
This module provides companion governance functionality to `x/gov` by allowing the creation of committees, or groups of addresses that can vote on proposals for which they have permission and which bypass the usual on-chain governance structures. Permissions scope the types of proposals that committees can submit and vote on. This allows for committees with unlimited breadth (ie, a committee can have permission to perform any governance action), or narrowly scoped abilities (ie, a committee can only change a single parameter of a single module within a specified range). Further, vote tallying is "first-past-the-post", so proposals can be enacted more rapidly and with greater flexibility than permitted by `x/gov`.

View File

@ -0,0 +1,19 @@
# State
## Genesis state
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the committee module to resume.
```go
// GenesisState is state that must be provided at chain genesis.
type GenesisState struct {
NextProposalID uint64 `json:"next_proposal_id" yaml:"next_proposal_id"`
Committees []Committee `json:"committees" yaml:"committees"`
Proposals []Proposal `json:"proposals" yaml:"proposals"`
Votes []Vote `json:"votes" yaml:"votes"`
}
```
## Store
For complete implementation details for how items are stored, see [keys.go](../types/keys.go). The committee module store state consists of committees, proposals, and votes. When a proposal expires or passes, the proposal and associated votes are deleted from state.

View File

@ -0,0 +1,34 @@
# Messages
Committee members submit proposals using a `MsgSubmitProposal`
```go
// MsgSubmitProposal is used by committee members to create a new proposal that they can vote on.
type MsgSubmitProposal struct {
PubProposal PubProposal `json:"pub_proposal" yaml:"pub_proposal"`
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
CommitteeID uint64 `json:"committee_id" yaml:"committee_id"`
}
```
## State Modifications
* Generate new `ProposalID`
* Create new `Proposal` with deadline equal to the time that the proposal will expire.
Committee members vote 'yes' on a proposal using a `MsgVote`
```go
// MsgVote is submitted by committee members to vote on proposals.
type MsgVote struct {
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
}
```
## State Modifications
* Create a new `Vote`
* If the proposal is over the threshold:
* Enact the proposal (proposals may cause state modifications)
* Delete the proposal and associated votes

View File

@ -0,0 +1,33 @@
# Events
The `x/committee` module emits the following events:
## MsgSubmitProposal
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|--------------------|
| proposal_submit | committee_id | {committee ID} |
| proposal_submit | proposal_id | {proposal ID} |
| message | module | committee |
| message | sender | {sender address} |
## MsgVote
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|--------------------|
| proposal_vote | committee_id | {committee ID} |
| proposal_vote | proposal_id | {proposal ID} |
| proposal_vote | voter | {voter address} |
| proposal_close | committee_id | {committee ID} |
| proposal_close | proposal_id | {proposal ID} |
| proposal_close | status | {outcome} |
| message | module | committee |
| message | sender | {sender address} |
## BeginBlock
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|--------------------|
| proposal_close | committee_id | {committee ID} |
| proposal_close | proposal_id | {proposal ID} |
| proposal_close | status | proposal_timeout |

View File

@ -0,0 +1,3 @@
# Parameters
The committee module has no parameters. Committees are created using the `x/gov` module and and inherit the parameters controlling governance proposals from `x/gov`.

View File

@ -0,0 +1,10 @@
# Begin Block
At the start of each block, expired proposals are deleted. The logic is as follows:
```go
// BeginBlocker runs at the start of every block.
func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) {
k.CloseExpiredProposals(ctx)
}
```

View File

@ -3,17 +3,25 @@
## Table of Contents
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**
5. **[Params](05_params.md)**
6. **[BeginBlock](06_begin_block.md)**
## Overview
The `x/committee` module is an additional governance module to `cosmos-sdk/x/gov`.
The `x/committee` module is Cosmos-SDK module that acts as an additional governance module to `cosmos-sdk/x/gov`.
It allows groups of accounts to vote on and enact proposals without a full chain governance vote. Certain proposal types can then be decided on quickly in emergency situations, or low risk parameter updates can be delegated to a smaller group of individuals.
Committees work with "proposals", using the same type from the `gov` module so they are compatible with all existing proposal types such as param changes, or community pool spend, or text proposals.
Committees have members and permissions.
Committees have members and permissions. Committees are 'elected' via traditional `gov` proposals - ie. all coin-holders vote on the creation, deletion, and updating of committees.
Members vote on proposals, with just simple one vote per member, no deposits or slashing. More sophisticated voting could be added.
Members of committees vote on proposals, with one vote per member and no deposits or slashing. Only a member of a committee can submit a proposal for that committee. More sophisticated voting could be added, as well as the ability for committees to edit themselves or other committees. A proposal passes when the number of votes is over the threshold for that committee. Vote thresholds are set per committee. Committee members vote yes by casting a vote and vote no by abstaining from voting and letting the proposal expire.
Permissions scope the allowed set of proposals a committee can enact. For example:
@ -21,6 +29,4 @@ Permissions scope the allowed set of proposals a committee can enact. For exampl
- allow the committee to change auction bid increments, but only within the range [0, 0.1]
- allow the committee to only disable cdp msg types, but not staking or gov
A permission acts as a filter for incoming gov proposals, rejecting them if they do not pass. A permission can be any type with a method `Allows(p Proposal) bool`. They reject all proposals that they don't explicitly allow.
This allows permissions to be parameterized to allow fine grained control specified at runtime. For example a generic parameter permission type can allow a committee to only change a particular param, or only change params within a certain percentage.
A permission acts as a filter for incoming gov proposals, rejecting them at the handler if they do not have the required permissions. A permission can be any type with a method `Allows(p Proposal) bool`. The handler will reject all proposals that are not explicitly allowed. This allows permissions to be parameterized to allow fine grained control specified at runtime. For example a generic parameter permission type can allow a committee to only change a particular param, or only change params within a certain range.

View File

@ -10,6 +10,7 @@ const (
AttributeKeyCommitteeID = "committee_id"
AttributeKeyProposalID = "proposal_id"
AttributeKeyProposalCloseStatus = "status"
AttributeKeyVoter = "voter"
AttributeValueProposalPassed = "proposal_passed"
AttributeValueProposalTimeout = "proposal_timeout"
AttributeValueProposalFailed = "proposal_failed"

View File

@ -1,9 +1,10 @@
package incentive
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper
// ALIASGEN: github.com/kava-labs/kava/x/incentive/types
package incentive
import (
"github.com/kava-labs/kava/x/incentive/keeper"
@ -11,18 +12,24 @@ import (
)
const (
EventTypeClaim = types.EventTypeClaim
AttributeValueCategory = types.AttributeValueCategory
AttributeKeySender = types.AttributeKeySender
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
DefaultParamspace = types.DefaultParamspace
QuerierRoute = types.QuerierRoute
QueryGetClaims = types.QueryGetClaims
RestClaimOwner = types.RestClaimOwner
RestClaimDenom = types.RestClaimDenom
QueryGetParams = types.QueryGetParams
EventTypeClaim = types.EventTypeClaim
EventTypeRewardPeriod = types.EventTypeRewardPeriod
EventTypeClaimPeriod = types.EventTypeClaimPeriod
EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry
AttributeValueCategory = types.AttributeValueCategory
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
DefaultParamspace = types.DefaultParamspace
QuerierRoute = types.QuerierRoute
QueryGetClaims = types.QueryGetClaims
RestClaimOwner = types.RestClaimOwner
RestClaimDenom = types.RestClaimDenom
QueryGetParams = types.QueryGetParams
)
var (
@ -46,6 +53,7 @@ var (
NewRewardPeriod = types.NewRewardPeriod
NewClaimPeriod = types.NewClaimPeriod
NewClaim = types.NewClaim
NewRewardPeriodFromReward = types.NewRewardPeriodFromReward
// variable aliases
ModuleCdc = types.ModuleCdc

View File

@ -1,8 +1,6 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth"
@ -29,10 +27,13 @@ func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, denom string,
return err
}
k.DeleteClaim(ctx, addr, denom, id)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", addr)),
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
),
)
return nil
@ -103,6 +104,13 @@ func (k Keeper) DeleteExpiredClaimsAndClaimPeriods(ctx sdk.Context) {
return false
})
k.DeleteClaimPeriod(ctx, cp.ID, cp.Denom)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimPeriodExpiry,
sdk.NewAttribute(types.AttributeKeyClaimPeriod, cp.String()),
),
)
return false
})
}

View File

@ -20,18 +20,15 @@ func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod)
// CreateNewRewardPeriod creates a new reward period from the input reward
func (k Keeper) CreateNewRewardPeriod(ctx sdk.Context, reward types.Reward) {
// reward periods store the amount of rewards paid PER SECOND
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
rp := types.RewardPeriod{
Denom: reward.Denom,
Start: ctx.BlockTime(),
End: ctx.BlockTime().Add(reward.Duration),
Reward: rewardCoinPerSecond,
ClaimEnd: ctx.BlockTime().Add(reward.Duration).Add(reward.ClaimDuration),
ClaimTimeLock: reward.TimeLock,
}
rp := types.NewRewardPeriodFromReward(reward, ctx.BlockTime())
k.SetRewardPeriod(ctx, rp)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRewardPeriod,
sdk.NewAttribute(types.AttributeKeyRewardPeriod, rp.String()),
),
)
}
// CreateAndDeleteRewardPeriods creates reward periods for active rewards that don't already have a reward period and deletes reward periods for inactive rewards that currently have a reward period
@ -95,6 +92,12 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, denom string, end time.Time, timeLock time.Duration) {
id := k.GetNextClaimPeriodID(ctx, denom)
claimPeriod := types.NewClaimPeriod(denom, id, end, timeLock)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimPeriod,
sdk.NewAttribute(types.AttributeKeyClaimPeriod, claimPeriod.String()),
),
)
k.SetClaimPeriod(ctx, claimPeriod)
k.SetNextClaimPeriodID(ctx, denom, id+1)
}

View File

@ -0,0 +1,5 @@
# Concepts
This module presents an implementation of user incentives that are controlled by governance. When users take a certain action, in this case opening a CDP, they become eligible for rewards. Rewards are __opt in__ meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf).
When governance adds a collateral type to be eligible for rewards, they set the rate (coins/time) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `Claim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends.

View File

@ -0,0 +1,57 @@
# State
## Parameters and Genesis State
`Parameters` define the collateral types which are eligible for rewards, the rate at which rewards are given to users, and the amount of time rewards must vest before users can transfer them.
```go
// Params governance parameters for the incentive module
type Params struct {
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards
Rewards Rewards `json:"rewards" yaml:"rewards"`
}
// Reward stores the specified state for a single reward period.
type Reward struct {
Active bool `json:"active" yaml:"active"` // governance switch to disable a period
Denom string `json:"denom" yaml:"denom"` // the collateral denom rewards apply to, must be found in the cdp collaterals
AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
}
```
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the incentive module to resume.
```go
// GenesisState is the state that must be provided at genesis.
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"`
Claims Claims `json:"claims" yaml:"claims"`
NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"`
}
```
## Store
For complete details for how items are stored, see [keys.go](../types/keys.go).
### Reward Period Creation
At genesis, or when a collateral is added to rewards, a `RewardPeriod` is created in the store by adding to the existing array of `[]RewardPeriod`. If the previous period for that collateral expired, it is deleted. This implies that, for each collateral, there will only ever be one reward period.
### Reward Period Deletion
When a `RewardPeriod` expires, a new `ClaimPeriod` is created in the store with the next sequential ID for that collateral (ie, if the previous claim period was ID 1, the next one will be ID 2) and the current `RewardPeriod` is deleted from the array of `[]RewardPeriod`.
### Reward Claim Creation
Every block, CDPs are iterated over and the collateral denom is checked for rewards eligibility. For eligible CDPs, a `Claim` is created in the store for all CDP owners, if one doesn't already exist. The claim object is associated with a `ClaimPeriod` via the ID. This implies that a `Claim` is created before `ClaimPeriod` are created. Therefore, a user who submits a `MsgClaimReward` will only be paid out IF 1) they have one or more active `Claim` objects, and 2) the `ClaimPeriod` with the associated ID for that object exists AND the current block time is between the start time and end time for that `ClaimPeriod`.
### Reward Claim Deletion
For claimed rewards, the `Claim` is deleted from the store by deleting the key associated with that denom, ID, and owner. Unclaimed rewards are handled as follows: Each block, the `ClaimPeriod` objects for each denom are iterated over and checked for expiry. If expired, all `Claim` objects for that ID are deleted, as well as the `ClaimPeriod` object. Since claim periods are monotonically increasing, once a non-expired claim period is reached, the iteration can be stopped.

View File

@ -0,0 +1,16 @@
# Messages
Users claim rewards using a `MsgClaimReward`.
```go
// MsgClaimReward message type used to claim rewards
type MsgClaimReward struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Denom string `json:"denom" yaml:"denom"`
}
```
## State Modifications
* Accumulated rewards for active claims are transferred from the `kavadist` module account to the users account as vesting coins
* The corresponding claim object(s) are deleted from the store

View File

@ -0,0 +1,20 @@
# Events
The `x/incentive` module emits the following events:
## MsgClaimReward
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|--------------------|
| claim_reward | claimed_by | {claiming address} |
| claim_reward | claim_amount | {amount claimed} |
| message | module | incentive |
| message | sender | {sender address} |
## BeginBlock
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|--------------------|
| new_claim_period | claim_period | {claim period} |
| new_reward_period | reward_period | {reward period} |
| claim_period_expiry | claim_period | {claim period} |

View File

@ -0,0 +1,19 @@
# Parameters
The incentive module contains the following parameters:
| Key | Type | Example | Description |
|------------|----------------|---------------|--------------------------------------------------|
| Active | bool | "true" | boolean for if this module is active |
| Rewards | array (Reward) | [{see below}] | array of params for each inflationary period |
Each `Reward` has the following parameters
| Key | Type | Example | Description |
|------------------|--------------------|----------------------------------|----------------------------------------------------------------|
| Active | bool | "true | boolean for if rewards for this collateral are active |
| Denom | string | "bnb" | the collateral for which rewards are eligible |
| AvailableRewards | object (coin) | {"denom":"kava","amount":"1000"} | the rewards available per reward period |
| Duration | string (time ns) | "172800000000000" | the duration of each reward period |
| TimeLock | string (time ns) | "172800000000000" | the duration for which claimed rewards will be vesting |
| ClaimDuration | string (time ns) | "172800000000000" | how long users have to claim rewards before they expire |

View File

@ -0,0 +1,11 @@
# Begin Block
At the start of each block, expired claims and claim periods are deleted, rewards are applied to CDPs for any ongoing reward periods, expired reward periods are deleted and replaced with a new reward period (if active), and claim periods are created for expiring reward periods. The logic is as follows:
```go
func BeginBlocker(ctx sdk.Context, k Keeper) {
k.DeleteExpiredClaimsAndClaimPeriods(ctx)
k.ApplyRewardsToCdps(ctx)
k.CreateAndDeleteRewardPeriods(ctx)
}
```

View File

@ -0,0 +1,17 @@
# `incentive`
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**
5. **[Params](05_params.md)**
6. **[BeginBlock](06_begin_block.md)**
## Abstract
`x/incentive` is an implementation of a Cosmos SDK Module that allows for governance controlled user incentives for users who create stable assets by opening a collateralized debt position (CDP). Governance proposes an array of collateral rewards, with each item representing a collateral type that will be eligible for rewards. Each collateral reward specifies the number of coins awarded per period, the length of rewards periods, the length of claim periods. Governance can alter the collateral rewards using parameter change proposals as well as adding or removing collateral types. All changes to parameters would take place in the _next_ period.
### Dependencies
This module depends on `x/cdp` for users to be able to create CDPs and on `x/kavadist`, which controls the module account from where rewards are spent. In the event that the module account is not funded, user's attempt to claim rewards will fail.

View File

@ -1,8 +1,15 @@
package types
// Events emitted by the incentive module
const (
EventTypeClaim = "claim_reward"
EventTypeClaim = "claim_reward"
EventTypeRewardPeriod = "new_reward_period"
EventTypeClaimPeriod = "new_claim_period"
EventTypeClaimPeriodExpiry = "claim_period_expiry"
AttributeValueCategory = ModuleName
AttributeKeySender = "sender"
AttributeValueCategory = ModuleName
AttributeKeyClaimedBy = "claimed_by"
AttributeKeyClaimAmount = "claim_amount"
AttributeKeyRewardPeriod = "reward_period"
AttributeKeyClaimPeriod = "claim_period"
)

View File

@ -25,8 +25,8 @@ func (rp RewardPeriod) String() string {
End: %s,
Reward: %s,
Claim End: %s,
Claim Time Lock: %s`,
rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
Claim Time Lock: %s
`, rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
}
// NewRewardPeriod returns a new RewardPeriod
@ -52,6 +52,16 @@ type ClaimPeriod struct {
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
}
// String implements fmt.Stringer
func (cp ClaimPeriod) String() string {
return fmt.Sprintf(`Claim Period:
Denom: %s,
ID: %d,
End: %s,
Claim Time Lock: %s
`, cp.Denom, cp.ID, cp.End, cp.TimeLock)
}
// NewClaimPeriod returns a new ClaimPeriod
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod {
return ClaimPeriod{
@ -89,9 +99,24 @@ func (c Claim) String() string {
Owner: %s,
Denom: %s,
Reward: %s,
Claim Period ID: %d,`,
c.Owner, c.Denom, c.Reward, c.ClaimPeriodID)
Claim Period ID: %d,
`, c.Owner, c.Denom, c.Reward, c.ClaimPeriodID)
}
// Claims array of Claim
type Claims []Claim
// NewRewardPeriodFromReward returns a new reward period from the input reward and block time
func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod {
// note: reward periods store the amount of rewards paid PER SECOND
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
return RewardPeriod{
Denom: reward.Denom,
Start: blockTime,
End: blockTime.Add(reward.Duration),
Reward: rewardCoinPerSecond,
ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration),
ClaimTimeLock: reward.TimeLock,
}
}

View File

@ -81,7 +81,7 @@ func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, ti
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeKavaDist,
sdk.NewAttribute(types.AttributeKeyInflation, amountToMint.String()),
sdk.NewAttribute(types.AttributeKeyInflation, sdk.NewCoin(denom, amountToMint).String()),
),
)
return nil

View File

@ -0,0 +1,3 @@
# Concepts
The minting mechanism in this module is designed to allow governance to determine a set of inflationary periods and the APR rate of inflation for each period. This module mints coins each block according to the schedule such that after 1 year the APR inflation worth of coins will have been minted. Governance can alter the APR inflation using a parameter change proposal. Parameter change proposals that change the APR will take effect in the block after they pass.

View File

@ -0,0 +1,30 @@
# State
## Parameters and Genesis State
`Parameters` define the rate at which inflationary coins are minted and for how long inflationary periods last.
```go
// Params governance parameters for kavadist module
type Params struct {
Active bool `json:"active" yaml:"active"`
Periods Periods `json:"periods" yaml:"periods"`
}
// Period stores the specified start and end dates, and the inflation, expressed as a decimal representing the yearly APR of tokens that will be minted during that period
type Period struct {
Start time.Time `json:"start" yaml:"start"` // example "2020-03-01T15:20:00Z"
End time.Time `json:"end" yaml:"end"` // example "2020-06-01T15:20:00Z"
Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation
}
```
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the kavadist module to resume.
```go
// GenesisState is the state that must be provided at genesis.
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
}
```

View File

@ -0,0 +1,3 @@
# Messages
There are no messages in the kavadist module. All state transitions are controlled by parameters, which can be updated via parameter change proposals.

View File

@ -0,0 +1,10 @@
# Events
The `x/kavadist` module emits the following events:
## BeginBlock
| Type | Attribute Key | Attribute Value |
|----------------------|---------------------|-----------------|
| kavadist | kava_dist_inflation | {amount} |
| kavadist | kava_dist_status | "inactive" |

View File

@ -0,0 +1,15 @@
# Parameters
The kavadist module has the following parameters:
| Key | Type | Example | Description |
|------------|----------------|---------------|--------------------------------------------------|
| Periods | array (Period) | [{see below}] | array of params for each inflationary period |
Each `Period` has the following parameters
| Key | Type | Example | Description |
|------------|--------------------|--------------------------|----------------------------------------------------------------|
| Start | time.Time | "2020-03-01T15:20:00Z" | the time when the period will start |
| End | time.Time | "2020-06-01T15:20:00Z" | the time when the period will end |
| Inflation | sdk.Dec | "1.000000003022265980" | the per-second inflation for the period |

View File

@ -0,0 +1,12 @@
# Begin Block
At the start of each block, the inflationary coins for the ongoing period, if any, are minted. The logic is as follows:
```go
func BeginBlocker(ctx sdk.Context, k Keeper) {
err := k.MintPeriodInflation(ctx)
if err != nil {
panic(err)
}
}
```

13
x/kavadist/spec/README.md Normal file
View File

@ -0,0 +1,13 @@
# `kavadist`
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**
5. **[Params](05_params.md)**
6. **[BeginBlock](06_begin_block.md)**
## Abstract
`x/kavadist` is an implementation of a Cosmos SDK Module that allows for governance controlled minting of coins into a module account. Coins are minted during inflationary periods, which each period have a governance specified APR and duration. This module does not provide functionality for spending or distributing the minted coins.

View File

@ -9,15 +9,15 @@ import (
// EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset.
for _, a := range k.GetMarkets(ctx) {
if a.Active {
err := k.SetCurrentPrices(ctx, a.MarketID)
for _, market := range k.GetMarkets(ctx) {
if market.Active {
err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil {
// In the event of failure, emit an event.
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeNoValidPrices,
sdk.NewAttribute(AttributeKeyPriceUpdateFailed, fmt.Sprintf("%s", a.MarketID)),
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
),
)
continue

View File

@ -12,26 +12,25 @@ import (
// nolint
const (
EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated
EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice
EventTypeNoValidPrices = types.EventTypeNoValidPrices
AttributeValueCategory = types.AttributeValueCategory
AttributeMarketID = types.AttributeMarketID
AttributeMarketPrice = types.AttributeMarketPrice
AttributeOracle = types.AttributeOracle
AttributeExpiry = types.AttributeExpiry
AttributeKeyPriceUpdateFailed = types.AttributeKeyPriceUpdateFailed
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
TypeMsgPostPrice = types.TypeMsgPostPrice
QueryGetParams = types.QueryGetParams
QueryMarkets = types.QueryMarkets
QueryOracles = types.QueryOracles
QueryRawPrices = types.QueryRawPrices
QueryPrice = types.QueryPrice
EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated
EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice
EventTypeNoValidPrices = types.EventTypeNoValidPrices
AttributeValueCategory = types.AttributeValueCategory
AttributeMarketID = types.AttributeMarketID
AttributeMarketPrice = types.AttributeMarketPrice
AttributeOracle = types.AttributeOracle
AttributeExpiry = types.AttributeExpiry
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
TypeMsgPostPrice = types.TypeMsgPostPrice
QueryGetParams = types.QueryGetParams
QueryMarkets = types.QueryMarkets
QueryOracles = types.QueryOracles
QueryRawPrices = types.QueryRawPrices
QueryPrice = types.QueryPrice
)
// nolint

View File

@ -0,0 +1,3 @@
# Concepts
Prices can be posted by any account which is added as an oracle. Oracles are specific to each market and can be updated via param change proposals. When an oracle posts a price, they submit a message to the blockchain that contains the current price for that market and a time when that price should be considered expired. If an oracle posts a new price, that price becomes the current price for that oracle, regardless of the previous price's expiry. A group of prices posted by a set of oracles for a particular market are referred to as 'raw prices' and the current median price of all valid oracle prices is referred to as the 'current price'. Each block, the current price for each market is determined by calculating the median of the raw prices.

View File

@ -0,0 +1,44 @@
# State
## Parameters and genesis state
`Paramaters` determine which markets are tracked by the pricefeed and which oracles are authorized to post prices for a given market. There is only one active parameter set at any given time. Updates to parameters can be made via on-chain parameter update proposals.
```go
// Params params for pricefeed. Can be altered via governance
type Params struct {
Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed
}
// Market an asset in the pricefeed
type Market struct {
MarketID string `json:"market_id" yaml:"market_id"`
BaseAsset string `json:"base_asset" yaml:"base_asset"`
QuoteAsset string `json:"quote_asset" yaml:"quote_asset"`
Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"`
Active bool `json:"active" yaml:"active"`
}
type Markets []Market
```
`GenesisState` defines the state that must be persisted when the blockchain stops/stars in order for the normal function of the pricefeed to resume.
```go
// GenesisState - pricefeed state that must be provided at genesis
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"`
}
// PostedPrice price for market posted by a specific oracle
type PostedPrice struct {
MarketID string `json:"market_id" yaml:"market_id"`
OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"`
Price sdk.Dec `json:"price" yaml:"price"`
Expiry time.Time `json:"expiry" yaml:"expiry"`
}
type PostedPrices []PostedPrice
```

View File

@ -0,0 +1,20 @@
# Messages
## Posting Prices
An authorized oraclef for a particular market can post the current price for that market using the `MsgPostPrice` type.
```go
// MsgPostPrice struct representing a posted price message.
// Used by oracles to input prices to the pricefeed
type MsgPostPrice struct {
From sdk.AccAddress `json:"from" yaml:"from"` // client that sent in this address
MarketID string `json:"market_id" yaml:"market_id"` // asset code used by exchanges/api
Price sdk.Dec `json:"price" yaml:"price"` // price in decimal (max precision 18)
Expiry time.Time `json:"expiry" yaml:"expiry"` // expiry time
}
```
### State Modifications
* Update the raw price for the oracle for this market. This replaces any previous price for that oracle.

View File

@ -0,0 +1,22 @@
# Events
The `x/pricefeed` module emits the following events:
## MsgPostPrice
| Type | Attribute Key | Attribute Value |
|----------------------|---------------|------------------|
| oracle_updated_price | market_id | {market ID} |
| oracle_updated_price | oracle | {oracle} |
| oracle_updated_price | market_price | {price} |
| oracle_updated_price | expiry | {expiry} |
| message | module | pricefeed |
| message | sender | {sender address} |
## BeginBlock
| Type | Attribute Key | Attribute Value |
|----------------------|-----------------|-----------------|
| market_price_updated | market_id | {market ID} |
| market_price_updated | market_price | {price} |
| no_valid_prices | market_id | {market ID} |

View File

@ -0,0 +1,17 @@
# Parameters
The pricefeed module has the following parameters:
| Key | Type | Example | Description |
|------------|----------------|---------------|--------------------------------------------------|
| Markets | array (Market) | [{see below}] | array of params for each market in the pricefeed |
Each `Market` has the following parameters
| Key | Type | Example | Description |
|------------|--------------------|--------------------------|----------------------------------------------------------------|
| MarketID | string | "bnb:usd" | identifier for the market -- **must** be unique across markets |
| BaseAsset | string | "bnb" | the base asset for the market pair |
| QuoteAsset | string | "usd" | the quote asset for the market pair |
| Oracles | array (AccAddress) | ["kava1...", "kava1..."] | addresses which can post prices for the market |
| Active | bool | true | flag to disable oracle interactions with the module |

View File

@ -0,0 +1,26 @@
# End Block
At the end of each block, the current price is calculated as the median of all raw prices for each market. The logic is as follows:
```go
// EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset.
for _, market := range k.GetMarkets(ctx) {
if market.Active {
err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil {
// In the event of failure, emit an event.
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeNoValidPrices,
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
),
)
continue
}
}
}
return
}
```

View File

@ -0,0 +1,13 @@
# `pricefeed`
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Events](04_events.md)**
5. **[Params](05_params.md)**
6. **[EndBlock](06_end_block.md)**
## Abstract
`x/pricefeed` is an implementation of a Cosmos SDK Module that handles the posting of prices for various markets by a group of whitelisted oracles. At the end of each block, the median price of all oracle posted prices is determined for each market and stored.

View File

@ -6,10 +6,9 @@ const (
EventTypeOracleUpdatedPrice = "oracle_updated_price"
EventTypeNoValidPrices = "no_valid_prices"
AttributeValueCategory = ModuleName
AttributeMarketID = "market_id"
AttributeMarketPrice = "market_price"
AttributeOracle = "oracle"
AttributeExpiry = "expiry"
AttributeKeyPriceUpdateFailed = "price_update_failed"
AttributeValueCategory = ModuleName
AttributeMarketID = "market_id"
AttributeMarketPrice = "market_price"
AttributeOracle = "oracle"
AttributeExpiry = "expiry"
)

View File

@ -1,8 +1,7 @@
# Concepts
The validator-vesting module is responsible for managing Validator Vesting Accounts, a vesting account for which the release of coins is tied to the validation of the blockchain. Validator Vesting Accounts implemnt the cosmos-sdk vesting account spec, which can be found [here](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec).
The validator-vesting module is responsible for managing Validator Vesting Accounts, a vesting account for which the release of coins is tied to the validation of the blockchain. Validator Vesting Accounts implement the cosmos-sdk vesting account spec, which can be found [here](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec).
The main concept the Validator Vesting Account introduces is that of _conditional vesting_, or vesting accounts in which it is possible for some or all of the vesting coins to fail to vest. For Validator Vesting Accounts, vesting is broken down into user-specified __vesting periods__. Each vesting period specifies an amount of coins that could vest in that period, and how long the vesting period lasts.
For each vesting period, a __signing threshold__ is specified, which is the percentage of blocks that must be signed for the coins to successfully vest. After a period ends, coins that are successfully vested become freely spendable. Coins that do not successfuly vest are burned, or sent to an optional return address.
For each vesting period, a __signing threshold__ is specified, which is the percentage of blocks that must be signed for the coins to successfully vest. After a period ends, coins that are successfully vested become freely spendable. Coins that do not successfully vest are burned, or sent to an optional return address.

View File

@ -2,24 +2,26 @@
## Validator Vesting Account type
Validator Vesting Accounts implement the `cosmos-sdk` vesting account spec and extend the `PeriodicVestingAccountType`:
Validator Vesting Accounts implement the `cosmos-sdk` vesting account interfaces and extends the `PeriodicVestingAccountType`:
```go
type ValidatorVestingAccount struct {
*vesting.PeriodicVestingAccount
ValidatorAddress sdk.ConsAddress // The validator address which will be used to check if blocks were signed
ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` // The account where coins will be returned in the event of a failed vesting period
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` // The percentage of blocks, as an integer between 0 and 100, that must be signed each period for coins to successfully vest.
MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"` // An array of two integers which track the number of blocks that were not signed during the current period and the total number of blocks which have passed during the current period, respectively.
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` //An 2d array with length equal to the number of vesting periods. After each period, the value at the first index of that period is updated with 1 to represent that the period is over. The value at the second index is updated to 0 for unsucessful vesting and 1 for successful vesting.
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` // The debt currently owed by the account. Debt accumulates in the event of unsuccessful vesting periods.
}
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` //An 2d array with length equal to the number of vesting periods. After each period, the value at the first index of that period is updated with 1 to represent that the period is over. The value at the second index is updated to 0 for unsuccessful vesting and 1 for successful vesting.
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` // The debt currently owed by the account. Debt accumulates in the event of unsuccessful vesting periods.
}
```
## Stores
There is one `KVStore` in `validator-vesting` which stores
* A mapping from each ValidatorVestingAccount `address` to `[]Byte{0}`
* A mapping from `previous_block_time_prefix` to `time.Time`
The use of `[]Byte{0}` value for each `address` key reflects that this module only accesses the store to get or iterate over keys, and does not require storing an value.
The use of `[]Byte{0}` value for each `address` key reflects that this module only accesses the store to get or iterate over keys, and does not require storing an value. The storage of the actual account state is done by the cosmos-sdk `x/auth` `AccountKeeper`.

View File

@ -1,6 +1,6 @@
# Begin Block
At each `BeginBlock`, all validator vesting accounts are iterated over to update the status of the current vesting period. Note that the address of each account is retreived by iterating over the keys in the `validator-vesting` store, while the account objects are stored and accessed using the `auth` module's `AccountKeeper`. For each account, the block count is incremented, the missed sign count is incremented if the validator did not sign the block or was not found in the validator set. By comparing the blocktime of the current `BeginBlock`, with the value of `previousBlockTime` stored in the `validator-vesting` store, it is determined if the end of the current period has been reached. If the current period has ended, the `VestingPeriodProgress` field is updated to reflect if the coins for the ending period successfully vested or not. After updates are made regarding the status of the current vesting period, any outstanding debt on the account is attempted to be collected. If there is enough `SpendableBalance` on the account to cover the debt, coins are sent to the `ReturnAdress` or burned. If there is not enough `SpendableBalance` to cover the debt, all delegations of the account are `Unbonded`. Once those unbonding events reach maturity, the coins freed from the undonding will be used to cover the debt. Finally, the time of the previous block is stored in the validator vesting account keeper, which is used to determine when a period has ended.
At each `BeginBlock`, all validator vesting accounts are iterated over to update the status of the current vesting period. Note that the address of each account is retrieved by iterating over the keys in the `validator-vesting` store, while the account objects are stored and accessed using the `auth` module's `AccountKeeper`. For each account, the block count is incremented, the missed sign count is incremented if the validator did not sign the block or was not found in the validator set. By comparing the blocktime of the current `BeginBlock`, with the value of `previousBlockTime` stored in the `validator-vesting` store, it is determined if the end of the current period has been reached. If the current period has ended, the `VestingPeriodProgress` field is updated to reflect if the coins for the ending period successfully vested or not. After updates are made regarding the status of the current vesting period, any outstanding debt on the account is attempted to be collected. If there is enough `SpendableBalance` on the account to cover the debt, coins are sent to the `ReturnAdress` or burned. If there is not enough `SpendableBalance` to cover the debt, all delegations of the account are `Unbonded`. Once those unbonding events reach maturity, the coins freed from the unbonding will be used to cover the debt. Finally, the time of the previous block is stored in the validator vesting account keeper, which is used to determine when a period has ended.
```go
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
@ -42,4 +42,3 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
k.SetPreviousBlockTime(ctx, currentBlockTime)
}
```

View File

@ -0,0 +1,14 @@
# `validator-vesting`
<!-- TOC -->
1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[BeginBlock](03_begin_block.md)**
## Abstract
`x/validator-vesting` is an implementation of a Cosmos SDK sub-module that defines a new type of vesting account, `ValidatorVestingAccount`. This account implements the Cosmos SDK `VestingAccount` interface and extends it to add conditions to the vesting balance. In this implementation, in order to receive the vesting balance, the validator vesting account specifies a validator that must sign a given `SigningThreshold` of blocks during each vesting period in order for coins to successfully vest.
## Dependencies
This module uses the Cosmos SDK `x/auth` module and `x/auth/vesting` sub-module definitions of `Account` and `VestingAccount`. The actual state of a `ValidatorVestingAccount` is stored in the `x/auth` keeper, while this module merely stores a list of addresses that correspond to validator vesting accounts for fast iteration.