x/rewards is a Cosmos SDK module that manages restakig rewards.
Contents
Concepts
F1 Distribution
Rewards Plans
Rewards plans are created by service admins to reward restakers who have
restaked their assets to the service.
A rewards plan consists of the following parameters:
Description: A description of the rewards plan
Amount per day: The amount of rewards to be distributed per day
Rewards are distributed per block, based on the previous block's duration
Start time: The time when the rewards plan starts
End time: The time when the rewards plan ends
Pools distribution: The distribution method of rewards toward pools
Operators distribution: The distribution method of rewards toward operators
Users distribution: The distribution method of rewards toward delegators who
have delegated to the service directly
Each distribution method object also has weight field which is used to
determine the distribution ratio among the pools, operators and users.
If all distributions' weights are set to 0 the rewards are distributed based
on restaked assets' USD value.
If only some distributions' weights are set to 0 the rewards aren't
distributed to them only.
There are three types of distribution methods, while users distribution can only
be basic:
Basic distribution
Weighted distribution
Egalitarian distribution
Basic Distribution
Rewards are distributed to all pools(or operators) proportionally based on the
USD value of restaked assets.
Example:
Pool A value: $30M
Pool B value: $50M
Total value: $80M
Pool A's rewards: $30M / $80M * This block's rewards
Pool B's rewards: $50M / $80M * This block's rewards
Weighted Distribution
Rewards are distributed to the predefined list of pools(or operators) with the
specified weights.
Example:
Operator A weight: 4
Operator B weight: 7
Operator C is not in the list
Total weight: 11
Operator A's rewards: 4 / 11 * This block's rewards
Operator B's rewards: 7 / 11 * This block's rewards
Operator C's rewards: 0
Egalitarian Distribution
Rewards are distributed equally among all pools(or operators).
Example:
Pool A's rewards: This block's rewards / Total number of pools
Pool B's rewards: This block's rewards / Total number of pools
...
Rewards Allocation
Rewards are allocated every block, based on the previous block's duration.
Here's the formula to calculate rewards per block:
Rewards are allocated to a pool's delegators when:
The service is active
The rewards plan's pool has enough balances to distribute rewards
The service is secured by the pool. By default, a service is secured by no
pools
There's at least one delegator to the pool who trusts the service with the
pool
Rewards are allocated to an operator and its delegators when:
The operator is active
The service is active
The operator has joined the service which implies that the operator is
allowed to validate the service
State
Params
The module parameters are stored under the 0x01 key:
Params: 0x01 -> ProtocolBuffer(Params)
// Params defines the parameters for the module.
message Params {
// RewardsPlanCreationFee represents the fee that an account must pay in
// order to create a rewards plan.
// The fee is drawn from the MsgCreateRewardsPlan sender's account and
// transferred to the community pool.
repeated cosmos.base.v1beta1.Coin rewards_plan_creation_fee = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}
NextRewardsPlanID
NextRewardsPlanID stores the ID of the next rewards plan to be created.
NextRewardsPlanID: 0xa1 -> uint64
RewardsPlans
All the rewards plan are stored under the 0xa2 key prefix.
// RewardsPlan represents a rewards allocation plan.
message RewardsPlan {
option (gogoproto.equal) = true;
// ID is the unique identifier of the plan.
uint64 id = 1 [(gogoproto.customname) = "ID"];
// Description is the description of the plan.
string description = 2;
// ServiceID is the service ID which the plan is related to.
uint32 service_id = 3 [(gogoproto.customname) = "ServiceID"];
// AmountPerDay is the amount of rewards to be distributed, per day.
// The rewards amount for every block will be calculated based on this.
cosmos.base.v1beta1.Coin amount_per_day = 11 [(gogoproto.nullable) = false];
// StartTime is the starting time of the plan.
google.protobuf.Timestamp start_time = 5 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// EndTime is the ending time of the plan.
google.protobuf.Timestamp end_time = 6 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// RewardsPool is the address where rewards to be distributed are stored.
// If the rewards pool doesn't have enough funds to be distributed, then
// the rewards allocation for this plan will be skipped.
string rewards_pool = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// PoolsDistribution is the rewards distribution parameters for pools.
Distribution pools_distribution = 8 [(gogoproto.nullable) = false];
// OperatorsDistribution is the rewards distribution parameters for operators.
Distribution operators_distribution = 9 [(gogoproto.nullable) = false];
// UsersDistribution is the rewards distribution parameters for users.
UsersDistribution users_distribution = 10 [(gogoproto.nullable) = false];
reserved 4; // old amount_per_day
}
LastRewardsAllocationTime
LastRewardsAllocationTime stores the timestamp of the last rewards allocation.
LastRewardsAllocationTime: 0xa3 -> Timestamp
DelegatorWithdrawAddrs
DelegatorWithdrawAddrs stores the withdraw address of each delegator.
// DelegatorStartingInfo represents the starting info for a delegator reward
// period. It tracks the previous delegation target period, the delegation's
// amount of staking token, and the creation height (to check later on if any
// slashes have occurred). NOTE: Even though validators are slashed to whole
// staking tokens, the delegators within the validator may be left with less
// than a full token, thus sdk.Dec is used.
message DelegatorStartingInfo {
uint64 previous_period = 1 [(gogoproto.moretags) = "yaml:\"previous_period\""];
repeated cosmos.base.v1beta1.DecCoin stakes = 2 [
(gogoproto.moretags) = "yaml:\"stakes\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
uint64 height = 3 [
(gogoproto.moretags) = "yaml:\"creation_height\"",
(gogoproto.jsontag) = "creation_height"
];
}
HistoricalRewards
HistoricalRewards stores the historical rewards for each delegation target.
It keeps track of cumulative rewards ratios in order to calculate rewards lazily
using the F1 distribution logic.
There are three HistoricalRewards for each delegation type:
PoolHistoricalRewards: 0xb2 | PoolID | Period -> ProtocolBuffer(HistoricalRewards)
OperatorHistoricalRewards: 0xc3 | OperatorID | Period -> ProtocolBuffer(HistoricalRewards)
ServiceHistoricalRewards: 0xd2 | ServiceID | Period -> ProtocolBuffer(HistoricalRewards)
// HistoricalRewards represents historical rewards for a delegation target.
// Height is implicit within the store key.
// Cumulative reward ratio is the sum from the zeroeth period
// until this period of rewards / tokens, per the spec.
// The reference count indicates the number of objects
// which might need to reference this historical entry at any point.
// ReferenceCount =
// number of outstanding delegations which ended the associated period (and
// might need to read that record)
// + number of slashes which ended the associated period (and might need to
// read that record)
// + one per validator for the zeroeth period, set on initialization
message HistoricalRewards {
repeated ServicePool cumulative_reward_ratios = 1 [
(gogoproto.moretags) = "yaml:\"cumulative_reward_ratios\"",
(gogoproto.castrepeated) = "ServicePools",
(gogoproto.nullable) = false
];
uint32 reference_count = 2 [(gogoproto.moretags) = "yaml:\"reference_count\""];
}
CurrentRewards
CurrentRewards stores the current rewards and the current period of each
delegation target.
There are three CurrentRewards for each delegation type:
// CurrentRewards represents current rewards and current
// period for a delegation target kept as a running counter and incremented
// each block as long as the delegation target's tokens remain constant.
message CurrentRewards {
repeated ServicePool rewards = 1 [
(gogoproto.moretags) = "yaml:\"rewards\"",
(gogoproto.castrepeated) = "ServicePools",
(gogoproto.nullable) = false
];
uint64 period = 2;
}
OutstandingRewards
OutstandingRewards stores the outstanding(un-withdrawn) rewards for each
delegation target.
There are three OutstandingRewards for each delegation type:
PoolServiceTotalDelegatorShares stores the total trusted delegator shares of
each pool-service pair.
The total trusted delegator shares means the sum of delegation shares of all
delegators who has delegated to the pool and trusts the service with it.
// AccumulatedCommission represents accumulated commission
// for a delegation target kept as a running counter, can be withdrawn at any
// time.
message AccumulatedCommission {
repeated DecPool commissions = 1 [
(gogoproto.castrepeated) = "DecPools",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
}
Messages
MsgCreateRewardsPlan
The MsgCreateRewardsPlan can be sent by service admins to create a new rewards
plan.
// MsgCreateRewardsPlan defines the message structure for the
// CreateRewardsPlan gRPC service method. It allows an account to create a
// new rewards plan. It requires a sender address as well as the details of
// the plan to be created.
message MsgCreateRewardsPlan {
option (cosmos.msg.v1.signer) = "sender";
option (amino.name) = "milkyway/MsgCreateRewardsPlan";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// Sender is the address of the user creating the rewards plan
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string description = 2;
uint32 service_id = 3 [(gogoproto.customname) = "ServiceID"];
// Amount is the amount of rewards to be distributed.
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
// StartTime is the starting time of the plan.
google.protobuf.Timestamp start_time = 5 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// EndTime is the ending time of the plan.
google.protobuf.Timestamp end_time = 6 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// PoolsDistribution is the rewards distribution parameters for pools.
Distribution pools_distribution = 7 [(gogoproto.nullable) = false];
// OperatorsDistribution is the rewards distribution parameters for operators.
Distribution operators_distribution = 8 [(gogoproto.nullable) = false];
// UsersDistribution is the rewards distribution parameters for users who
// delegated directly to the service.
UsersDistribution users_distribution = 9 [(gogoproto.nullable) = false];
// FeeAmount represents the fees that are going to be paid to create the
// rewards plan. These should always be greater or equals of any of the coins
// specified inside the RewardsPlanCreationFee field of the modules params.
// If no fees are specified inside the module parameters, this field can be
// omitted.
repeated cosmos.base.v1beta1.Coin fee_amount = 10 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}
The message will fail under the following conditions:
The service doesn't exist
The sender is not the service admin
The supplied rewards plan creation fee is insufficient
The plan is invalid
MsgEditRewardsPlan
The MsgEditRewardsPlan can be sent by service admins to edit an existing
rewards plan.
// MsgEditRewardsPlan defines the message structure for the
// EditRewardsPlan gRPC service method. It allows an account to edit a
// previously created rewards plan.
message MsgEditRewardsPlan {
option (cosmos.msg.v1.signer) = "sender";
option (amino.name) = "milkyway/MsgEditRewardsPlan";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
// Sender is the address of the user editing the rewards plan.
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// ID is the ID of the rewards plan to be edited.
uint64 id = 2 [(gogoproto.customname) = "ID"];
string description = 3;
// Amount is the amount of rewards to be distributed.
cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false];
// StartTime is the starting time of the plan.
google.protobuf.Timestamp start_time = 5 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// EndTime is the ending time of the plan.
google.protobuf.Timestamp end_time = 6 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false
];
// PoolsDistribution is the rewards distribution parameters for pools.
Distribution pools_distribution = 7 [(gogoproto.nullable) = false];
// OperatorsDistribution is the rewards distribution parameters for operators.
Distribution operators_distribution = 8 [(gogoproto.nullable) = false];
// UsersDistribution is the rewards distribution parameters for users who
// delegated directly to the service.
UsersDistribution users_distribution = 9 [(gogoproto.nullable) = false];
}
The message will fail under the following conditions:
The service doesn't exist
The sender is not the service admin
The plan is invalid
MsgSetWithdrawAddress
The MsgSetWithdrawAddress can be sent by anyone to set the withdraw address.
By default, the withdraw address is the delegator address.
// MsgSetWithdrawAddress sets the withdraw address for a delegator(or an
// operator when withdrawing commission).
message MsgSetWithdrawAddress {
option (cosmos.msg.v1.signer) = "sender";
option (amino.name) = "milkyway/MsgSetWithdrawAddress";
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;
string sender = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string withdraw_address = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}
The message will fail under the following conditions:
The withdraw address is blocked from receiving funds by the bank module
MsgWithdrawDelegatorReward
The MsgWithdrawDelegatorReward can be sent by anyone to withdraw the rewards.
The message will fail under the following conditions:
The operator doesn't exist
The sender is not the operator admin
There's no accumulated commission to withdraw
Events
BeginBlocker
Type
Attribute Key
Attribute Value
commission
operator_id
{operatorID}
commission
pool
{denomOfRestakedAssetBeingRewarded}
commission
amount
{commissionAmount}
rewards
delegation_type
{delegationType}
rewards
delegation_target_id
{delegationTargetID}
rewards
pool
{restakedAssetBeingRewarded}
rewards
amount
{rewardAmount}
Handlers
MsgCreateRewardsPlan
Type
Attribute Key
Attribute Value
create_rewards_plan
rewards_plan_id
{rewardsPlanID}
create_rewards_plan
service_id
{serviceID}
create_rewards_plan
sender
{senderAddress}
MsgEditRewardsPlan
Type
Attribute Key
Attribute Value
edit_rewards_plan
rewards_plan_id
{rewardsPlanID}
edit_rewards_plan
service_id
{serviceID}
edit_rewards_plan
sender
{senderAddress}
MsgSetWithdrawAddress
Type
Attribute Key
Attribute Value
set_withdraw_address
sender
{senderAddress}
set_withdraw_address
withdraw_address
{withdrawAddress}
MsgWithdrawDelegatorReward
Type
Attribute Key
Attribute Value
withdraw_rewards
delegation_type
{delegationType}
withdraw_rewards
delegation_target_id
{delegationTargetID}
withdraw_rewards
delegator
{delegatorAddress}
withdraw_rewards
amount
{withdrawnRewardAmount}
withdraw_rewards
amount_per_pool
{withdrawnRewardAmountPerRestakedAsset}
MsgWithdrawOperatorCommission
Type
Attribute Key
Attribute Value
withdraw_commission
operator_id
{operatorID}
withdraw_commission
amount
{withdrawnCommissionAmount}
withdraw_commission
amount_per_pool
{withdrawnCommissionAmountPerRestakedAsset}
Parameters
The rewards module contains the following parameters:
Key
Type
Example
rewards_plan_creation_fee
array (coins)
[{"denom":"umilk","amount":"10000000"}]
Note that the rewards_plan_creation_fee represents the OR(not AND) conditions
of the fee which means that a plan creator can pay the fee with any coin
denomination and amount specified inside the rewards_plan_creation_fee.
// Params defines the parameters for the module.
message Params {
// RewardsPlanCreationFee represents the fee that an account must pay in
// order to create a rewards plan.
// The fee is drawn from the MsgCreateRewardsPlan sender's account and
// transferred to the community pool.
repeated cosmos.base.v1beta1.Coin rewards_plan_creation_fee = 1 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins",
(gogoproto.nullable) = false
];
}
x/rewards uses the F1 distribution algorithm which the Cosmos SDK's
x/distribution module also uses.
For more information on the F1 distribution algorithm, refer to the
and the .