Merge PR #2437: Distr-PR-4 Distribution Types
This commit is contained in:
parent
2e040dd971
commit
9c9113fa3b
|
@ -136,6 +136,7 @@ IMPROVEMENTS
|
|||
* [genesis] \#2229 Ensure that there are no duplicate accounts or validators in the genesis state.
|
||||
* Add SDK validation to `config.toml` (namely disabling `create_empty_blocks`) \#1571
|
||||
* \#1941(https://github.com/cosmos/cosmos-sdk/issues/1941) Version is now inferred via `git describe --tags`.
|
||||
* [x/distribution] \#1671 add distribution types and tests
|
||||
|
||||
* SDK
|
||||
* [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present.
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// Register concrete types on codec codec
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgWithdrawDelegatorRewardsAll{}, "cosmos-sdk/MsgWithdrawDelegationRewardsAll", nil)
|
||||
cdc.RegisterConcrete(MsgWithdrawDelegatorReward{}, "cosmos-sdk/MsgWithdrawDelegationReward", nil)
|
||||
cdc.RegisterConcrete(MsgWithdrawValidatorRewardsAll{}, "cosmos-sdk/MsgWithdrawValidatorRewardsAll", nil)
|
||||
cdc.RegisterConcrete(MsgSetWithdrawAddress{}, "cosmos-sdk/MsgModifyWithdrawAddress", nil)
|
||||
}
|
||||
|
||||
// generic sealed codec to be used throughout module
|
||||
var MsgCdc *codec.Codec
|
||||
|
||||
func init() {
|
||||
cdc := codec.New()
|
||||
RegisterCodec(cdc)
|
||||
codec.RegisterCrypto(cdc)
|
||||
MsgCdc = cdc.Seal()
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Coins which can have additional decimal points
|
||||
type DecCoin struct {
|
||||
Denom string `json:"denom"`
|
||||
Amount sdk.Dec `json:"amount"`
|
||||
}
|
||||
|
||||
func NewDecCoin(denom string, amount int64) DecCoin {
|
||||
return DecCoin{
|
||||
Denom: denom,
|
||||
Amount: sdk.NewDec(amount),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDecCoinFromCoin(coin sdk.Coin) DecCoin {
|
||||
return DecCoin{
|
||||
Denom: coin.Denom,
|
||||
Amount: sdk.NewDecFromInt(coin.Amount),
|
||||
}
|
||||
}
|
||||
|
||||
// Adds amounts of two coins with same denom
|
||||
func (coin DecCoin) Plus(coinB DecCoin) DecCoin {
|
||||
if coin.Denom != coinB.Denom {
|
||||
panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom))
|
||||
}
|
||||
return DecCoin{coin.Denom, coin.Amount.Add(coinB.Amount)}
|
||||
}
|
||||
|
||||
// Subtracts amounts of two coins with same denom
|
||||
func (coin DecCoin) Minus(coinB DecCoin) DecCoin {
|
||||
if coin.Denom != coinB.Denom {
|
||||
panic(fmt.Sprintf("coin denom different: %v %v\n", coin.Denom, coinB.Denom))
|
||||
}
|
||||
return DecCoin{coin.Denom, coin.Amount.Sub(coinB.Amount)}
|
||||
}
|
||||
|
||||
// return the decimal coins with trunctated decimals
|
||||
func (coin DecCoin) TruncateDecimal() sdk.Coin {
|
||||
return sdk.NewCoin(coin.Denom, coin.Amount.TruncateInt())
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
// coins with decimal
|
||||
type DecCoins []DecCoin
|
||||
|
||||
func NewDecCoins(coins sdk.Coins) DecCoins {
|
||||
dcs := make(DecCoins, len(coins))
|
||||
for i, coin := range coins {
|
||||
dcs[i] = NewDecCoinFromCoin(coin)
|
||||
}
|
||||
return dcs
|
||||
}
|
||||
|
||||
// return the coins with trunctated decimals
|
||||
func (coins DecCoins) TruncateDecimal() sdk.Coins {
|
||||
out := make(sdk.Coins, len(coins))
|
||||
for i, coin := range coins {
|
||||
out[i] = coin.TruncateDecimal()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Plus combines two sets of coins
|
||||
// CONTRACT: Plus will never return Coins where one Coin has a 0 amount.
|
||||
func (coins DecCoins) Plus(coinsB DecCoins) DecCoins {
|
||||
sum := ([]DecCoin)(nil)
|
||||
indexA, indexB := 0, 0
|
||||
lenA, lenB := len(coins), len(coinsB)
|
||||
for {
|
||||
if indexA == lenA {
|
||||
if indexB == lenB {
|
||||
return sum
|
||||
}
|
||||
return append(sum, coinsB[indexB:]...)
|
||||
} else if indexB == lenB {
|
||||
return append(sum, coins[indexA:]...)
|
||||
}
|
||||
coinA, coinB := coins[indexA], coinsB[indexB]
|
||||
switch strings.Compare(coinA.Denom, coinB.Denom) {
|
||||
case -1:
|
||||
sum = append(sum, coinA)
|
||||
indexA++
|
||||
case 0:
|
||||
if coinA.Amount.Add(coinB.Amount).IsZero() {
|
||||
// ignore 0 sum coin type
|
||||
} else {
|
||||
sum = append(sum, coinA.Plus(coinB))
|
||||
}
|
||||
indexA++
|
||||
indexB++
|
||||
case 1:
|
||||
sum = append(sum, coinB)
|
||||
indexB++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Negative returns a set of coins with all amount negative
|
||||
func (coins DecCoins) Negative() DecCoins {
|
||||
res := make([]DecCoin, 0, len(coins))
|
||||
for _, coin := range coins {
|
||||
res = append(res, DecCoin{
|
||||
Denom: coin.Denom,
|
||||
Amount: coin.Amount.Neg(),
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Minus subtracts a set of coins from another (adds the inverse)
|
||||
func (coins DecCoins) Minus(coinsB DecCoins) DecCoins {
|
||||
return coins.Plus(coinsB.Negative())
|
||||
}
|
||||
|
||||
// multiply all the coins by a decimal
|
||||
func (coins DecCoins) MulDec(d sdk.Dec) DecCoins {
|
||||
res := make([]DecCoin, len(coins))
|
||||
for i, coin := range coins {
|
||||
product := DecCoin{
|
||||
Denom: coin.Denom,
|
||||
Amount: coin.Amount.Mul(d),
|
||||
}
|
||||
res[i] = product
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// divide all the coins by a multiple
|
||||
func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins {
|
||||
res := make([]DecCoin, len(coins))
|
||||
for i, coin := range coins {
|
||||
quotient := DecCoin{
|
||||
Denom: coin.Denom,
|
||||
Amount: coin.Amount.Quo(d),
|
||||
}
|
||||
res[i] = quotient
|
||||
}
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlusDecCoin(t *testing.T) {
|
||||
decCoinA1 := DecCoin{"A", sdk.NewDecWithPrec(11, 1)}
|
||||
decCoinA2 := DecCoin{"A", sdk.NewDecWithPrec(22, 1)}
|
||||
decCoinB1 := DecCoin{"B", sdk.NewDecWithPrec(11, 1)}
|
||||
|
||||
// regular add
|
||||
res := decCoinA1.Plus(decCoinA1)
|
||||
require.Equal(t, decCoinA2, res, "sum of coins is incorrect")
|
||||
|
||||
// bad denom add
|
||||
assert.Panics(t, func() {
|
||||
decCoinA1.Plus(decCoinB1)
|
||||
}, "expected panic on sum of different denoms")
|
||||
|
||||
}
|
||||
|
||||
func TestPlusCoins(t *testing.T) {
|
||||
one := sdk.NewDec(1)
|
||||
zero := sdk.NewDec(0)
|
||||
negone := sdk.NewDec(-1)
|
||||
two := sdk.NewDec(2)
|
||||
|
||||
cases := []struct {
|
||||
inputOne DecCoins
|
||||
inputTwo DecCoins
|
||||
expected DecCoins
|
||||
}{
|
||||
{DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", two}, {"B", two}}},
|
||||
{DecCoins{{"A", zero}, {"B", one}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"B", one}}},
|
||||
{DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", one}, {"B", zero}}, DecCoins{{"A", negone}, {"B", zero}}, DecCoins(nil)},
|
||||
{DecCoins{{"A", negone}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", negone}}},
|
||||
}
|
||||
|
||||
for tcIndex, tc := range cases {
|
||||
res := tc.inputOne.Plus(tc.inputTwo)
|
||||
require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// distribution info for a delegation - used to determine entitled rewards
|
||||
type DelegatorDistInfo struct {
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
ValOperatorAddr sdk.ValAddress `json:"val_operator_addr"`
|
||||
WithdrawalHeight int64 `json:"withdrawal_height"` // last time this delegation withdrew rewards
|
||||
}
|
||||
|
||||
func NewDelegatorDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress,
|
||||
currentHeight int64) DelegatorDistInfo {
|
||||
|
||||
return DelegatorDistInfo{
|
||||
DelegatorAddr: delegatorAddr,
|
||||
ValOperatorAddr: valOperatorAddr,
|
||||
WithdrawalHeight: currentHeight,
|
||||
}
|
||||
}
|
||||
|
||||
// withdraw rewards from delegator
|
||||
func (di DelegatorDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo,
|
||||
height int64, totalBonded, vdTokens, totalDelShares, delegatorShares,
|
||||
commissionRate sdk.Dec) (DelegatorDistInfo, ValidatorDistInfo, FeePool, DecCoins) {
|
||||
|
||||
vi = vi.UpdateTotalDelAccum(height, totalDelShares)
|
||||
|
||||
if vi.DelAccum.Accum.IsZero() {
|
||||
return di, vi, fp, DecCoins{}
|
||||
}
|
||||
|
||||
vi, fp = vi.TakeFeePoolRewards(fp, height, totalBonded, vdTokens, commissionRate)
|
||||
|
||||
blocks := height - di.WithdrawalHeight
|
||||
di.WithdrawalHeight = height
|
||||
accum := delegatorShares.MulInt(sdk.NewInt(blocks))
|
||||
withdrawalTokens := vi.Pool.MulDec(accum).QuoDec(vi.DelAccum.Accum)
|
||||
remainingTokens := vi.Pool.Minus(withdrawalTokens)
|
||||
|
||||
vi.Pool = remainingTokens
|
||||
vi.DelAccum.Accum = vi.DelAccum.Accum.Sub(accum)
|
||||
|
||||
return di, vi, fp, withdrawalTokens
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWithdrawRewards(t *testing.T) {
|
||||
|
||||
// initialize
|
||||
height := int64(0)
|
||||
fp := InitialFeePool()
|
||||
vi := NewValidatorDistInfo(valAddr1, height)
|
||||
commissionRate := sdk.NewDecWithPrec(2, 2)
|
||||
validatorTokens := sdk.NewDec(10)
|
||||
validatorDelShares := sdk.NewDec(10)
|
||||
totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power
|
||||
|
||||
di1 := NewDelegatorDistInfo(delAddr1, valAddr1, height)
|
||||
di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator
|
||||
|
||||
di2 := NewDelegatorDistInfo(delAddr2, valAddr1, height)
|
||||
di2Shares := sdk.NewDec(5)
|
||||
|
||||
// simulate adding some stake for inflation
|
||||
height = 10
|
||||
fp.Pool = DecCoins{NewDecCoin("stake", 1000)}
|
||||
|
||||
// withdraw rewards
|
||||
di1, vi, fp, rewardRecv1 := di1.WithdrawRewards(fp, vi, height, totalBondedTokens,
|
||||
validatorTokens, validatorDelShares, di1Shares, commissionRate)
|
||||
|
||||
assert.Equal(t, height, di1.WithdrawalHeight)
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(49), rewardRecv1[0].Amount))
|
||||
|
||||
// add more blocks and inflation
|
||||
height = 20
|
||||
fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000))
|
||||
|
||||
// withdraw rewards
|
||||
di2, vi, fp, rewardRecv2 := di2.WithdrawRewards(fp, vi, height, totalBondedTokens,
|
||||
validatorTokens, validatorDelShares, di2Shares, commissionRate)
|
||||
|
||||
assert.Equal(t, height, di2.WithdrawalHeight)
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.PoolCommission[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount))
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// nolint
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
type CodeType = sdk.CodeType
|
||||
|
||||
const (
|
||||
DefaultCodespace sdk.CodespaceType = 6
|
||||
CodeInvalidInput CodeType = 103
|
||||
)
|
||||
|
||||
func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil")
|
||||
}
|
||||
func ErrNilWithdrawAddr(codespace sdk.CodespaceType) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidInput, "withdraw address is nil")
|
||||
}
|
||||
func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil")
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// total accumulation tracker
|
||||
type TotalAccum struct {
|
||||
UpdateHeight int64 `json:"update_height"`
|
||||
Accum sdk.Dec `json:"accum"`
|
||||
}
|
||||
|
||||
func NewTotalAccum(height int64) TotalAccum {
|
||||
return TotalAccum{
|
||||
UpdateHeight: height,
|
||||
Accum: sdk.ZeroDec(),
|
||||
}
|
||||
}
|
||||
|
||||
// update total validator accumulation factor for the new height
|
||||
// CONTRACT: height should be greater than the old height
|
||||
func (ta TotalAccum) UpdateForNewHeight(height int64, accumCreatedPerBlock sdk.Dec) TotalAccum {
|
||||
blocks := height - ta.UpdateHeight
|
||||
if blocks < 0 {
|
||||
panic("reverse updated for new height")
|
||||
}
|
||||
ta.Accum = ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks)))
|
||||
ta.UpdateHeight = height
|
||||
return ta
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________________
|
||||
|
||||
// global fee pool for distribution
|
||||
type FeePool struct {
|
||||
ValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators
|
||||
Pool DecCoins `json:"pool"` // funds for all validators which have yet to be withdrawn
|
||||
CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent
|
||||
}
|
||||
|
||||
// update total validator accumulation factor
|
||||
func (f FeePool) UpdateTotalValAccum(height int64, totalBondedTokens sdk.Dec) FeePool {
|
||||
f.ValAccum = f.ValAccum.UpdateForNewHeight(height, totalBondedTokens)
|
||||
return f
|
||||
}
|
||||
|
||||
// zero fee pool
|
||||
func InitialFeePool() FeePool {
|
||||
return FeePool{
|
||||
ValAccum: NewTotalAccum(0),
|
||||
Pool: DecCoins{},
|
||||
CommunityPool: DecCoins{},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTotalAccumUpdateForNewHeight(t *testing.T) {
|
||||
|
||||
ta := NewTotalAccum(0)
|
||||
|
||||
ta = ta.UpdateForNewHeight(5, sdk.NewDec(3))
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(15), ta.Accum))
|
||||
|
||||
ta = ta.UpdateForNewHeight(8, sdk.NewDec(2))
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(21), ta.Accum))
|
||||
}
|
||||
|
||||
func TestUpdateTotalValAccum(t *testing.T) {
|
||||
|
||||
fp := InitialFeePool()
|
||||
|
||||
fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3))
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(15), fp.ValAccum.Accum))
|
||||
|
||||
fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2))
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(21), fp.ValAccum.Accum))
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package types
|
||||
|
||||
import sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
// the address for where distributions rewards are withdrawn to by default
|
||||
// this struct is only used at genesis to feed in default withdraw addresses
|
||||
type DelegatorWithdrawInfo struct {
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
WithdrawAddr sdk.AccAddress `json:"withdraw_addr"`
|
||||
}
|
||||
|
||||
// GenesisState - all distribution state that must be provided at genesis
|
||||
type GenesisState struct {
|
||||
FeePool FeePool `json:"fee_pool"`
|
||||
ValidatorDistInfos []ValidatorDistInfo `json:"validator_dist_infos"`
|
||||
DelegatorDistInfos []DelegatorDistInfo `json:"delegator_dist_infos"`
|
||||
DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"`
|
||||
}
|
||||
|
||||
func NewGenesisState(feePool FeePool, vdis []ValidatorDistInfo, ddis []DelegatorDistInfo) GenesisState {
|
||||
return GenesisState{
|
||||
FeePool: feePool,
|
||||
ValidatorDistInfos: vdis,
|
||||
DelegatorDistInfos: ddis,
|
||||
}
|
||||
}
|
||||
|
||||
// get raw genesis raw message for testing
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return GenesisState{
|
||||
FeePool: InitialFeePool(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package types
|
||||
|
||||
import sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
// expected stake keeper
|
||||
type StakeKeeper interface {
|
||||
IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress,
|
||||
fn func(index int64, delegation sdk.Delegation) (stop bool))
|
||||
Delegation(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) sdk.Delegation
|
||||
Validator(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Validator
|
||||
ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) sdk.Validator
|
||||
TotalPower(ctx sdk.Context) sdk.Dec
|
||||
}
|
||||
|
||||
// expected coin keeper
|
||||
type BankKeeper interface {
|
||||
AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error)
|
||||
}
|
||||
|
||||
// from ante handler
|
||||
type FeeCollectionKeeper interface {
|
||||
GetCollectedFees(ctx sdk.Context) sdk.Coins
|
||||
ClearCollectedFees(ctx sdk.Context)
|
||||
}
|
|
@ -0,0 +1,175 @@
|
|||
//nolint
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// name to identify transaction types
|
||||
const MsgType = "distr"
|
||||
|
||||
// Verify interface at compile time
|
||||
var _, _ sdk.Msg = &MsgSetWithdrawAddress{}, &MsgWithdrawDelegatorRewardsAll{}
|
||||
var _, _ sdk.Msg = &MsgWithdrawDelegatorReward{}, &MsgWithdrawValidatorRewardsAll{}
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
// msg struct for changing the withdraw address for a delegator (or validator self-delegation)
|
||||
type MsgSetWithdrawAddress struct {
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
WithdrawAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
}
|
||||
|
||||
func NewMsgSetWithdrawAddress(delAddr, withdrawAddr sdk.AccAddress) MsgSetWithdrawAddress {
|
||||
return MsgSetWithdrawAddress{
|
||||
DelegatorAddr: delAddr,
|
||||
WithdrawAddr: withdrawAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg MsgSetWithdrawAddress) Type() string { return MsgType }
|
||||
func (msg MsgSetWithdrawAddress) Name() string { return "set_withdraw_address" }
|
||||
|
||||
// Return address that must sign over msg.GetSignBytes()
|
||||
func (msg MsgSetWithdrawAddress) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)}
|
||||
}
|
||||
|
||||
// get the bytes for the message signer to sign on
|
||||
func (msg MsgSetWithdrawAddress) GetSignBytes() []byte {
|
||||
b, err := MsgCdc.MarshalJSON(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
|
||||
// quick validity check
|
||||
func (msg MsgSetWithdrawAddress) ValidateBasic() sdk.Error {
|
||||
if msg.DelegatorAddr == nil {
|
||||
return ErrNilDelegatorAddr(DefaultCodespace)
|
||||
}
|
||||
if msg.WithdrawAddr == nil {
|
||||
return ErrNilWithdrawAddr(DefaultCodespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
// msg struct for delegation withdraw for all of the delegator's delegations
|
||||
type MsgWithdrawDelegatorRewardsAll struct {
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
}
|
||||
|
||||
func NewMsgWithdrawDelegatorRewardsAll(delAddr sdk.AccAddress) MsgWithdrawDelegatorRewardsAll {
|
||||
return MsgWithdrawDelegatorRewardsAll{
|
||||
DelegatorAddr: delAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg MsgWithdrawDelegatorRewardsAll) Type() string { return MsgType }
|
||||
func (msg MsgWithdrawDelegatorRewardsAll) Name() string { return "withdraw_delegation_rewards_all" }
|
||||
|
||||
// Return address that must sign over msg.GetSignBytes()
|
||||
func (msg MsgWithdrawDelegatorRewardsAll) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)}
|
||||
}
|
||||
|
||||
// get the bytes for the message signer to sign on
|
||||
func (msg MsgWithdrawDelegatorRewardsAll) GetSignBytes() []byte {
|
||||
b, err := MsgCdc.MarshalJSON(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
|
||||
// quick validity check
|
||||
func (msg MsgWithdrawDelegatorRewardsAll) ValidateBasic() sdk.Error {
|
||||
if msg.DelegatorAddr == nil {
|
||||
return ErrNilDelegatorAddr(DefaultCodespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
// msg struct for delegation withdraw from a single validator
|
||||
type MsgWithdrawDelegatorReward struct {
|
||||
DelegatorAddr sdk.AccAddress `json:"delegator_addr"`
|
||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
|
||||
}
|
||||
|
||||
func NewMsgWithdrawDelegatorReward(delAddr sdk.AccAddress, valAddr sdk.ValAddress) MsgWithdrawDelegatorReward {
|
||||
return MsgWithdrawDelegatorReward{
|
||||
DelegatorAddr: delAddr,
|
||||
ValidatorAddr: valAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg MsgWithdrawDelegatorReward) Type() string { return MsgType }
|
||||
func (msg MsgWithdrawDelegatorReward) Name() string { return "withdraw_delegation_reward" }
|
||||
|
||||
// Return address that must sign over msg.GetSignBytes()
|
||||
func (msg MsgWithdrawDelegatorReward) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{sdk.AccAddress(msg.DelegatorAddr)}
|
||||
}
|
||||
|
||||
// get the bytes for the message signer to sign on
|
||||
func (msg MsgWithdrawDelegatorReward) GetSignBytes() []byte {
|
||||
b, err := MsgCdc.MarshalJSON(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
|
||||
// quick validity check
|
||||
func (msg MsgWithdrawDelegatorReward) ValidateBasic() sdk.Error {
|
||||
if msg.DelegatorAddr == nil {
|
||||
return ErrNilDelegatorAddr(DefaultCodespace)
|
||||
}
|
||||
if msg.ValidatorAddr == nil {
|
||||
return ErrNilValidatorAddr(DefaultCodespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//______________________________________________________________________
|
||||
|
||||
// msg struct for validator withdraw
|
||||
type MsgWithdrawValidatorRewardsAll struct {
|
||||
ValidatorAddr sdk.ValAddress `json:"validator_addr"`
|
||||
}
|
||||
|
||||
func NewMsgWithdrawValidatorRewardsAll(valAddr sdk.ValAddress) MsgWithdrawValidatorRewardsAll {
|
||||
return MsgWithdrawValidatorRewardsAll{
|
||||
ValidatorAddr: valAddr,
|
||||
}
|
||||
}
|
||||
|
||||
func (msg MsgWithdrawValidatorRewardsAll) Type() string { return MsgType }
|
||||
func (msg MsgWithdrawValidatorRewardsAll) Name() string { return "withdraw_validator_rewards_all" }
|
||||
|
||||
// Return address that must sign over msg.GetSignBytes()
|
||||
func (msg MsgWithdrawValidatorRewardsAll) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{sdk.AccAddress(msg.ValidatorAddr.Bytes())}
|
||||
}
|
||||
|
||||
// get the bytes for the message signer to sign on
|
||||
func (msg MsgWithdrawValidatorRewardsAll) GetSignBytes() []byte {
|
||||
b, err := MsgCdc.MarshalJSON(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sdk.MustSortJSON(b)
|
||||
}
|
||||
|
||||
// quick validity check
|
||||
func (msg MsgWithdrawValidatorRewardsAll) ValidateBasic() sdk.Error {
|
||||
if msg.ValidatorAddr == nil {
|
||||
return ErrNilValidatorAddr(DefaultCodespace)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// test ValidateBasic for MsgCreateValidator
|
||||
func TestMsgSetWithdrawAddress(t *testing.T) {
|
||||
tests := []struct {
|
||||
delegatorAddr sdk.AccAddress
|
||||
withdrawAddr sdk.AccAddress
|
||||
expectPass bool
|
||||
}{
|
||||
{delAddr1, delAddr2, true},
|
||||
{delAddr1, delAddr1, true},
|
||||
{emptyDelAddr, delAddr1, false},
|
||||
{delAddr1, emptyDelAddr, false},
|
||||
{emptyDelAddr, emptyDelAddr, false},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgSetWithdrawAddress(tc.delegatorAddr, tc.withdrawAddr)
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test ValidateBasic for MsgEditValidator
|
||||
func TestMsgWithdrawDelegatorReward(t *testing.T) {
|
||||
tests := []struct {
|
||||
delegatorAddr sdk.AccAddress
|
||||
validatorAddr sdk.ValAddress
|
||||
expectPass bool
|
||||
}{
|
||||
{delAddr1, valAddr1, true},
|
||||
{emptyDelAddr, valAddr1, false},
|
||||
{delAddr1, emptyValAddr, false},
|
||||
{emptyDelAddr, emptyValAddr, false},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgWithdrawDelegatorReward(tc.delegatorAddr, tc.validatorAddr)
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf
|
||||
func TestMsgWithdrawDelegatorRewardsAll(t *testing.T) {
|
||||
tests := []struct {
|
||||
delegatorAddr sdk.AccAddress
|
||||
expectPass bool
|
||||
}{
|
||||
{delAddr1, true},
|
||||
{emptyDelAddr, false},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgWithdrawDelegatorRewardsAll(tc.delegatorAddr)
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test ValidateBasic for MsgDelegate
|
||||
func TestMsgWithdrawValidatorRewardsAll(t *testing.T) {
|
||||
tests := []struct {
|
||||
validatorAddr sdk.ValAddress
|
||||
expectPass bool
|
||||
}{
|
||||
{valAddr1, true},
|
||||
{emptyValAddr, false},
|
||||
}
|
||||
for i, tc := range tests {
|
||||
msg := NewMsgWithdrawValidatorRewardsAll(tc.validatorAddr)
|
||||
if tc.expectPass {
|
||||
require.Nil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
} else {
|
||||
require.NotNil(t, msg.ValidateBasic(), "test index: %v", i)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
)
|
||||
|
||||
var (
|
||||
delPk1 = ed25519.GenPrivKey().PubKey()
|
||||
delPk2 = ed25519.GenPrivKey().PubKey()
|
||||
delPk3 = ed25519.GenPrivKey().PubKey()
|
||||
delAddr1 = sdk.AccAddress(delPk1.Address())
|
||||
delAddr2 = sdk.AccAddress(delPk2.Address())
|
||||
delAddr3 = sdk.AccAddress(delPk3.Address())
|
||||
emptyDelAddr sdk.AccAddress
|
||||
|
||||
valPk1 = ed25519.GenPrivKey().PubKey()
|
||||
valPk2 = ed25519.GenPrivKey().PubKey()
|
||||
valPk3 = ed25519.GenPrivKey().PubKey()
|
||||
valAddr1 = sdk.ValAddress(delPk1.Address())
|
||||
valAddr2 = sdk.ValAddress(delPk2.Address())
|
||||
valAddr3 = sdk.ValAddress(delPk3.Address())
|
||||
emptyValAddr sdk.ValAddress
|
||||
|
||||
emptyPubkey crypto.PubKey
|
||||
)
|
|
@ -0,0 +1,72 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// distribution info for a particular validator
|
||||
type ValidatorDistInfo struct {
|
||||
OperatorAddr sdk.ValAddress `json:"operator_addr"`
|
||||
|
||||
FeePoolWithdrawalHeight int64 `json:"global_withdrawal_height"` // last height this validator withdrew from the global pool
|
||||
Pool DecCoins `json:"pool"` // rewards owed to delegators, commission has already been charged (includes proposer reward)
|
||||
PoolCommission DecCoins `json:"pool_commission"` // commission collected by this validator (pending withdrawal)
|
||||
|
||||
DelAccum TotalAccum `json:"del_accum"` // total proposer pool accumulation factor held by delegators
|
||||
}
|
||||
|
||||
func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) ValidatorDistInfo {
|
||||
return ValidatorDistInfo{
|
||||
OperatorAddr: operatorAddr,
|
||||
FeePoolWithdrawalHeight: currentHeight,
|
||||
Pool: DecCoins{},
|
||||
PoolCommission: DecCoins{},
|
||||
DelAccum: NewTotalAccum(currentHeight),
|
||||
}
|
||||
}
|
||||
|
||||
// update total delegator accumululation
|
||||
func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk.Dec) ValidatorDistInfo {
|
||||
vi.DelAccum = vi.DelAccum.UpdateForNewHeight(height, totalDelShares)
|
||||
return vi
|
||||
}
|
||||
|
||||
// move any available accumulated fees in the FeePool to the validator's pool
|
||||
func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBonded, vdTokens,
|
||||
commissionRate sdk.Dec) (ValidatorDistInfo, FeePool) {
|
||||
|
||||
fp = fp.UpdateTotalValAccum(height, totalBonded)
|
||||
|
||||
if fp.ValAccum.Accum.IsZero() {
|
||||
return vi, fp
|
||||
}
|
||||
|
||||
// update the validators pool
|
||||
blocks := height - vi.FeePoolWithdrawalHeight
|
||||
vi.FeePoolWithdrawalHeight = height
|
||||
accum := vdTokens.MulInt(sdk.NewInt(blocks))
|
||||
withdrawalTokens := fp.Pool.MulDec(accum).QuoDec(fp.ValAccum.Accum)
|
||||
remainingTokens := fp.Pool.Minus(withdrawalTokens)
|
||||
|
||||
commission := withdrawalTokens.MulDec(commissionRate)
|
||||
afterCommission := withdrawalTokens.Minus(commission)
|
||||
|
||||
fp.ValAccum.Accum = fp.ValAccum.Accum.Sub(accum)
|
||||
fp.Pool = remainingTokens
|
||||
vi.PoolCommission = vi.PoolCommission.Plus(commission)
|
||||
vi.Pool = vi.Pool.Plus(afterCommission)
|
||||
|
||||
return vi, fp
|
||||
}
|
||||
|
||||
// withdraw commission rewards
|
||||
func (vi ValidatorDistInfo) WithdrawCommission(fp FeePool, height int64,
|
||||
totalBonded, vdTokens, commissionRate sdk.Dec) (vio ValidatorDistInfo, fpo FeePool, withdrawn DecCoins) {
|
||||
|
||||
vi, fp = vi.TakeFeePoolRewards(fp, height, totalBonded, vdTokens, commissionRate)
|
||||
|
||||
withdrawalTokens := vi.PoolCommission
|
||||
vi.PoolCommission = DecCoins{} // zero
|
||||
|
||||
return vi, fp, withdrawalTokens
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTakeFeePoolRewards(t *testing.T) {
|
||||
|
||||
// initialize
|
||||
height := int64(0)
|
||||
fp := InitialFeePool()
|
||||
vi1 := NewValidatorDistInfo(valAddr1, height)
|
||||
vi2 := NewValidatorDistInfo(valAddr2, height)
|
||||
vi3 := NewValidatorDistInfo(valAddr3, height)
|
||||
commissionRate1 := sdk.NewDecWithPrec(2, 2)
|
||||
commissionRate2 := sdk.NewDecWithPrec(3, 2)
|
||||
commissionRate3 := sdk.NewDecWithPrec(4, 2)
|
||||
validatorTokens1 := sdk.NewDec(10)
|
||||
validatorTokens2 := sdk.NewDec(40)
|
||||
validatorTokens3 := sdk.NewDec(50)
|
||||
totalBondedTokens := validatorTokens1.Add(validatorTokens2).Add(validatorTokens3)
|
||||
|
||||
// simulate adding some stake for inflation
|
||||
height = 10
|
||||
fp.Pool = DecCoins{NewDecCoin("stake", 1000)}
|
||||
|
||||
vi1, fp = vi1.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens1, commissionRate1)
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.PoolCommission[0].Amount))
|
||||
|
||||
vi2, fp = vi2.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens2, commissionRate2)
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, vi2.PoolCommission[0].Amount, sdk.NewDec(12)))
|
||||
|
||||
// add more blocks and inflation
|
||||
height = 20
|
||||
fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000))
|
||||
|
||||
vi3, fp = vi3.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens3, commissionRate3)
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, vi3.PoolCommission[0].Amount, sdk.NewDec(40)))
|
||||
}
|
||||
|
||||
func TestWithdrawCommission(t *testing.T) {
|
||||
|
||||
// initialize
|
||||
height := int64(0)
|
||||
fp := InitialFeePool()
|
||||
vi := NewValidatorDistInfo(valAddr1, height)
|
||||
commissionRate := sdk.NewDecWithPrec(2, 2)
|
||||
validatorTokens := sdk.NewDec(10)
|
||||
totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power
|
||||
|
||||
// simulate adding some stake for inflation
|
||||
height = 10
|
||||
fp.Pool = DecCoins{NewDecCoin("stake", 1000)}
|
||||
|
||||
// for a more fun staring condition, have an non-withdraw update
|
||||
vi, fp = vi.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens, commissionRate)
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount))
|
||||
|
||||
// add more blocks and inflation
|
||||
height = 20
|
||||
fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000))
|
||||
|
||||
vi, fp, commissionRecv := vi.WithdrawCommission(fp, height, totalBondedTokens, validatorTokens, commissionRate)
|
||||
require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.Pool[0].Amount))
|
||||
assert.Zero(t, len(vi.PoolCommission))
|
||||
assert.True(sdk.DecEq(t, sdk.NewDec(4), commissionRecv[0].Amount))
|
||||
}
|
Loading…
Reference in New Issue