Skip to content

Commit

Permalink
feat: incentive module proto (#1630)
Browse files Browse the repository at this point in the history
* proto files from PR 1514

* proto gen

* additional basic queries

* remove MsgGovCreateAndSponsorProgram

* Update proto/umee/incentive/v1/query.proto

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* Update proto/umee/incentive/v1/query.proto

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* Update proto/umee/incentive/v1/query.proto

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* Update proto/umee/incentive/v1/tx.proto

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* Update proto/umee/incentive/v1/tx.proto

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* proto gen after suggestions

* RewardTracker

* comment++

* Apply suggestions from code review

Co-authored-by: Robert Zaremba <robert@zaremba.ch>

* added parameter community_fund_address and proto comments explaining gov flow

* ++

Co-authored-by: Robert Zaremba <robert@zaremba.ch>
  • Loading branch information
toteki and robert-zaremba authored Dec 7, 2022
1 parent 8f78f50 commit 3ceefd6
Show file tree
Hide file tree
Showing 10 changed files with 11,546 additions and 5 deletions.
10 changes: 5 additions & 5 deletions docs/design_docs/009-liquidity-mining.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,20 +194,20 @@ The following approach is proposed:
- Additionally, each nonzero `TotalLocked(denom,tier) = Amount` is kept up to date in state. These track the sum of uTokens locked across all addresses.
- For each `(denom,tier)` that has ever been incentivized, any nonzero `RewardAccumulator(denom,tier)` is stored in state. This `sdk.DecCoins` represents the total rewards a single locked `uToken` would have accumulated if locked into a given tier at genesis.
- For each user, any nonzero `PendingReward` is stored as `sdk.DecCoins`.
- For each nonzero `Locked(address,denom,tier)`, a value `RewardBasis(address,denom,tier)` is stored which tracks the value of `RewardAccumulator(denom,tier)` at the most recent time rewards were added to `PendingReward` by the given (address,denom,tier).
- For each nonzero `Locked(address,denom,tier)`, a value `RewardTracker(address,denom,tier)` is stored which tracks the value of `RewardAccumulator(denom,tier)` at the most recent time rewards were added to `PendingReward` by the given (address,denom,tier).
- At any given `EndBlock`, each active incentive program performs some computations:
- Calculates the total `RewardDenom` rewards that will be given by the program in the current block `X = program.TotalRewards * (secondsElapsed / program.Duration)`
- Each lock tier receives a `weightedValue(program,denom,tier) = TotalLocked(denom,tier) * tierWeight(program,tier)`.
- The amount `X` for each program is then split between the tiers by `weightedValue` into three `X(tier)` values (X1,X2,X3)
- For each tier, `RewardAccumulator(denom,tier)` is increased by `X(tier) / TotalLocked(denom,tier)`.
- When a user's `Locked(address,denom,tier) = Amount` is updated for any reason, `RewardBasis(address,denom,tier)` is set to the current `RewardAccumulator(denom,tier)` and `PendingRewards(addr)` is increased by the difference of the two, times the user's locked amount. (This uses the locked amount _before_ it is updated.)
- When a user's `Locked(address,denom,tier) = Amount` is updated for any reason, `RewardTracker(address,denom,tier)` is set to the current `RewardAccumulator(denom,tier)` and `PendingRewards(addr)` is increased by the difference of the two, times the user's locked amount. (This uses the locked amount _before_ it is updated.)
- When a user claims rewards, they receive `PendingRewards(addr)` and set it to zero (which clears it from state).

The algorithm above uses an approach similar to [F1 Fee Distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/) in that it uses an exchange rate (in our case, HistoricalReward) to track each denom's hypothetical rewards since genesis, and determines actual reward amounts by recording the previous exchange rate (RewardBasis) at which each user made their previous claim.
The algorithm above uses an approach similar to [F1 Fee Distribution](https://drops.dagstuhl.de/opus/volltexte/2020/11974/) in that it uses an exchange rate (in our case, HistoricalReward) to track each denom's hypothetical rewards since genesis, and determines actual reward amounts by recording the previous exchange rate (RewardTracker) at which each user made their previous claim.

The F1 algorithm only works if users are forced to claim rewards every time their locked amount increases or decreases (thus, locked amount is known to have stayed constant between any two claims).
Our implementation is less complex than F1 because there is no equivalent to slashing in `x/incentive`, and we move rewards to the `PendingRewards` instead of claiming them directly.
The same mathematical effect is achieved, where `Locked(addr,denom,tier)` remains constant in the time `RewardAccumulator(denom,tier)` has increased to its current value from `RewardBasis(denom,tier)`, allowing reward calculation on demand using only those three values.
The same mathematical effect is achieved, where `Locked(addr,denom,tier)` remains constant in the time `RewardAccumulator(denom,tier)` has increased to its current value from `RewardTracker(denom,tier)`, allowing reward calculation on demand using only those three values.

### Claiming Rewards

Expand All @@ -219,7 +219,7 @@ type MsgClaim struct {
}
```

This message type gathers `PendingRewards` by updating `RewardBasis` for each nonzero `Locked(addr,denom,tier)` associated with the user's address, then claims all pending rewards.
This message type gathers `PendingRewards` by updating `RewardTracker` for each nonzero `Locked(addr,denom,tier)` associated with the user's address, then claims all pending rewards.

### Funding Programs

Expand Down
80 changes: 80 additions & 0 deletions proto/umee/incentive/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
syntax = "proto3";
package umeenetwork.umee.incentive.v1;

option go_package = "github.com/umee-network/umee/v3/x/incentive";

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";
import "umee/incentive/v1/incentive.proto";

// GenesisState defines the x/incentive module's genesis state.
message GenesisState {
Params params = 1 [(gogoproto.nullable) = false];
repeated IncentiveProgram completed_programs = 2 [(gogoproto.nullable) = false];
repeated IncentiveProgram ongoing_programs = 3 [(gogoproto.nullable) = false];
repeated IncentiveProgram upcoming_programs = 4 [(gogoproto.nullable) = false];
uint32 next_program_id = 5;
uint64 last_rewards_time = 6;
repeated cosmos.base.v1beta1.Coin total_bonded = 7 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
repeated Bond bonds = 8 [(gogoproto.nullable) = false];
repeated PendingReward pending_rewards = 9 [(gogoproto.nullable) = false];
repeated RewardTracker reward_bases = 10 [(gogoproto.nullable) = false];
repeated RewardAccumulator reward_accumulators = 11 [(gogoproto.nullable) = false];
repeated Unbonding unbondings = 12 [(gogoproto.nullable) = false];
}

// Bond tracks the amount of coins of one uToken denomination bonded to a
// given reward tier by a single account.
message Bond {
string account = 1;
uint32 tier = 2;
cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false];
}

// PendingReward tracks the amount of rewards that a given account has calculated
// but not yet claimed.
message PendingReward {
string account = 1;
repeated cosmos.base.v1beta1.Coin pending_reward = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// RewardTracker tracks the value of a given tier and lock denom's RewardAccumulator
// at the last time a specific account calculated pending rewards for it. When calculating
// available rewards, this value is used to determine the difference between the current
// RewardAccumulator for a tier and the last value at which the user updated bonds or claimed
// tokens. Their pending rewards increase by only the rewards accrued in that time period.
message RewardTracker {
string account = 1;
uint32 tier = 2;
string denom = 3;
repeated cosmos.base.v1beta1.DecCoin reward_tracker = 4 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"
];
}

// RewardAccumulator is a global reward tracking struct that indicates the amount
// of rewards that a single unit of denom would have acucmulated if it was bonded
// at a given tier since genesis.
message RewardAccumulator {
uint32 tier = 1;
string denom = 2;
repeated cosmos.base.v1beta1.DecCoin reward_tracker = 3 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"
];
}

// Unbonding is a structure that tracks an in-progress token unbonding.
message Unbonding {
string account = 1;
uint32 tier = 2;
uint64 end = 3;
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
}
93 changes: 93 additions & 0 deletions proto/umee/incentive/v1/incentive.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
syntax = "proto3";
package umeenetwork.umee.incentive.v1;

import "cosmos/base/v1beta1/coin.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/umee-network/umee/v3/x/incentive";

// Params defines the parameters for the incentive module.
message Params {
option (gogoproto.equal) = true;

// max_unbondings defines the maximum amount of concurrent unbondings an address can have.
uint32 max_unbondings = 1;

// unbonding_duration_long defines the unbonding duration (in seconds) of the long tier.
uint64 unbonding_duration_long = 2;

// unbonding_duration_middle defines the unbonding duration (in seconds) of the middle tier.
uint64 unbonding_duration_middle = 3;

// unbonding_duration_short defines the unbonding duration (in seconds) of the short tier.
uint64 unbonding_duration_short = 4;

// tier_weight_short defines the proportion of rewards which assets bonded
// in the short unbonding duration receive compared to what the same amount
// would receive bonded to the long tier.
// valid values: [0;1]
string tier_weight_short = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// tier_weight_middle defines the proportion of rewards which assets bonded
// in the middle unbonding duration receive compared to what the same amount
// would receive bonded to the long tier.
// valid values: [0;1]
string tier_weight_middle = 6 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// community_fund_address is the address from which all incentive programs
// proposed with "from_community_fund = true" will receive their funding.
// Since funds are withdrawn automatically when an incentive program passes
// governance, this account should always contain sufficient balance to
// cover incentive programs which are being voted upon.
string community_fund_address = 7;
}

// IncentiveProgram defines a liquidity mining incentive program on a single
// locked uToken denom that will run for a set amount of time.
message IncentiveProgram {
option (gogoproto.equal) = true;

// ID uniquely identifies the incentive program after it has been created.
// It is zero when the program is being proposed by governance.
uint32 id = 1;

// start_time is the unix time (in seconds) at which the incentives begin.
uint64 start_time = 2;

// duration is the length of the incentive program in seconds.
uint64 duration = 3;

// denom is the incentivized uToken collateral.
string denom = 4;

// total_rewards are total amount of rewards which can be distributed to
// suppliers by this program. This is set to its final value when the program
// is proposed by governance.
cosmos.base.v1beta1.Coin total_rewards = 5 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// funded_rewards are total amount of rewards which have been funded by a
// sponsor to this program. This is zero until the program is both passed
// by governance and funded.
cosmos.base.v1beta1.Coin funded_rewards = 6 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// remaining_rewards are total amount of this program's funded rewards
// which have not yet been allocated to suppliers. This is zero until the
// program is both passed by governance and funded, then begins decreasing
// to zero as the program runs to completion.
cosmos.base.v1beta1.Coin remaining_rewards = 7 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
179 changes: 179 additions & 0 deletions proto/umee/incentive/v1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
syntax = "proto3";
package umeenetwork.umee.incentive.v1;

import "cosmos/base/query/v1beta1/pagination.proto";
import "google/api/annotations.proto";
import "gogoproto/gogo.proto";
import "umee/incentive/v1/incentive.proto";
import "umee/incentive/v1/genesis.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/umee-network/umee/v3/x/incentive";

// Query defines the gRPC querier service.
service Query {
// Params queries the parameters of the x/incentive module.
rpc Params(QueryParams) returns (QueryParamsResponse) {
option (google.api.http).get = "/umee/incentive/v1/params";
}

// PendingRewards queries unclaimed incentive rewards associated with an account.
rpc PendingRewards(QueryPendingRewards)
returns (QueryPendingRewardsResponse) {
option (google.api.http).get = "/umee/incentive/v1/pending_rewards/{address}";
}

// Bonded queries all bonded collateral uTokens associated with an account.
rpc Bonded(QueryBonded)
returns (QueryBondedResponse) {
option (google.api.http).get = "/umee/incentive/v1/bonded/{address}";
}

// Unbondings queries all current uToken unbondings associated with an account.
rpc Unbondings(QueryUnbondings)
returns (QueryUnbondingsResponse) {
option (google.api.http).get = "/umee/incentive/v1/unbondings/{address}";
}

// TotalBonded queries the sum of all bonded collateral uTokens, separated by tier.
rpc TotalBonded(QueryTotalBonded)
returns (QueryTotalBondedResponse) {
option (google.api.http).get = "/umee/incentive/v1/total_bonded";
}

// CompletedIncentivePrograms queries for all incentives programs that have been passed
// by governance, and either run to completion or expired immediately due to not being funded.
rpc CompletedIncentivePrograms(QueryCompletedIncentivePrograms)
returns (QueryCompletedIncentiveProgramsResponse) {
option (google.api.http).get = "/umee/incentive/v1/incentive_programs/completed";
}

// OngoingIncentivePrograms queries for all incentives programs that have been passed
// by governance, funded, and started but not yet completed.
rpc OngoingIncentivePrograms(QueryOngoingIncentivePrograms)
returns (QueryOngoingIncentiveProgramsResponse) {
option (google.api.http).get = "/umee/incentive/v1/incentive_programs/ongoing";
}

// UpcomingIncentivePrograms queries for all incentives programs that have been passed
// by governance, but not yet started. They may or may not have been funded.
rpc UpcomingIncentivePrograms(QueryUpcomingIncentivePrograms)
returns (QueryUpcomingIncentiveProgramsResponse) {
option (google.api.http).get = "/umee/incentive/v1/incentive_programs/upcoming";
}

// IncentiveProgram queries a single incentive program by ID.
rpc IncentiveProgram(QueryIncentiveProgram)
returns (QueryIncentiveProgramResponse) {
option (google.api.http).get = "/umee/incentive/v1/incentive_program/{id}";
}
}

// TotalBond tracks the amount of coins of one uToken denomination bonded to a
// given reward tier without specifying an account.
message TotalBond {
uint32 tier = 1;
cosmos.base.v1beta1.Coin amount = 2 [(gogoproto.nullable) = false];
}

// QueryParams defines the request structure for the Params gRPC service
// handler.
message QueryParams {}

// QueryParamsResponse defines the response structure for the Params gRPC
// service handler.
message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}

// QueryPendingRewards defines the request structure for the PendingRewards gRPC service handler.
message QueryPendingRewards {
string address = 1;
}

// QueryPendingRewardsResponse defines the response structure for the PendingRewards gRPC service handler.
message QueryPendingRewardsResponse {
repeated cosmos.base.v1beta1.Coin rewards = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// QueryBonded defines the request structure for the Bonded gRPC service handler.
message QueryBonded {
string address = 1;
}

// QueryBondedResponse defines the response structure for the Bonded gRPC service handler.
message QueryBondedResponse {
repeated TotalBond bonded = 1 [(gogoproto.nullable) = false];
}

// QueryUnbondings defines the request structure for the Unbondings gRPC service handler.
message QueryUnbondings {
string address = 1;
}

// QueryUnbondingsResponse defines the response structure for the Unbondings gRPC service handler.
message QueryUnbondingsResponse {
repeated Unbonding unbondings = 1 [(gogoproto.nullable) = false];
}

// QueryTotalBonded defines the request structure for the TotalBonded gRPC service handler.
message QueryTotalBonded {
}

// QueryTotalBondedResponse defines the response structure for the TotalBonded gRPC service handler.
message QueryTotalBondedResponse {
repeated TotalBond bonded = 1 [(gogoproto.nullable) = false];
}

// QueryUpcomingIncentivePrograms defines the request structure for the
// OngoingIncentivePrograms and UpcomingIncentivePrograms gRPC service handlers.
message QueryUpcomingIncentivePrograms {
}

// QueryUpcomingIncentiveProgramsResponse defines the response structure for the
// OngoingIncentivePrograms and UpcomingIncentivePrograms gRPC service handlers.
message QueryUpcomingIncentiveProgramsResponse {
repeated IncentiveProgram programs = 1 [(gogoproto.nullable) = false];
}

// QueryOngoingIncentivePrograms defines the request structure for the
// OngoingIncentivePrograms and UpcomingIncentivePrograms gRPC service handlers.
message QueryOngoingIncentivePrograms {
}

// QueryOngoingIncentiveProgramsResponse defines the response structure for the
// OngoingIncentivePrograms and UpcomingIncentivePrograms gRPC service handlers.
message QueryOngoingIncentiveProgramsResponse {
repeated IncentiveProgram programs = 1 [(gogoproto.nullable) = false];
}

// QueryCompletedIncentivePrograms defines the request structure for the
// CompletedIncentivePrograms gRPC service handler.
message QueryCompletedIncentivePrograms {
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 1;
}

// QueryCompletedIncentiveProgramsResponse defines the response structure for the
// CompletedIncentivePrograms gRPC service handler.
message QueryCompletedIncentiveProgramsResponse {
repeated IncentiveProgram programs = 1 [(gogoproto.nullable) = false];
// pagination defines an optional pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryIncentiveProgram defines the request structure for the IncentiveProgram
// gRPC service handler.
message QueryIncentiveProgram {
// ID specifies which program to query for
uint32 id = 1;
}

// QueryIncentivePrograResponse defines the response structure for the
// IncentiveProgram gRPC service handler.
message QueryIncentiveProgramResponse {
IncentiveProgram program = 1 [(gogoproto.nullable) = false];
}
Loading

0 comments on commit 3ceefd6

Please sign in to comment.