From 9c9113fa3b3f5f035f47a215879941958a0f72fe Mon Sep 17 00:00:00 2001 From: Rigel Date: Fri, 5 Oct 2018 06:41:38 -0400 Subject: [PATCH 1/2] Merge PR #2437: Distr-PR-4 Distribution Types --- PENDING.md | 1 + x/distribution/types/codec.go | 23 +++ x/distribution/types/dec_coin.go | 149 +++++++++++++++++ x/distribution/types/dec_coin_test.go | 49 ++++++ x/distribution/types/delegator_info.go | 47 ++++++ x/distribution/types/delegator_info_test.go | 56 +++++++ x/distribution/types/errors.go | 23 +++ x/distribution/types/fee_pool.go | 54 ++++++ x/distribution/types/fee_pool_test.go | 30 ++++ x/distribution/types/genesis.go | 33 ++++ x/distribution/types/keepers.go | 24 +++ x/distribution/types/msg.go | 175 ++++++++++++++++++++ x/distribution/types/msg_test.go | 93 +++++++++++ x/distribution/types/test_utils.go | 27 +++ x/distribution/types/validator_info.go | 72 ++++++++ x/distribution/types/validator_info_test.go | 85 ++++++++++ 16 files changed, 941 insertions(+) create mode 100644 x/distribution/types/codec.go create mode 100644 x/distribution/types/dec_coin.go create mode 100644 x/distribution/types/dec_coin_test.go create mode 100644 x/distribution/types/delegator_info.go create mode 100644 x/distribution/types/delegator_info_test.go create mode 100644 x/distribution/types/errors.go create mode 100644 x/distribution/types/fee_pool.go create mode 100644 x/distribution/types/fee_pool_test.go create mode 100644 x/distribution/types/genesis.go create mode 100644 x/distribution/types/keepers.go create mode 100644 x/distribution/types/msg.go create mode 100644 x/distribution/types/msg_test.go create mode 100644 x/distribution/types/test_utils.go create mode 100644 x/distribution/types/validator_info.go create mode 100644 x/distribution/types/validator_info_test.go diff --git a/PENDING.md b/PENDING.md index 84d38ecf3..9c1ab1c02 100644 --- a/PENDING.md +++ b/PENDING.md @@ -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. diff --git a/x/distribution/types/codec.go b/x/distribution/types/codec.go new file mode 100644 index 000000000..10b2dc2fb --- /dev/null +++ b/x/distribution/types/codec.go @@ -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() +} diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go new file mode 100644 index 000000000..59373976d --- /dev/null +++ b/x/distribution/types/dec_coin.go @@ -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 +} diff --git a/x/distribution/types/dec_coin_test.go b/x/distribution/types/dec_coin_test.go new file mode 100644 index 000000000..5a326fa17 --- /dev/null +++ b/x/distribution/types/dec_coin_test.go @@ -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) + } +} diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go new file mode 100644 index 000000000..4de296890 --- /dev/null +++ b/x/distribution/types/delegator_info.go @@ -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 +} diff --git a/x/distribution/types/delegator_info_test.go b/x/distribution/types/delegator_info_test.go new file mode 100644 index 000000000..2d9e9bc0f --- /dev/null +++ b/x/distribution/types/delegator_info_test.go @@ -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)) +} diff --git a/x/distribution/types/errors.go b/x/distribution/types/errors.go new file mode 100644 index 000000000..57a3dd73e --- /dev/null +++ b/x/distribution/types/errors.go @@ -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") +} diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go new file mode 100644 index 000000000..66731cb19 --- /dev/null +++ b/x/distribution/types/fee_pool.go @@ -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{}, + } +} diff --git a/x/distribution/types/fee_pool_test.go b/x/distribution/types/fee_pool_test.go new file mode 100644 index 000000000..e39fb09c9 --- /dev/null +++ b/x/distribution/types/fee_pool_test.go @@ -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)) +} diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go new file mode 100644 index 000000000..f01d1b781 --- /dev/null +++ b/x/distribution/types/genesis.go @@ -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(), + } +} diff --git a/x/distribution/types/keepers.go b/x/distribution/types/keepers.go new file mode 100644 index 000000000..31a68a5da --- /dev/null +++ b/x/distribution/types/keepers.go @@ -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) +} diff --git a/x/distribution/types/msg.go b/x/distribution/types/msg.go new file mode 100644 index 000000000..f00dceae1 --- /dev/null +++ b/x/distribution/types/msg.go @@ -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 +} diff --git a/x/distribution/types/msg_test.go b/x/distribution/types/msg_test.go new file mode 100644 index 000000000..b0c0dd9eb --- /dev/null +++ b/x/distribution/types/msg_test.go @@ -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) + } + } +} diff --git a/x/distribution/types/test_utils.go b/x/distribution/types/test_utils.go new file mode 100644 index 000000000..3ea78cd79 --- /dev/null +++ b/x/distribution/types/test_utils.go @@ -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 +) diff --git a/x/distribution/types/validator_info.go b/x/distribution/types/validator_info.go new file mode 100644 index 000000000..18aef8bde --- /dev/null +++ b/x/distribution/types/validator_info.go @@ -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 +} diff --git a/x/distribution/types/validator_info_test.go b/x/distribution/types/validator_info_test.go new file mode 100644 index 000000000..afa6d8c76 --- /dev/null +++ b/x/distribution/types/validator_info_test.go @@ -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)) +} From c4b243142d73b604dcd703ee745b3abde004a811 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 5 Oct 2018 03:42:52 -0700 Subject: [PATCH 2/2] Merge PR #2438: Add tests for more staking key operations as requested --- x/stake/keeper/key.go | 28 ++++++++-------- x/stake/keeper/key_test.go | 67 ++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 7d3ea0850..6beb17937 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -177,28 +177,28 @@ func GetREDByValDstIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.V return key } -// rearranges the ValSrcIndexKey to get the REDKey -func GetREDKeyFromValSrcIndexKey(IndexKey []byte) []byte { - addrs := IndexKey[1:] // remove prefix bytes - if len(addrs) != 3*sdk.AddrLen { +// GetREDKeyFromValSrcIndexKey rearranges the ValSrcIndexKey to get the REDKey +func GetREDKeyFromValSrcIndexKey(indexKey []byte) []byte { + // note that first byte is prefix byte + if len(indexKey) != 3*sdk.AddrLen+1 { panic("unexpected key length") } - valSrcAddr := addrs[:sdk.AddrLen] - delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] - valDstAddr := addrs[2*sdk.AddrLen:] + valSrcAddr := indexKey[1 : sdk.AddrLen+1] + delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1] + valDstAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1] return GetREDKey(delAddr, valSrcAddr, valDstAddr) } -// rearranges the ValDstIndexKey to get the REDKey -func GetREDKeyFromValDstIndexKey(IndexKey []byte) []byte { - addrs := IndexKey[1:] // remove prefix bytes - if len(addrs) != 3*sdk.AddrLen { +// GetREDKeyFromValDstIndexKey rearranges the ValDstIndexKey to get the REDKey +func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte { + // note that first byte is prefix byte + if len(indexKey) != 3*sdk.AddrLen+1 { panic("unexpected key length") } - valDstAddr := addrs[:sdk.AddrLen] - delAddr := addrs[sdk.AddrLen : 2*sdk.AddrLen] - valSrcAddr := addrs[2*sdk.AddrLen:] + valDstAddr := indexKey[1 : sdk.AddrLen+1] + delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1] + valSrcAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1] return GetREDKey(delAddr, valSrcAddr, valDstAddr) } diff --git a/x/stake/keeper/key_test.go b/x/stake/keeper/key_test.go index 3272a50e2..591e7d1bc 100644 --- a/x/stake/keeper/key_test.go +++ b/x/stake/keeper/key_test.go @@ -2,30 +2,43 @@ package keeper import ( "encoding/hex" + "math/big" "testing" - "github.com/stretchr/testify/assert" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/stretchr/testify/assert" "github.com/tendermint/tendermint/crypto/ed25519" ) var ( - pk1 = ed25519.GenPrivKey().PubKey() + pk1 = ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() + pk2 = ed25519.GenPrivKeyFromSecret([]byte{2}).PubKey() + pk3 = ed25519.GenPrivKeyFromSecret([]byte{3}).PubKey() + addr1 = pk1.Address() + addr2 = pk2.Address() + addr3 = pk3.Address() ) func TestGetValidatorPowerRank(t *testing.T) { - valAddr1 := sdk.ValAddress(pk1.Bytes()) + valAddr1 := sdk.ValAddress(addr1) emptyDesc := types.Description{} val1 := types.NewValidator(valAddr1, pk1, emptyDesc) - val1.Tokens = sdk.NewDec(12) + val1.Tokens = sdk.NewDec(0) + val2, val3, val4 := val1, val1, val1 + val2.Tokens = sdk.NewDec(1) + val3.Tokens = sdk.NewDec(10) + x := new(big.Int).Exp(big.NewInt(2), big.NewInt(40), big.NewInt(0)) + val4.Tokens = sdk.NewDecFromBigInt(x) tests := []struct { validator types.Validator wantHex string }{ - {val1, "05303030303030303030303132ffffffffffffffffffff"}, + {val1, "05303030303030303030303030ffffffffffffffffffff"}, + {val2, "05303030303030303030303031ffffffffffffffffffff"}, + {val3, "05303030303030303030303130ffffffffffffffffffff"}, + {val4, "0531303939353131363237373736ffffffffffffffffffff"}, } for i, tt := range tests { got := hex.EncodeToString(getValidatorPowerRank(tt.validator)) @@ -33,3 +46,45 @@ func TestGetValidatorPowerRank(t *testing.T) { assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) } } + +func TestGetREDByValDstIndexKey(t *testing.T) { + tests := []struct { + delAddr sdk.AccAddress + valSrcAddr sdk.ValAddress + valDstAddr sdk.ValAddress + wantHex string + }{ + {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), + "0c63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), + "0c3ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, + {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), + "0c3ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, + } + for i, tt := range tests { + got := hex.EncodeToString(GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) + + assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) + } +} + +func TestGetREDByValSrcIndexKey(t *testing.T) { + tests := []struct { + delAddr sdk.AccAddress + valSrcAddr sdk.ValAddress + valDstAddr sdk.ValAddress + wantHex string + }{ + {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), + "0b63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), + "0b5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, + {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), + "0b63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, + } + for i, tt := range tests { + got := hex.EncodeToString(GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) + + assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) + } +}