From d26dbbd5f5ecf4c3b445e0a31ac3420a8e41a755 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 3 Jul 2018 23:10:26 +0200 Subject: [PATCH 1/4] Remove unnecessary tmlibs in Gopkg.toml --- Gopkg.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index 486293af71..0a4d3b2f17 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -64,10 +64,6 @@ name = "github.com/tendermint/tendermint" version = "=0.22.0-rc2" -[[constraint]] - name = "github.com/tendermint/tmlibs" - version = "=0.9.0" - [[constraint]] name = "github.com/bartekn/go-bip39" branch = "master" From cae6b40221979ded236c0fb0bad2859434b0f086 Mon Sep 17 00:00:00 2001 From: Rigel Date: Tue, 3 Jul 2018 19:15:48 -0400 Subject: [PATCH 2/4] Merge pull request #1467: staking index keys don't hold values * docs: Explain the expected return type within the store (#1452) * bug somewhere here * ... * ... * fix appending over key * keys cleanup * changelog * remove some junk * address bucky comments - rearrange appends * hard code address length --- CHANGELOG.md | 2 + x/stake/client/cli/query.go | 12 +- x/stake/client/cli/tx.go | 2 +- x/stake/client/rest/query.go | 6 +- x/stake/keeper/delegation.go | 48 ++++---- x/stake/keeper/delegation_test.go | 40 ++++++- x/stake/keeper/key.go | 182 ++++++++++++++++++------------ x/stake/keeper/sdk_types.go | 4 +- x/stake/keeper/validator.go | 8 +- 9 files changed, 192 insertions(+), 112 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecba003625..56bad46c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ BREAKING CHANGES to an infraction, slash them proportional to their stake at the time * Add REST endpoint to unrevoke a validator previously revoked for downtime * Add REST endpoint to retrieve liveness signing information for a validator +* [types] renamed rational.Evaluate to rational.Round{Int64, Int} +* [stake] most index keys nolonger hold a value - inputs are rearranged to form the desired key * [lcd] Switch key creation output to return bech32 FEATURES diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index bccfd7c2be..5a22f2c416 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -122,7 +122,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := stake.GetDelegationKey(delAddr, valAddr, cdc) + key := stake.GetDelegationKey(delAddr, valAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -169,7 +169,7 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { if err != nil { return err } - key := stake.GetDelegationsKey(delegatorAddr, cdc) + key := stake.GetDelegationsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -214,7 +214,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Co return err } - key := stake.GetUBDKey(delAddr, valAddr, cdc) + key := stake.GetUBDKey(delAddr, valAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -261,7 +261,7 @@ func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.C if err != nil { return err } - key := stake.GetUBDsKey(delegatorAddr, cdc) + key := stake.GetUBDsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -309,7 +309,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr, cdc) + key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -356,7 +356,7 @@ func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command if err != nil { return err } - key := stake.GetREDsKey(delegatorAddr, cdc) + key := stake.GetREDsKey(delegatorAddr) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index dff765fac7..7200caef3f 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -240,7 +240,7 @@ func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercent } // make a query to get the existing delegation shares - key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) ctx := context.NewCoreContextFromViper() resQuery, err := ctx.QueryStore(key, storeName) if err != nil { diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index afa9e3bf0c..12ef882e5e 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -60,7 +60,7 @@ func delegationHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerF return } - key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -117,7 +117,7 @@ func ubdHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - key := stake.GetUBDKey(delegatorAddr, validatorAddr, cdc) + key := stake.GetUBDKey(delegatorAddr, validatorAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { @@ -182,7 +182,7 @@ func redHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc { return } - key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr, cdc) + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr) res, err := ctx.QueryStore(key, storeName) if err != nil { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 514939e17f..bb8170a6a3 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -12,7 +12,7 @@ func (k Keeper) GetDelegation(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address) (delegation types.Delegation, found bool) { store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) + delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr)) if delegatorBytes == nil { return delegation, false } @@ -46,7 +46,7 @@ func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (delegations []types.Delegation) { store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) + delegatorPrefixKey := GetDelegationsKey(delegator) iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest delegations = make([]types.Delegation, maxRetrieve) @@ -69,13 +69,13 @@ func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) b := k.cdc.MustMarshalBinary(delegation) - store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc), b) + store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr), b) } // remove the delegation func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc)) + store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr)) } //_____________________________________________________________________________________ @@ -85,7 +85,7 @@ func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, DelegatorAddr, ValidatorAddr sdk.Address) (ubd types.UnbondingDelegation, found bool) { store := ctx.KVStore(k.storeKey) - ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr, k.cdc) + ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr) bz := store.Get(ubdKey) if bz == nil { return ubd, false @@ -98,13 +98,12 @@ func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, // load all unbonding delegations from a particular validator func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (unbondingDelegations []types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr, k.cdc)) - i := 0 - for ; ; i++ { + iterator := sdk.KVStorePrefixIterator(store, GetUBDsByValIndexKey(valAddr)) + for { if !iterator.Valid() { break } - unbondingKey := iterator.Value() + unbondingKey := GetUBDKeyFromValIndexKey(iterator.Key()) unbondingBytes := store.Get(unbondingKey) var unbondingDelegation types.UnbondingDelegation k.cdc.MustUnmarshalBinary(unbondingBytes, &unbondingDelegation) @@ -119,17 +118,17 @@ func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sd func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(ubd) - ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) store.Set(ubdKey, bz) - store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc), ubdKey) + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr), []byte{}) } // remove the unbonding delegation object and associated index func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) - ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr) store.Delete(ubdKey) - store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr)) } //_____________________________________________________________________________________ @@ -139,7 +138,7 @@ func (k Keeper) GetRedelegation(ctx sdk.Context, DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.Address) (red types.Redelegation, found bool) { store := ctx.KVStore(k.storeKey) - redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr, k.cdc) + redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr) bz := store.Get(redKey) if bz == nil { return red, false @@ -152,13 +151,12 @@ func (k Keeper) GetRedelegation(ctx sdk.Context, // load all redelegations from a particular validator func (k Keeper) GetRedelegationsFromValidator(ctx sdk.Context, valAddr sdk.Address) (redelegations []types.Redelegation) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr, k.cdc)) - i := 0 - for ; ; i++ { + iterator := sdk.KVStorePrefixIterator(store, GetREDsFromValSrcIndexKey(valAddr)) + for { if !iterator.Valid() { break } - redelegationKey := iterator.Value() + redelegationKey := GetREDKeyFromValSrcIndexKey(iterator.Key()) redelegationBytes := store.Get(redelegationKey) var redelegation types.Redelegation k.cdc.MustUnmarshalBinary(redelegationBytes, &redelegation) @@ -174,7 +172,7 @@ func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, DelegatorAddr, ValidatorDstAddr sdk.Address) bool { store := ctx.KVStore(k.storeKey) - prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr, k.cdc) + prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr) iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest found := false @@ -190,19 +188,19 @@ func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinary(red) - redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) store.Set(redKey, bz) - store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) - store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr), []byte{}) } // remove a redelegation object and associated index func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { store := ctx.KVStore(k.storeKey) - redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr) store.Delete(redKey) - store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) - store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr)) } //_____________________________________________________________________________________ diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index eb318df4d3..c0a3ee8c57 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -179,6 +179,36 @@ func TestUnbondDelegation(t *testing.T) { require.Equal(t, int64(4), pool.BondedTokens) } +// Make sure that that the retrieving the delegations doesn't affect the state +func TestGetRedelegationsFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + // get the redelegations one time + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + + // get the redelegations a second time, should be exactly the same + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) +} + // tests Get/Set/Remove/Has UnbondingDelegation func TestRedelegation(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 0) @@ -201,7 +231,10 @@ func TestRedelegation(t *testing.T) { keeper.SetRedelegation(ctx, rd) resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) - require.True(t, rd.Equal(resBond)) + + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) // check if has the redelegation has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) @@ -211,10 +244,15 @@ func TestRedelegation(t *testing.T) { rd.SharesSrc = sdk.NewRat(21) rd.SharesDst = sdk.NewRat(21) keeper.SetRedelegation(ctx, rd) + resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.True(t, found) require.True(t, rd.Equal(resBond)) + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resBond)) + // delete a record keeper.RemoveRedelegation(ctx, rd) _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 667f5f681e..cfe8ded82b 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -6,7 +6,6 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake/types" ) @@ -29,41 +28,49 @@ var ( UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner RedelegationKey = []byte{0x0D} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by validator owner - RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by validator owner + RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by source validator owner + RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by destination validator owner ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch -// get the key for the validator with address +// get the key for the validator with address. +// VALUE: stake/types.Validator func GetValidatorKey(ownerAddr sdk.Address) []byte { return append(ValidatorsKey, ownerAddr.Bytes()...) } -// get the key for the validator with pubkey +// get the key for the validator with pubkey. +// VALUE: validator owner address ([]byte) func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) } -// get the key for the current validator group, ordered like tendermint +// get the key for the current validator group +// VALUE: none (key rearrangement with GetValKeyFromValBondedIndexKey) func GetValidatorsBondedIndexKey(ownerAddr sdk.Address) []byte { return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...) } -// get the power which is the key for the validator used in the power-store -func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { - - // NOTE the address doesn't need to be stored because counter bytes must always be different - return GetValidatorPowerRank(validator, pool) +// Get the validator owner address from ValBondedIndexKey +func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { + return IndexKey[1:] // remove prefix bytes } -// get the power of a validator -func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { +// get the validator by power index. power index is the key used in the power-store, +// and represents the relative power ranking of the validator. +// VALUE: validator owner address ([]byte) +func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { + // NOTE the address doesn't need to be stored because counter bytes must always be different + return getValidatorPowerRank(validator, pool) +} + +// get the power ranking of a validator +func getValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { power := validator.EquivalentBondedShares(pool) powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - // TODO ensure that the key will be a readable string.. probably should add seperators and have revokedBytes := make([]byte, 1) if validator.Revoked { revokedBytes[0] = byte(0x01) @@ -71,127 +78,162 @@ func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { revokedBytes[0] = byte(0x00) } - // TODO ensure that the key will be a readable string.. probably should add seperators and have // heightBytes and counterBytes represent strings like powerBytes does heightBytes := make([]byte, binary.MaxVarintLen64) binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) counterBytes := make([]byte, 2) binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(ValidatorsByPowerIndexKey, - append(revokedBytes, - append(powerBytes, - append(heightBytes, counterBytes...)...)...)...) + return append(append(append(append( + ValidatorsByPowerIndexKey, + revokedBytes...), + powerBytes...), + heightBytes...), + counterBytes...) } -// get the key for the accumulated update validators +// get the key for the accumulated update validators. +// VALUE: abci.Validator +// note records using these keys should never persist between blocks func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { return append(TendermintUpdatesKey, ownerAddr.Bytes()...) } //________________________________________________________________________________ -// get the key for delegator bond with validator -func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +// get the key for delegator bond with validator. +// VALUE: stake/types.Delegation +func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append(GetDelegationsKey(delegatorAddr), validatorAddr.Bytes()...) } // get the prefix for a delegator for all validators -func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(DelegationKey, res...) +func GetDelegationsKey(delegatorAddr sdk.Address) []byte { + return append(DelegationKey, delegatorAddr.Bytes()...) } //________________________________________________________________________________ -// get the key for an unbonding delegation -func GetUBDKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetUBDsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +// get the key for an unbonding delegation by delegator and validator addr. +// VALUE: stake/types.UnbondingDelegation +func GetUBDKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append( + GetUBDsKey(delegatorAddr.Bytes()), + validatorAddr.Bytes()...) } // get the index-key for an unbonding delegation, stored by validator-index -func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetUBDsByValIndexKey(validatorAddr, cdc), delegatorAddr.Bytes()...) +// VALUE: none (key rearrangement used) +func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address) []byte { + return append(GetUBDsByValIndexKey(validatorAddr), delegatorAddr.Bytes()...) +} + +// rearrange the ValIndexKey to get the UBDKey +func GetUBDKeyFromValIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 40 { + panic("unexpected key length") + } + valAddr := addrs[:20] + delAddr := addrs[20:] + return GetUBDKey(delAddr, valAddr) } //______________ // get the prefix for all unbonding delegations from a delegator -func GetUBDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(UnbondingDelegationKey, res...) +func GetUBDsKey(delegatorAddr sdk.Address) []byte { + return append(UnbondingDelegationKey, delegatorAddr.Bytes()...) } -// get the prefix keyspace for the indexs of unbonding delegations for a validator -func GetUBDsByValIndexKey(validatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorAddr) - return append(UnbondingDelegationByValIndexKey, res...) +// get the prefix keyspace for the indexes of unbonding delegations for a validator +func GetUBDsByValIndexKey(validatorAddr sdk.Address) []byte { + return append(UnbondingDelegationByValIndexKey, validatorAddr.Bytes()...) } //________________________________________________________________________________ // get the key for a redelegation +// VALUE: stake/types.RedelegationKey func GetREDKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsKey(delegatorAddr, cdc), - append( - validatorSrcAddr.Bytes(), - validatorDstAddr.Bytes()...)..., - ) + return append(append( + GetREDsKey(delegatorAddr.Bytes()), + validatorSrcAddr.Bytes()...), + validatorDstAddr.Bytes()...) } // get the index-key for a redelegation, stored by source-validator-index +// VALUE: none (key rearrangement used) func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsFromValSrcIndexKey(validatorSrcAddr, cdc), - append( - delegatorAddr.Bytes(), - validatorDstAddr.Bytes()...)..., - ) + return append(append( + GetREDsFromValSrcIndexKey(validatorSrcAddr), + delegatorAddr.Bytes()...), + validatorDstAddr.Bytes()...) } // get the index-key for a redelegation, stored by destination-validator-index +// VALUE: none (key rearrangement used) func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { - return append( - GetREDsToValDstIndexKey(validatorDstAddr, cdc), - append( - delegatorAddr.Bytes(), - validatorSrcAddr.Bytes()...)..., - ) + return append(append( + GetREDsToValDstIndexKey(validatorDstAddr), + delegatorAddr.Bytes()...), + validatorSrcAddr.Bytes()...) +} + +// rearrange the ValSrcIndexKey to get the REDKey +func GetREDKeyFromValSrcIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 60 { + panic("unexpected key length") + } + valSrcAddr := addrs[:20] + delAddr := addrs[20:40] + valDstAddr := addrs[40:] + + return GetREDKey(delAddr, valSrcAddr, valDstAddr) +} + +// rearrange the ValDstIndexKey to get the REDKey +func GetREDKeyFromValDstIndexKey(IndexKey []byte) []byte { + addrs := IndexKey[1:] // remove prefix bytes + if len(addrs) != 60 { + panic("unexpected key length") + } + valDstAddr := addrs[:20] + delAddr := addrs[20:40] + valSrcAddr := addrs[40:] + return GetREDKey(delAddr, valSrcAddr, valDstAddr) } //______________ // get the prefix keyspace for redelegations from a delegator -func GetREDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&delegatorAddr) - return append(RedelegationKey, res...) +func GetREDsKey(delegatorAddr sdk.Address) []byte { + return append(RedelegationKey, delegatorAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating away from a source validator -func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorSrcAddr) - return append(RedelegationByValSrcIndexKey, res...) +func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address) []byte { + return append(RedelegationByValSrcIndexKey, validatorSrcAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating towards a destination validator -func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { - res := cdc.MustMarshalBinary(&validatorDstAddr) - return append(RedelegationByValDstIndexKey, res...) +func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address) []byte { + return append(RedelegationByValDstIndexKey, validatorDstAddr.Bytes()...) } // get the prefix keyspace for all redelegations redelegating towards a destination validator // from a particular delegator func GetREDsByDelToValDstIndexKey(delegatorAddr sdk.Address, - validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + validatorDstAddr sdk.Address) []byte { return append( - GetREDsToValDstIndexKey(validatorDstAddr, cdc), + GetREDsToValDstIndexKey(validatorDstAddr), delegatorAddr.Bytes()...) } diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 55ad658a2d..bedbc15591 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -34,7 +34,7 @@ func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, va iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) i := int64(0) for ; iterator.Valid(); iterator.Next() { - address := iterator.Value() + address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -87,7 +87,7 @@ func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Add // iterate through the active validator set and perform the provided function func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr, k.cdc) + key := GetDelegationsKey(delAddr) iterator := sdk.KVStorePrefixIterator(store, key) i := int64(0) for ; iterator.Valid(); iterator.Next() { diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 0d556f7dc4..c2711788be 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -56,7 +56,7 @@ func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Valida // validator index func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) } // used in testing @@ -124,7 +124,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat if i > int(maxValidators-1) { panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") } - address := iterator.Value() + address := GetAddressFromValBondedIndexKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -362,7 +362,7 @@ func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { toKickOut := make(map[string]byte) iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) for ; iterator.Valid(); iterator.Next() { - ownerAddr := iterator.Value() + ownerAddr := GetAddressFromValBondedIndexKey(iterator.Key()) toKickOut[string(ownerAddr)] = 0 // set anything } iterator.Close() @@ -471,7 +471,7 @@ func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types. // save the now bonded validator record to the three referenced stores bzVal := k.cdc.MustMarshalBinary(validator) store.Set(GetValidatorKey(validator.Owner), bzVal) - store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), []byte{}) // add to accumulated changes for tendermint bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator()) From f1194019cd295c20cc4a00fc3aa023c962b9e32c Mon Sep 17 00:00:00 2001 From: Yukai Tu Date: Wed, 4 Jul 2018 11:29:05 +0800 Subject: [PATCH 3/4] Merge PR #1534: Check new rat decimal string length --- docs/clients/ledger.md | 29 +++++++++++++++++++++++++++++ types/rational.go | 4 ++++ types/rational_test.go | 1 + 3 files changed, 34 insertions(+) create mode 100644 docs/clients/ledger.md diff --git a/docs/clients/ledger.md b/docs/clients/ledger.md new file mode 100644 index 0000000000..9f6386715d --- /dev/null +++ b/docs/clients/ledger.md @@ -0,0 +1,29 @@ +# Ledger // Cosmos + +### Ledger Support for account keys + +`gaiacli` now supports derivation of account keys from a Ledger seed. To use this functionality you will need the following: + +- A running `gaiad` instance connected to the network you wish to use. +- A `gaiacli` instance configured to connect to your chosen `gaiad` instance. +- A LedgerNano with the `ledger-cosmos` app installed + * Install the Cosmos app onto your Ledger by following the instructions in the [`ledger-cosmos`](https://github.com/cosmos/ledger-cosmos/blob/master/docs/BUILD.md) repository. + * A production-ready version of this app will soon be included in the [Ledger Apps Store](https://www.ledgerwallet.com/apps) + +> **NOTE:** Cosmos keys are derived acording to the [BIP 44 Hierarchical Deterministic wallet spec](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki). For more information on Cosmos derivation paths [see the hd package](https://github.com/cosmos/cosmos-sdk/blob/develop/crypto/keys/hd/hdpath.go#L30). + +Once you have the Cosmos app installed on your Ledger, and the Ledger is accessible from the machine you are using `gaiacli` from you can create a new account key using the Ledger: + +```bash +$ gaiacli keys add {{ .Key.Name }} --ledger +NAME: TYPE: ADDRESS: PUBKEY: +{{ .Key.Name }} ledger cosmosaccaddr1aw64xxr80lwqqdk8u2xhlrkxqaxamkr3e2g943 cosmosaccpub1addwnpepqvhs678gh9aqrjc2tg2vezw86csnvgzqq530ujkunt5tkuc7lhjkz5mj629 +``` + +This key will only be accessible while the Ledger is plugged in and unlocked. To send some coins with this key, run the following: + +```bash +$ gaiacli send --name {{ .Key.Name }} --to {{ .Destination.AccAddr }} --chain-id=gaia-7000 +``` + +You will be asked to review and confirm the transaction on the Ledger. Once you do this you should see the result in the console! Now you can use your Ledger to manage your Atoms and Stake! diff --git a/types/rational.go b/types/rational.go index ab400868d4..24072fc09e 100644 --- a/types/rational.go +++ b/types/rational.go @@ -41,6 +41,10 @@ func NewRat(Numerator int64, Denominator ...int64) Rat { // precision is the number of values after the decimal point which should be read func NewRatFromDecimal(decimalStr string, prec int) (f Rat, err Error) { // first extract any negative symbol + if len(decimalStr) == 0 { + return f, ErrUnknownRequest("decimal string is empty") + } + neg := false if string(decimalStr[0]) == "-" { neg = true diff --git a/types/rational_test.go b/types/rational_test.go index 3215313e09..ecbc09e881 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -27,6 +27,7 @@ func TestNewFromDecimal(t *testing.T) { expErr bool exp Rat }{ + {"", true, Rat{}}, {"0", false, NewRat(0)}, {"1", false, NewRat(1)}, {"1.1", false, NewRat(11, 10)}, From 0b9e0f2afc2bf39257af101119727fd4afaafc99 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Wed, 4 Jul 2018 00:21:36 -0400 Subject: [PATCH 4/4] Merge PR #1492: Improve Module Test Coverage * Merge pull request #1492: Improve Module Test Coverage * Revert renaming of SignCheckDeliver [#1492] * Remove named fields from stake unit tests & fix comments [#1492] * update for tmlibs->tendermint/libs * Remove tmlibs dependency --- Gopkg.lock | 3 +- docs/core/app5.md | 2 +- examples/democoin/app/app_test.go | 4 +- examples/democoin/x/cool/app_test.go | 10 +- examples/democoin/x/pow/app_test.go | 2 +- x/auth/mock/app.go | 110 ------------ x/auth/mock/auth_app_test.go | 92 ---------- x/auth/mock/simulate_block.go | 110 ------------ x/bank/app_test.go | 2 +- x/bank/bench_test.go | 2 +- x/gov/msgs_test.go | 2 +- x/gov/test_common.go | 4 +- x/ibc/app_test.go | 2 +- x/mock/app.go | 168 ++++++++++++++++++ x/mock/app_test.go | 102 +++++++++++ x/mock/test_utils.go | 71 ++++++++ x/slashing/app_test.go | 10 +- x/stake/app_test.go | 118 ++++++------ x/stake/genesis.go | 34 ++-- x/stake/stake.go | 46 +++-- x/stake/types/delegation.go | 35 ++-- x/stake/types/delegation_test.go | 116 ++++++++++++ x/stake/types/errors.go | 27 ++- x/stake/types/msg_test.go | 32 +--- x/stake/types/params.go | 10 +- x/stake/types/params_test.go | 21 +++ x/stake/types/pool_test.go | 25 ++- x/stake/types/shares.go | 78 ++++---- x/stake/types/shares_test.go | 35 ++++ .../types/{test_common.go => test_utils.go} | 68 +++---- x/stake/types/validator.go | 98 ++++++---- x/stake/types/validator_test.go | 91 +++++++++- 32 files changed, 942 insertions(+), 588 deletions(-) delete mode 100644 x/auth/mock/app.go delete mode 100644 x/auth/mock/auth_app_test.go delete mode 100644 x/auth/mock/simulate_block.go create mode 100644 x/mock/app.go create mode 100644 x/mock/app_test.go create mode 100644 x/mock/test_utils.go create mode 100644 x/stake/types/delegation_test.go create mode 100644 x/stake/types/params_test.go create mode 100644 x/stake/types/shares_test.go rename x/stake/types/{test_common.go => test_utils.go} (84%) diff --git a/Gopkg.lock b/Gopkg.lock index a9b4a1f5cf..7a9b566965 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -137,6 +137,7 @@ ".", "hcl/ast", "hcl/parser", + "hcl/printer", "hcl/scanner", "hcl/strconv", "hcl/token", @@ -433,7 +434,7 @@ "netutil", "trace" ] - revision = "87b3feba568e144938625fc5d80ec92566c1a8fe" + revision = "ed29d75add3d7c4bf7ca65aac0c6df3d1420216f" [[projects]] branch = "master" diff --git a/docs/core/app5.md b/docs/core/app5.md index c6011f0425..39cc7a2637 100644 --- a/docs/core/app5.md +++ b/docs/core/app5.md @@ -59,7 +59,7 @@ func newApp(logger log.Logger, db dbm.DB) abci.Application { Note we utilize the popular [cobra library](https://github.com/spf13/cobra) for the CLI, in concert with the [viper library](https://github.com/spf13/library) -for managing configuration. See our [cli library](https://github.com/tendermint/tmlibs/blob/master/cli/setup.go) +for managing configuration. See our [cli library](https://github.com/tendermint/blob/master/tmlibs/cli/setup.go) for more details. TODO: compile and run the binary diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go index a642eff72d..60005713ef 100644 --- a/examples/democoin/app/app_test.go +++ b/examples/democoin/app/app_test.go @@ -4,14 +4,12 @@ import ( "os" "testing" - "github.com/stretchr/testify/require" - "github.com/cosmos/cosmos-sdk/examples/democoin/types" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" - + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" dbm "github.com/tendermint/tendermint/libs/db" diff --git a/examples/democoin/x/cool/app_test.go b/examples/democoin/x/cool/app_test.go index e93f6d99c5..df5786a043 100644 --- a/examples/democoin/x/cool/app_test.go +++ b/examples/democoin/x/cool/app_test.go @@ -3,15 +3,13 @@ package cool import ( "testing" - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" bank "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" ) var ( diff --git a/examples/democoin/x/pow/app_test.go b/examples/democoin/x/pow/app_test.go index d223a1f107..076dc20bc6 100644 --- a/examples/democoin/x/pow/app_test.go +++ b/examples/democoin/x/pow/app_test.go @@ -7,8 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" diff --git a/x/auth/mock/app.go b/x/auth/mock/app.go deleted file mode 100644 index 758b1efab8..0000000000 --- a/x/auth/mock/app.go +++ /dev/null @@ -1,110 +0,0 @@ -package mock - -import ( - "os" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" - - bam "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" -) - -// Extended ABCI application -type App struct { - *bam.BaseApp - Cdc *wire.Codec // public since the codec is passed into the module anyways. - KeyMain *sdk.KVStoreKey - KeyAccount *sdk.KVStoreKey - - // TODO: Abstract this out from not needing to be auth specifically - AccountMapper auth.AccountMapper - FeeCollectionKeeper auth.FeeCollectionKeeper - - GenesisAccounts []auth.Account -} - -// partially construct a new app on the memstore for module and genesis testing -func NewApp() *App { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") - db := dbm.NewMemDB() - - // create the cdc with some standard codecs - cdc := wire.NewCodec() - sdk.RegisterWire(cdc) - wire.RegisterCrypto(cdc) - auth.RegisterWire(cdc) - - // create your application object - app := &App{ - BaseApp: bam.NewBaseApp("mock", cdc, logger, db), - Cdc: cdc, - KeyMain: sdk.NewKVStoreKey("main"), - KeyAccount: sdk.NewKVStoreKey("acc"), - } - - // define the accountMapper - app.AccountMapper = auth.NewAccountMapper( - app.Cdc, - app.KeyAccount, // target store - &auth.BaseAccount{}, // prototype - ) - - // initialize the app, the chainers and blockers can be overwritten before calling complete setup - app.SetInitChainer(app.InitChainer) - - app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) - - return app -} - -// complete the application setup after the routes have been registered -func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { - newKeys = append(newKeys, app.KeyMain) - newKeys = append(newKeys, app.KeyAccount) - app.MountStoresIAVL(newKeys...) - err := app.LoadLatestVersion(app.KeyMain) - return err -} - -// custom logic for initialization -func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { - - // load the accounts - for _, genacc := range app.GenesisAccounts { - acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) - err := acc.SetCoins(genacc.GetCoins()) - if err != nil { - // TODO: Handle with #870 - panic(err) - } - app.AccountMapper.SetAccount(ctx, acc) - } - - return abci.ResponseInitChain{} -} - -// Generate genesis accounts loaded with coins, and returns their addresses, pubkeys, and privkeys -func CreateGenAccounts(numAccs int64, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.Address, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { - for i := int64(0); i < numAccs; i++ { - privKey := crypto.GenPrivKeyEd25519() - pubKey := privKey.PubKey() - addr := pubKey.Address() - - genAcc := &auth.BaseAccount{ - Address: addr, - Coins: genCoins, - } - - genAccs = append(genAccs, genAcc) - privKeys = append(privKeys, privKey) - pubKeys = append(pubKeys, pubKey) - addrs = append(addrs, addr) - } - - return -} diff --git a/x/auth/mock/auth_app_test.go b/x/auth/mock/auth_app_test.go deleted file mode 100644 index 3f340bbf90..0000000000 --- a/x/auth/mock/auth_app_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" -) - -// A mock transaction that has a validation which can fail. -type testMsg struct { - signers []sdk.Address - positiveNum int64 -} - -// TODO: Clean this up, make it public -const msgType = "testMsg" - -func (tx testMsg) Type() string { return msgType } -func (tx testMsg) GetMsg() sdk.Msg { return tx } -func (tx testMsg) GetMemo() string { return "" } -func (tx testMsg) GetSignBytes() []byte { return nil } -func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } -func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } -func (tx testMsg) ValidateBasic() sdk.Error { - if tx.positiveNum >= 0 { - return nil - } - return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") -} - -// test auth module messages - -var ( - priv1 = crypto.GenPrivKeyEd25519() - addr1 = priv1.PubKey().Address() - priv2 = crypto.GenPrivKeyEd25519() - addr2 = priv2.PubKey().Address() - - coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} - testMsg1 = testMsg{signers: []sdk.Address{addr1}, positiveNum: 1} -) - -// initialize the mock application for this module -func getMockApp(t *testing.T) *App { - mapp := NewApp() - - mapp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{})) - return mapp -} - -func TestMsgPrivKeys(t *testing.T) { - mapp := getMockApp(t) - mapp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) - - // Construct some genesis bytes to reflect basecoin/types/AppAccount - // Give 77 foocoin to the first key - coins := sdk.Coins{sdk.NewCoin("foocoin", 77)} - acc1 := &auth.BaseAccount{ - Address: addr1, - Coins: coins, - } - accs := []auth.Account{acc1} - - // Construct genesis state - SetGenesis(mapp, accs) - - // A checkTx context (true) - ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) - res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) - require.Equal(t, acc1, res1.(*auth.BaseAccount)) - - // Run a CheckDeliver - SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{0}, true, priv1) - - // signing a SendMsg with the wrong privKey should be an auth error - mapp.BeginBlock(abci.RequestBeginBlock{}) - tx := GenTx([]sdk.Msg{testMsg1}, []int64{0}, []int64{1}, priv2) - res := mapp.Deliver(tx) - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) - - // resigning the tx with the correct priv key should still work - res = SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{1}, true, priv1) - - require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), res.Code, res.Log) -} diff --git a/x/auth/mock/simulate_block.go b/x/auth/mock/simulate_block.go deleted file mode 100644 index f85fdf1803..0000000000 --- a/x/auth/mock/simulate_block.go +++ /dev/null @@ -1,110 +0,0 @@ -package mock - -import ( - "testing" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - - abci "github.com/tendermint/tendermint/abci/types" -) - -var chainID = "" // TODO - -// set the mock app genesis -func SetGenesis(app *App, accs []auth.Account) { - - // pass the accounts in via the application (lazy) instead of through RequestInitChain - app.GenesisAccounts = accs - - app.InitChain(abci.RequestInitChain{}) - app.Commit() -} - -// check an account balance -func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { - ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) - res := app.AccountMapper.GetAccount(ctxCheck, addr) - require.Equal(t, exp, res.GetCoins()) -} - -// generate a signed transaction -func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { - - // make the transaction free - fee := auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", 0)}, - 100000, - } - - sigs := make([]auth.StdSignature, len(priv)) - memo := "testmemotestmemo" - for i, p := range priv { - sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo)) - if err != nil { - panic(err) - } - sigs[i] = auth.StdSignature{ - PubKey: p.PubKey(), - Signature: sig, - AccountNumber: accnums[i], - Sequence: seq[i], - } - } - return auth.NewStdTx(msgs, fee, sigs, memo) -} - -// generate a set of signed transactions a msg, that differ only by having the -// sequence numbers incremented between every transaction. -func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKeyEd25519) []auth.StdTx { - txs := make([]auth.StdTx, numToGenerate, numToGenerate) - for i := 0; i < numToGenerate; i++ { - txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) - incrementAllSequenceNumbers(initSeqNums) - } - return txs -} - -func incrementAllSequenceNumbers(initSeqNums []int64) { - for i := 0; i < len(initSeqNums); i++ { - initSeqNums[i]++ - } -} - -// check a transaction result -func SignCheck(app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result { - tx := GenTx(msgs, accnums, seq, priv...) - res := app.Check(tx) - return res -} - -// simulate a block -func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) sdk.Result { - - // Sign the tx - tx := GenTx(msgs, accnums, seq, priv...) - - // Run a Check - res := app.Check(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - - // Simulate a Block - app.BeginBlock(abci.RequestBeginBlock{}) - res = app.Deliver(tx) - if expPass { - require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) - } else { - require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) - } - app.EndBlock(abci.RequestEndBlock{}) - - app.Commit() - return res -} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 72ea312a9a..425e9aa2d9 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index dce4d8fa95..be8319f9fd 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -5,7 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/gov/msgs_test.go b/x/gov/msgs_test.go index e9be4ded3d..d0a6292997 100644 --- a/x/gov/msgs_test.go +++ b/x/gov/msgs_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/mock" + "github.com/cosmos/cosmos-sdk/x/mock" ) var ( diff --git a/x/gov/test_common.go b/x/gov/test_common.go index 743c5b6392..70e47e86d7 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -12,13 +12,13 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/stake" ) // initialize the mock application for this module -func getMockApp(t *testing.T, numGenAccs int64) (*mock.App, Keeper, stake.Keeper, []sdk.Address, []crypto.PubKey, []crypto.PrivKey) { +func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, []sdk.Address, []crypto.PubKey, []crypto.PrivKey) { mapp := mock.NewApp() stake.RegisterWire(mapp.Cdc) diff --git a/x/ibc/app_test.go b/x/ibc/app_test.go index 1540bf2ed8..3671cd3e19 100644 --- a/x/ibc/app_test.go +++ b/x/ibc/app_test.go @@ -7,8 +7,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" diff --git a/x/mock/app.go b/x/mock/app.go new file mode 100644 index 0000000000..e16a322688 --- /dev/null +++ b/x/mock/app.go @@ -0,0 +1,168 @@ +package mock + +import ( + "os" + + bam "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +const chainID = "" + +// App extends an ABCI application. +type App struct { + *bam.BaseApp + Cdc *wire.Codec // Cdc is public since the codec is passed into the module anyways + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + + // TODO: Abstract this out from not needing to be auth specifically + AccountMapper auth.AccountMapper + FeeCollectionKeeper auth.FeeCollectionKeeper + + GenesisAccounts []auth.Account +} + +// NewApp partially constructs a new app on the memstore for module and genesis +// testing. +func NewApp() *App { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + + // Create the cdc with some standard codecs + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + auth.RegisterWire(cdc) + + // Create your application object + app := &App{ + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + } + + // Define the accountMapper + app.AccountMapper = auth.NewAccountMapper( + app.Cdc, + app.KeyAccount, + &auth.BaseAccount{}, + ) + + // Initialize the app. The chainers and blockers can be overwritten before + // calling complete setup. + app.SetInitChainer(app.InitChainer) + app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + + return app +} + +// CompleteSetup completes the application setup after the routes have been +// registered. +func (app *App) CompleteSetup(newKeys []*sdk.KVStoreKey) error { + newKeys = append(newKeys, app.KeyMain) + newKeys = append(newKeys, app.KeyAccount) + + app.MountStoresIAVL(newKeys...) + err := app.LoadLatestVersion(app.KeyMain) + + return err +} + +// InitChainer performs custom logic for initialization. +func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + // Load the genesis accounts + for _, genacc := range app.GenesisAccounts { + acc := app.AccountMapper.NewAccountWithAddress(ctx, genacc.GetAddress()) + acc.SetCoins(genacc.GetCoins()) + app.AccountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} +} + +// CreateGenAccounts generates genesis accounts loaded with coins, and returns +// their addresses, pubkeys, and privkeys. +func CreateGenAccounts(numAccs int, genCoins sdk.Coins) (genAccs []auth.Account, addrs []sdk.Address, pubKeys []crypto.PubKey, privKeys []crypto.PrivKey) { + for i := 0; i < numAccs; i++ { + privKey := crypto.GenPrivKeyEd25519() + pubKey := privKey.PubKey() + addr := pubKey.Address() + + genAcc := &auth.BaseAccount{ + Address: addr, + Coins: genCoins, + } + + genAccs = append(genAccs, genAcc) + privKeys = append(privKeys, privKey) + pubKeys = append(pubKeys, pubKey) + addrs = append(addrs, addr) + } + + return +} + +// SetGenesis sets the mock app genesis accounts. +func SetGenesis(app *App, accs []auth.Account) { + // Pass the accounts in via the application (lazy) instead of through + // RequestInitChain. + app.GenesisAccounts = accs + + app.InitChain(abci.RequestInitChain{}) + app.Commit() +} + +// GenTx generates a signed mock transaction. +func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { + // Make the transaction free + fee := auth.StdFee{ + sdk.Coins{sdk.NewCoin("foocoin", 0)}, + 100000, + } + + sigs := make([]auth.StdSignature, len(priv)) + memo := "testmemotestmemo" + + for i, p := range priv { + sig, err := p.Sign(auth.StdSignBytes(chainID, accnums[i], seq[i], fee, msgs, memo)) + if err != nil { + panic(err) + } + + sigs[i] = auth.StdSignature{ + PubKey: p.PubKey(), + Signature: sig, + AccountNumber: accnums[i], + Sequence: seq[i], + } + } + + return auth.NewStdTx(msgs, fee, sigs, memo) +} + +// GenSequenceOfTxs generates a set of signed transactions of messages, such +// that they differ only by having the sequence numbers incremented between +// every transaction. +func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKey) []auth.StdTx { + txs := make([]auth.StdTx, numToGenerate, numToGenerate) + for i := 0; i < numToGenerate; i++ { + txs[i] = GenTx(msgs, accnums, initSeqNums, priv...) + incrementAllSequenceNumbers(initSeqNums) + } + + return txs +} + +func incrementAllSequenceNumbers(initSeqNums []int64) { + for i := 0; i < len(initSeqNums); i++ { + initSeqNums[i]++ + } +} diff --git a/x/mock/app_test.go b/x/mock/app_test.go new file mode 100644 index 0000000000..7ee82474ce --- /dev/null +++ b/x/mock/app_test.go @@ -0,0 +1,102 @@ +package mock + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +const msgType = "testMsg" + +var ( + numAccts = 2 + genCoins = sdk.Coins{sdk.NewCoin("foocoin", 77)} + accs, addrs, pubKeys, privKeys = CreateGenAccounts(numAccts, genCoins) +) + +// testMsg is a mock transaction that has a validation which can fail. +type testMsg struct { + signers []sdk.Address + positiveNum int64 +} + +func (tx testMsg) Type() string { return msgType } +func (tx testMsg) GetMsg() sdk.Msg { return tx } +func (tx testMsg) GetMemo() string { return "" } +func (tx testMsg) GetSignBytes() []byte { return nil } +func (tx testMsg) GetSigners() []sdk.Address { return tx.signers } +func (tx testMsg) GetSignatures() []auth.StdSignature { return nil } +func (tx testMsg) ValidateBasic() sdk.Error { + if tx.positiveNum >= 0 { + return nil + } + return sdk.ErrTxDecode("positiveNum should be a non-negative integer.") +} + +// getMockApp returns an initialized mock application. +func getMockApp(t *testing.T) *App { + mApp := NewApp() + + mApp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return }) + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{})) + + return mApp +} + +func TestCheckAndDeliverGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + ctxCheck := mApp.BaseApp.NewContext(true, abci.Header{}) + + msg := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: 1} + + acct := mApp.AccountMapper.GetAccount(ctxCheck, addrs[0]) + require.Equal(t, accs[0], acct.(*auth.BaseAccount)) + + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + // Signing a tx with the wrong privKey should result in an auth error + res := SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[1].GetAccountNumber()}, []int64{accs[1].GetSequence() + 1}, + false, privKeys[1], + ) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // Resigning the tx with the correct privKey should result in an OK result + SignCheckDeliver( + t, mApp.BaseApp, []sdk.Msg{msg}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence() + 1}, + true, privKeys[0], + ) +} + +func TestCheckGenTx(t *testing.T) { + mApp := getMockApp(t) + mApp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil) + + SetGenesis(mApp, accs) + + msg1 := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: 1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg1}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + true, privKeys[0], + ) + + msg2 := testMsg{signers: []sdk.Address{addrs[0]}, positiveNum: -1} + CheckGenTx( + t, mApp.BaseApp, []sdk.Msg{msg2}, + []int64{accs[0].GetAccountNumber()}, []int64{accs[0].GetSequence()}, + false, privKeys[0], + ) +} diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go new file mode 100644 index 0000000000..f9620c1403 --- /dev/null +++ b/x/mock/test_utils.go @@ -0,0 +1,71 @@ +package mock + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// CheckBalance checks the balance of an account. +func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { + ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) + res := app.AccountMapper.GetAccount(ctxCheck, addr) + + require.Equal(t, exp, res.GetCoins()) +} + +// CheckGenTx checks a generated signed transaction. The result of the check is +// compared against the parameter 'expPass'. A test assertion is made using the +// parameter 'expPass' against the result. A corresponding result is returned. +func CheckGenTx( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + return res +} + +// SignCheckDeliver checks a generated signed transaction and simulates a +// block commitment with the given transaction. A test assertion is made using +// the parameter 'expPass' against the result. A corresponding result is +// returned. +func SignCheckDeliver( + t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accNums []int64, + seq []int64, expPass bool, priv ...crypto.PrivKey, +) sdk.Result { + tx := GenTx(msgs, accNums, seq, priv...) + res := app.Check(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + // Simulate a sending a transaction and committing a block + app.BeginBlock(abci.RequestBeginBlock{}) + res = app.Deliver(tx) + + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + app.EndBlock(abci.RequestEndBlock{}) + app.Commit() + + return res +} diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index a64d3eb0f1..d6fb0d9fe8 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -5,11 +5,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/require" - + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -103,10 +102,9 @@ func TestSlashingMsgs(t *testing.T) { require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) unrevokeMsg := MsgUnrevoke{ValidatorAddr: validator.PubKey.Address()} - // no signing info yet checkValidatorSigningInfo(t, mapp, keeper, addr1, false) - // unrevoke should fail with validator not revoked - res := mock.SignCheck(mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, priv1) + // unrevoke should fail with unknown validator + res := mock.CheckGenTx(t, mapp.BaseApp, []sdk.Msg{unrevokeMsg}, []int64{0}, []int64{1}, false, priv1) require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotRevoked), res.Code) } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index d4fc5bdf00..02dd091341 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -5,11 +5,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/mock" "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/stretchr/testify/assert" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -22,81 +20,86 @@ var ( addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() priv4 = crypto.GenPrivKeyEd25519() addr4 = priv4.PubKey().Address() - coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} - fee = auth.StdFee{ - sdk.Coins{{"foocoin", sdk.NewInt(0)}}, - 100000, - } + coins = sdk.NewCoin("foocoin", 10) + fee = auth.StdFee{sdk.Coins{sdk.NewCoin("foocoin", 0)}, 100000} ) -// initialize the mock application for this module +// getMockApp returns an initialized mock application for this module. func getMockApp(t *testing.T) (*mock.App, Keeper) { - mapp := mock.NewApp() + mApp := mock.NewApp() + + RegisterWire(mApp.Cdc) - RegisterWire(mapp.Cdc) keyStake := sdk.NewKVStoreKey("stake") - coinKeeper := bank.NewKeeper(mapp.AccountMapper) - keeper := NewKeeper(mapp.Cdc, keyStake, coinKeeper, mapp.RegisterCodespace(DefaultCodespace)) - mapp.Router().AddRoute("stake", NewHandler(keeper)) + coinKeeper := bank.NewKeeper(mApp.AccountMapper) + keeper := NewKeeper(mApp.Cdc, keyStake, coinKeeper, mApp.RegisterCodespace(DefaultCodespace)) - mapp.SetEndBlocker(getEndBlocker(keeper)) - mapp.SetInitChainer(getInitChainer(mapp, keeper)) + mApp.Router().AddRoute("stake", NewHandler(keeper)) + mApp.SetEndBlocker(getEndBlocker(keeper)) + mApp.SetInitChainer(getInitChainer(mApp, keeper)) - require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) - return mapp, keeper + require.NoError(t, mApp.CompleteSetup([]*sdk.KVStoreKey{keyStake})) + return mApp, keeper } -// stake endblocker +// getEndBlocker returns a stake endblocker. func getEndBlocker(keeper Keeper) sdk.EndBlocker { return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates := EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, } } } -// overwrite the mock init chainer +// getInitChainer initializes the chainer of the mock app and sets the genesis +// state. It returns an empty ResponseInitChain. func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) + stakeGenesis := DefaultGenesisState() stakeGenesis.Pool.LooseTokens = 100000 + InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } } -//__________________________________________________________________________________________ - -func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, - addr sdk.Address, expFound bool) Validator { - +func checkValidator( + t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.Address, expFound bool, +) Validator { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) validator, found := keeper.GetValidator(ctxCheck, addr1) + require.Equal(t, expFound, found) return validator } -func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, - validatorAddr sdk.Address, expFound bool, expShares sdk.Rat) { - +func checkDelegation( + t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, + validatorAddr sdk.Address, expFound bool, expShares sdk.Rat, +) { ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) if expFound { require.True(t, found) - assert.True(sdk.RatEq(t, expShares, delegation.Shares)) + require.True(sdk.RatEq(t, expShares, delegation.Shares)) + return } + require.False(t, found) } func TestStakeMsgs(t *testing.T) { - mapp, keeper := getMockApp(t) + mApp, keeper := getMockApp(t) - genCoin := sdk.Coin{"steak", sdk.NewInt(42)} - bondCoin := sdk.Coin{"steak", sdk.NewInt(10)} + genCoin := sdk.NewCoin("steak", 42) + bondCoin := sdk.NewCoin("steak", 10) acc1 := &auth.BaseAccount{ Address: addr1, @@ -108,56 +111,51 @@ func TestStakeMsgs(t *testing.T) { } accs := []auth.Account{acc1, acc2} - mock.SetGenesis(mapp, accs) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin}) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) - - //////////////////// - // Create Validator + mock.SetGenesis(mApp, accs) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + // create validator description := NewDescription("foo_moniker", "", "", "") createValidatorMsg := NewMsgCreateValidator( addr1, priv1.PubKey(), bondCoin, description, ) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) - mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) - mapp.BeginBlock(abci.RequestBeginBlock{}) - validator := checkValidator(t, mapp, keeper, addr1, true) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, priv1) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, addr1, validator.Owner) require.Equal(t, sdk.Bonded, validator.Status()) require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) // check the bond that should have been created as well - checkDelegation(t, mapp, keeper, addr1, addr1, true, sdk.NewRat(10)) - - //////////////////// - // Edit Validator + checkDelegation(t, mApp, keeper, addr1, addr1, true, sdk.NewRat(10)) + // edit the validator description = NewDescription("bar_moniker", "", "", "") editValidatorMsg := NewMsgEditValidator(addr1, description) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{1}, true, priv1) - validator = checkValidator(t, mapp, keeper, addr1, true) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{1}, true, priv1) + validator = checkValidator(t, mApp, keeper, addr1, true) require.Equal(t, description, validator.Description) - //////////////////// - // Delegate - - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + // delegate + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) delegateMsg := NewMsgDelegate(addr2, addr1, bondCoin) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{0}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) - checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) - //////////////////// - // Begin Unbonding + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{1}, []int64{0}, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, addr1, true, sdk.NewRat(10)) + // begin unbonding beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10)) - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) // delegation should exist anymore - checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) + checkDelegation(t, mApp, keeper, addr2, addr1, false, sdk.Rat{}) // balance should be the same because bonding not yet complete - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) } diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 55dd06f76f..1116a4748d 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -1,50 +1,58 @@ package stake import ( - tmtypes "github.com/tendermint/tendermint/types" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/stake/types" + tmtypes "github.com/tendermint/tendermint/types" ) -// InitGenesis - store genesis parameters +// InitGenesis sets the pool and parameters for the provided keeper and +// initializes the IntraTxCounter. For each validator in data, it sets that +// validator in the keeper along with manually setting the indexes. In +// addition, it also sets any delegations found in data. Finally, it updates +// the bonded validators. func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { keeper.SetPool(ctx, data.Pool) keeper.SetNewParams(ctx, data.Params) keeper.InitIntraTxCounter(ctx) - for _, validator := range data.Validators { - // set validator + for _, validator := range data.Validators { keeper.SetValidator(ctx, validator) - // manually set indexes for the first time + // Manually set indexes for the first time keeper.SetValidatorByPubKeyIndex(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) + if validator.Status() == sdk.Bonded { keeper.SetValidatorBondedIndex(ctx, validator) } } + for _, bond := range data.Bonds { keeper.SetDelegation(ctx, bond) } + keeper.UpdateBondedValidatorsFull(ctx) } -// WriteGenesis - output genesis parameters +// WriteGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, params, validators, and bonds found in +// the keeper. func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) validators := keeper.GetAllValidators(ctx) bonds := keeper.GetAllDelegations(ctx) + return types.GenesisState{ - pool, - params, - validators, - bonds, + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, } } -// WriteValidators - output current validator set +// WriteValidators returns a slice of bonded genesis validators. func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ @@ -52,7 +60,9 @@ func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisVali Power: validator.GetPower().RoundInt64(), Name: validator.GetMoniker(), }) + return false }) + return } diff --git a/x/stake/stake.go b/x/stake/stake.go index 5e4356b814..869b838ffa 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -7,30 +7,29 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// keeper -type Keeper = keeper.Keeper - -var NewKeeper = keeper.NewKeeper - -// types -type Validator = types.Validator -type Description = types.Description -type Delegation = types.Delegation -type UnbondingDelegation = types.UnbondingDelegation -type Redelegation = types.Redelegation -type Params = types.Params -type Pool = types.Pool -type PoolShares = types.PoolShares -type MsgCreateValidator = types.MsgCreateValidator -type MsgEditValidator = types.MsgEditValidator -type MsgDelegate = types.MsgDelegate -type MsgBeginUnbonding = types.MsgBeginUnbonding -type MsgCompleteUnbonding = types.MsgCompleteUnbonding -type MsgBeginRedelegate = types.MsgBeginRedelegate -type MsgCompleteRedelegate = types.MsgCompleteRedelegate -type GenesisState = types.GenesisState +type ( + Keeper = keeper.Keeper + Validator = types.Validator + Description = types.Description + Delegation = types.Delegation + UnbondingDelegation = types.UnbondingDelegation + Redelegation = types.Redelegation + Params = types.Params + Pool = types.Pool + PoolShares = types.PoolShares + MsgCreateValidator = types.MsgCreateValidator + MsgEditValidator = types.MsgEditValidator + MsgDelegate = types.MsgDelegate + MsgBeginUnbonding = types.MsgBeginUnbonding + MsgCompleteUnbonding = types.MsgCompleteUnbonding + MsgBeginRedelegate = types.MsgBeginRedelegate + MsgCompleteRedelegate = types.MsgCompleteRedelegate + GenesisState = types.GenesisState +) var ( + NewKeeper = keeper.NewKeeper + GetValidatorKey = keeper.GetValidatorKey GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey @@ -72,7 +71,6 @@ var ( DefaultGenesisState = types.DefaultGenesisState RegisterWire = types.RegisterWire - // messages NewMsgCreateValidator = types.NewMsgCreateValidator NewMsgEditValidator = types.NewMsgEditValidator NewMsgDelegate = types.NewMsgDelegate @@ -82,7 +80,6 @@ var ( NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate ) -// errors const ( DefaultCodespace = types.DefaultCodespace CodeInvalidValidator = types.CodeInvalidValidator @@ -126,7 +123,6 @@ var ( ErrMissingSignature = types.ErrMissingSignature ) -// tags var ( ActionCreateValidator = tags.ActionCreateValidator ActionEditValidator = tags.ActionEditValidator diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index eb38a50a7a..a7d0a1f8c5 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -17,7 +17,7 @@ type Delegation struct { Height int64 `json:"height"` // Last height bond updated } -// two are equal +// Equal returns a boolean determining if two Delegation types are identical. func (d Delegation) Equal(d2 Delegation) bool { return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && @@ -33,16 +33,20 @@ func (d Delegation) GetDelegator() sdk.Address { return d.DelegatorAddr } func (d Delegation) GetValidator() sdk.Address { return d.ValidatorAddr } func (d Delegation) GetBondShares() sdk.Rat { return d.Shares } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// Delegation. An error is returned if the Delegation's delegator or validator +// addresses cannot be Bech32 encoded. func (d Delegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) if err != nil { return "", err } + resp := "Delegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Validator: %s\n", bechVal) @@ -50,12 +54,9 @@ func (d Delegation) HumanReadableString() (string, error) { resp += fmt.Sprintf("Height: %d", d.Height) return resp, nil - } -//__________________________________________________________________ - -// element stored to represent the passive unbonding queue +// UnbondingDelegation reflects a delegation's passive unbonding queue. type UnbondingDelegation struct { DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator ValidatorAddr sdk.Address `json:"validator_addr"` // validator unbonding from owner addr @@ -65,23 +66,28 @@ type UnbondingDelegation struct { Balance sdk.Coin `json:"balance"` // atoms to receive at completion } -// nolint +// Equal returns a boolean determining if two UnbondingDelegation types are +// identical. func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { bz1 := MsgCdc.MustMarshalBinary(&d) bz2 := MsgCdc.MustMarshalBinary(&d2) return bytes.Equal(bz1, bz2) } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of an +// UnbondingDelegation. An error is returned if the UnbondingDelegation's +// delegator or validator addresses cannot be Bech32 encoded. func (d UnbondingDelegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) if err != nil { return "", err } + resp := "Unbonding Delegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Validator: %s\n", bechVal) @@ -93,9 +99,7 @@ func (d UnbondingDelegation) HumanReadableString() (string, error) { } -//__________________________________________________________________ - -// element stored to represent the passive redelegation queue +// Redelegation reflects a delegation's passive re-delegation queue. type Redelegation struct { DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` // validator redelegation source owner addr @@ -108,27 +112,32 @@ type Redelegation struct { SharesDst sdk.Rat `json:"shares_dst"` // amount of destination shares redelegating } -// nolint +// Equal returns a boolean determining if two Redelegation types are identical. func (d Redelegation) Equal(d2 Redelegation) bool { bz1 := MsgCdc.MustMarshalBinary(&d) bz2 := MsgCdc.MustMarshalBinary(&d2) return bytes.Equal(bz1, bz2) } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// Redelegation. An error is returned if the UnbondingDelegation's delegator or +// validator addresses cannot be Bech32 encoded. func (d Redelegation) HumanReadableString() (string, error) { bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) if err != nil { return "", err } + bechValSrc, err := sdk.Bech32ifyAcc(d.ValidatorSrcAddr) if err != nil { return "", err } + bechValDst, err := sdk.Bech32ifyAcc(d.ValidatorDstAddr) if err != nil { return "", err } + resp := "Redelegation \n" resp += fmt.Sprintf("Delegator: %s\n", bechAcc) resp += fmt.Sprintf("Source Validator: %s\n", bechValSrc) diff --git a/x/stake/types/delegation_test.go b/x/stake/types/delegation_test.go new file mode 100644 index 0000000000..640f5d9791 --- /dev/null +++ b/x/stake/types/delegation_test.go @@ -0,0 +1,116 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestDelegationEqual(t *testing.T) { + d1 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + d2 := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + ok := d1.Equal(d2) + require.True(t, ok) + + d2.ValidatorAddr = addr3 + d2.Shares = sdk.NewRat(200) + + ok = d1.Equal(d2) + require.False(t, ok) +} + +func TestDelegationHumanReadableString(t *testing.T) { + d := Delegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + Shares: sdk.NewRat(100), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := d.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestUnbondingDelegationEqual(t *testing.T) { + ud1 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + ud2 := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + ok := ud1.Equal(ud2) + require.True(t, ok) + + ud2.ValidatorAddr = addr3 + ud2.MinTime = 20 * 20 * 2 + + ok = ud1.Equal(ud2) + require.False(t, ok) +} + +func TestUnbondingDelegationHumanReadableString(t *testing.T) { + ud := UnbondingDelegation{ + DelegatorAddr: addr1, + ValidatorAddr: addr2, + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := ud.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestRedelegationEqual(t *testing.T) { + r1 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + r2 := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + + ok := r1.Equal(r2) + require.True(t, ok) + + r2.SharesDst = sdk.NewRat(10) + r2.SharesSrc = sdk.NewRat(20) + r2.MinTime = 20 * 20 * 2 + + ok = r1.Equal(r2) + require.False(t, ok) +} + +func TestRedelegationHumanReadableString(t *testing.T) { + r := Redelegation{ + DelegatorAddr: addr1, + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + SharesDst: sdk.NewRat(10), + SharesSrc: sdk.NewRat(20), + } + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := r.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 2914741f45..09ed2d3696 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -25,97 +25,118 @@ const ( func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") } + func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") } + func ErrValidatorAlreadyExists(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator") } + func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") } + func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator") } + func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error { msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max) return sdk.NewError(codespace, CodeInvalidValidator, msg) } + func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive") } + func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") } -// delegation func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") } + func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") } + func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0") } + func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair") } + func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address") } + func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation") } + func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares") } + func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator") } + func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares)) } + func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0") } + func ErrBadSharesPrecision(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("shares denominator must be < %s, try reducing the number of decimal points", maximumBondingRationalDenominator.String()), ) } + func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1") } -// redelegation func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error { msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v", operation, descriptor, got, min) return sdk.NewError(codespace, CodeUnauthorized, msg) } + func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") } + func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") } + func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") } + func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") } -// messages func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") } + func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided") } + func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") } diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go index f3d8bc686f..1a854a4926 100644 --- a/x/stake/types/msg_test.go +++ b/x/stake/types/msg_test.go @@ -10,9 +10,9 @@ import ( ) var ( - coinPos = sdk.Coin{"steak", sdk.NewInt(1000)} - coinZero = sdk.Coin{"steak", sdk.NewInt(0)} - coinNeg = sdk.Coin{"steak", sdk.NewInt(-10000)} + coinPos = sdk.NewCoin("steak", 1000) + coinZero = sdk.NewCoin("steak", 0) + coinNeg = sdk.NewCoin("steak", -10000) ) // test ValidateBasic for MsgCreateValidator @@ -197,29 +197,3 @@ func TestMsgCompleteUnbonding(t *testing.T) { } } } - -// TODO introduce with go-amino -//func TestSerializeMsg(t *testing.T) { - -//// make sure all types construct properly -//bondAmt := 1234321 -//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - -//tests := []struct { -//tx sdk.Msg -//}{ -//{NewMsgCreateValidator(addr1, pk1, bond, Description{})}, -//{NewMsgEditValidator(addr1, Description{})}, -//{NewMsgDelegate(addr1, addr2, bond)}, -//{NewMsgUnbond(addr1, addr2, strconv.Itoa(bondAmt))}, -//} - -//for i, tc := range tests { -//var tx sdk.Tx -//bs := wire.BinaryBytes(tc.tx) -//err := wire.ReadBinaryBytes(bs, &tx) -//if require.NoError(t, err, "%d", i) { -//require.Equal(t, tc.tx, tx, "%d", i) -//} -//} -//} diff --git a/x/stake/types/params.go b/x/stake/types/params.go index 8c1b897f2e..5a7dd6ef55 100644 --- a/x/stake/types/params.go +++ b/x/stake/types/params.go @@ -6,6 +6,10 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// defaultUnbondingTime reflects three weeks in seconds as the default +// unbonding time. +const defaultUnbondingTime int64 = 60 * 60 * 24 * 3 + // Params defines the high level settings for staking type Params struct { InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate @@ -19,21 +23,21 @@ type Params struct { BondDenom string `json:"bond_denom"` // bondable coin denomination } -// nolint +// Equal returns a boolean determining if two Param types are identical. func (p Params) Equal(p2 Params) bool { bz1 := MsgCdc.MustMarshalBinary(&p) bz2 := MsgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } -// default params +// DefaultParams returns a default set of parameters. func DefaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), - UnbondingTime: 60 * 60 * 24 * 3, // 3 weeks in seconds + UnbondingTime: defaultUnbondingTime, MaxValidators: 100, BondDenom: "steak", } diff --git a/x/stake/types/params_test.go b/x/stake/types/params_test.go new file mode 100644 index 0000000000..c18700ef43 --- /dev/null +++ b/x/stake/types/params_test.go @@ -0,0 +1,21 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParamsEqual(t *testing.T) { + p1 := DefaultParams() + p2 := DefaultParams() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.UnbondingTime = 60 * 60 * 24 * 2 + p2.BondDenom = "soup" + + ok = p1.Equal(p2) + require.False(t, ok) +} diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go index a62c69d468..3a52646f67 100644 --- a/x/stake/types/pool_test.go +++ b/x/stake/types/pool_test.go @@ -3,11 +3,24 @@ package types import ( "testing" - "github.com/stretchr/testify/require" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" ) +func TestPoolEqual(t *testing.T) { + p1 := InitialPool() + p2 := InitialPool() + + ok := p1.Equal(p2) + require.True(t, ok) + + p2.BondedTokens = 3 + p2.BondedShares = sdk.NewRat(10) + + ok = p1.Equal(p2) + require.False(t, ok) +} + func TestBondedRatio(t *testing.T) { pool := InitialPool() pool.LooseTokens = 1 @@ -62,10 +75,10 @@ func TestUnbondedShareExRate(t *testing.T) { } func TestAddTokensBonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensBonded(10) require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) @@ -78,10 +91,10 @@ func TestAddTokensBonded(t *testing.T) { } func TestRemoveSharesBonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) require.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) @@ -94,10 +107,10 @@ func TestRemoveSharesBonded(t *testing.T) { } func TestAddTokensUnbonded(t *testing.T) { - poolA := InitialPool() poolA.LooseTokens = 10 require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensUnbonded(10) require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) @@ -110,11 +123,11 @@ func TestAddTokensUnbonded(t *testing.T) { } func TestRemoveSharesUnbonded(t *testing.T) { - poolA := InitialPool() poolA.UnbondedTokens = 10 poolA.UnbondedShares = sdk.NewRat(10) require.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) require.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) diff --git a/x/stake/types/shares.go b/x/stake/types/shares.go index 09301d0db6..5a2cb2be6c 100644 --- a/x/stake/types/shares.go +++ b/x/stake/types/shares.go @@ -4,18 +4,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// pool shares held by a validator +// PoolShares reflects the shares of a validator in a pool. type PoolShares struct { Status sdk.BondStatus `json:"status"` - Amount sdk.Rat `json:"amount"` // total shares of type ShareKind + Amount sdk.Rat `json:"amount"` } -// only the vitals - does not check bond height of IntraTxCounter +// Equal returns a boolean determining of two PoolShares are identical. func (s PoolShares) Equal(s2 PoolShares) bool { return s.Status == s2.Status && s.Amount.Equal(s2.Amount) } +// NewUnbondedShares returns a new PoolShares with a specified unbonded amount. func NewUnbondedShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Unbonded, @@ -23,6 +24,8 @@ func NewUnbondedShares(amount sdk.Rat) PoolShares { } } +// NewUnbondingShares returns a new PoolShares with a specified unbonding +// amount. func NewUnbondingShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Unbonding, @@ -30,6 +33,7 @@ func NewUnbondingShares(amount sdk.Rat) PoolShares { } } +// NewBondedShares returns a new PoolSahres with a specified bonding amount. func NewBondedShares(amount sdk.Rat) PoolShares { return PoolShares{ Status: sdk.Bonded, @@ -37,9 +41,7 @@ func NewBondedShares(amount sdk.Rat) PoolShares { } } -//_________________________________________________________________________________________________________ - -// amount of unbonded shares +// Unbonded returns the amount of unbonded shares. func (s PoolShares) Unbonded() sdk.Rat { if s.Status == sdk.Unbonded { return s.Amount @@ -47,7 +49,7 @@ func (s PoolShares) Unbonded() sdk.Rat { return sdk.ZeroRat() } -// amount of unbonding shares +// Unbonding returns the amount of unbonding shares. func (s PoolShares) Unbonding() sdk.Rat { if s.Status == sdk.Unbonding { return s.Amount @@ -55,7 +57,7 @@ func (s PoolShares) Unbonding() sdk.Rat { return sdk.ZeroRat() } -// amount of bonded shares +// Bonded returns amount of bonded shares. func (s PoolShares) Bonded() sdk.Rat { if s.Status == sdk.Bonded { return s.Amount @@ -63,64 +65,80 @@ func (s PoolShares) Bonded() sdk.Rat { return sdk.ZeroRat() } -//_________________________________________________________________________________________________________ - -// equivalent amount of shares if the shares were unbonded +// ToUnbonded returns the equivalent amount of pool shares if the shares were +// unbonded. func (s PoolShares) ToUnbonded(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: - exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr + // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) + // bondedshr*unbondedshr/bondedshr = unbondedshr + amount = s.Amount.Mul(exRate) case sdk.Unbonding: - exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr - amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr + // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr + exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) + // unbondingshr*unbondedshr/unbondingshr = unbondedshr + amount = s.Amount.Mul(exRate) case sdk.Unbonded: amount = s.Amount } + return NewUnbondedShares(amount) } -// equivalent amount of shares if the shares were unbonding +// ToUnbonding returns the equivalent amount of pool shares if the shares were +// unbonding. func (s PoolShares) ToUnbonding(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: - exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr - amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr + // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) + // bondedshr*unbondingshr/bondedshr = unbondingshr + amount = s.Amount.Mul(exRate) case sdk.Unbonding: amount = s.Amount case sdk.Unbonded: - exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr - amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr + // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) + // unbondedshr*unbondingshr/unbondedshr = unbondingshr + amount = s.Amount.Mul(exRate) } + return NewUnbondingShares(amount) } -// equivalent amount of shares if the shares were bonded +// ToBonded the equivalent amount of pool shares if the shares were bonded. func (s PoolShares) ToBonded(p Pool) PoolShares { var amount sdk.Rat + switch s.Status { case sdk.Bonded: amount = s.Amount case sdk.Unbonding: - exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) case sdk.Unbonded: - exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr - amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) + // ubshr*bshr/ubshr = bshr + amount = s.Amount.Mul(exRate) } + return NewUnbondedShares(amount) } -//_________________________________________________________________________________________________________ - -// TODO better tests -// get the equivalent amount of tokens contained by the shares +// Tokens returns the equivalent amount of tokens contained by the pool shares +// for a given pool. func (s PoolShares) Tokens(p Pool) sdk.Rat { switch s.Status { case sdk.Bonded: - return p.BondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + return p.BondedShareExRate().Mul(s.Amount) case sdk.Unbonding: return p.UnbondingShareExRate().Mul(s.Amount) case sdk.Unbonded: diff --git a/x/stake/types/shares_test.go b/x/stake/types/shares_test.go new file mode 100644 index 0000000000..8a374606c5 --- /dev/null +++ b/x/stake/types/shares_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestPoolSharesTokens(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + poolShares := NewBondedShares(sdk.NewRat(50)) + tokens := poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondingShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) + + poolShares = NewUnbondedShares(sdk.NewRat(50)) + tokens = poolShares.Tokens(pool) + require.Equal(t, int64(50), tokens.RoundInt64()) +} diff --git a/x/stake/types/test_common.go b/x/stake/types/test_utils.go similarity index 84% rename from x/stake/types/test_common.go rename to x/stake/types/test_utils.go index 12f11f864b..7912b7fa85 100644 --- a/x/stake/types/test_common.go +++ b/x/stake/types/test_utils.go @@ -11,7 +11,6 @@ import ( ) var ( - // dummy pubkeys/addresses pk1 = crypto.GenPrivKeyEd25519().PubKey() pk2 = crypto.GenPrivKeyEd25519().PubKey() pk3 = crypto.GenPrivKeyEd25519().PubKey() @@ -23,18 +22,20 @@ var ( emptyPubkey crypto.PubKey ) -//______________________________________________________________ - -// any operation that transforms staking state -// takes in RNG instance, pool, validator -// returns updated pool, updated validator, delta tokens, descriptive message +// Operation reflects any operation that transforms staking state. It takes in +// a RNG instance, pool, validator and returns an updated pool, updated +// validator, delta tokens, and descriptive message. type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) -// operation: bond or unbond a validator depending on current status +// OpBondOrUnbond implements an operation that bonds or unbonds a validator +// depending on current status. // nolint: unparam func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - var msg string - var newStatus sdk.BondStatus + var ( + msg string + newStatus sdk.BondStatus + ) + if val.Status() == sdk.Bonded { msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) @@ -45,21 +46,27 @@ func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, in val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) newStatus = sdk.Bonded } + val, pool = val.UpdateStatus(pool, newStatus) return pool, val, 0, msg } -// operation: add a random number of tokens to a validator +// OpAddTokens implements an operation that adds a random number of tokens to a +// validator. func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { - tokens := int64(r.Int31n(1000)) msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + + tokens := int64(r.Int31n(1000)) val, pool, _ = val.AddTokensFromDel(pool, tokens) msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative + + // Tokens are removed so for accounting must be negative + return pool, val, -1 * tokens, msg } -// operation: remove a random number of shares from a validator +// OpRemoveShares implements an operation that removes a random number of +// shares from a validator. func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { var shares sdk.Rat for { @@ -76,7 +83,7 @@ func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, in return pool, val, tokens, msg } -// pick a random staking operation +// RandomOperation returns a random staking operation. func RandomOperation(r *rand.Rand) Operation { operations := []Operation{ OpBondOrUnbond, @@ -86,10 +93,11 @@ func RandomOperation(r *rand.Rand) Operation { r.Shuffle(len(operations), func(i, j int) { operations[i], operations[j] = operations[j], operations[i] }) + return operations[0] } -// ensure invariants that should always be true are true +// AssertInvariants ensures invariants that should always be true are true. // nolint: unparam func AssertInvariants(t *testing.T, msg string, pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) { @@ -105,29 +113,28 @@ func AssertInvariants(t *testing.T, msg string, pOrig.UnbondedTokens, pOrig.BondedTokens, pMod.UnbondedTokens, pMod.BondedTokens, tokens) - // nonnegative bonded shares + // Nonnegative bonded shares require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", msg, pOrig, pMod, tokens) - // nonnegative unbonded shares + // Nonnegative unbonded shares require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", msg, pOrig, pMod, tokens) - // nonnegative bonded ex rate + // Nonnegative bonded ex rate require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative BondedShareExRate: %d", msg, pMod.BondedShareExRate().RoundInt64()) - // nonnegative unbonded ex rate + // Nonnegative unbonded ex rate require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d", msg, pMod.UnbondedShareExRate().RoundInt64()) for _, vMod := range vMods { - - // nonnegative ex rate + // Nonnegative ex rate require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", msg, @@ -135,7 +142,7 @@ func AssertInvariants(t *testing.T, msg string, vMod.Owner, ) - // nonnegative poolShares + // Nonnegative poolShares require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", msg, @@ -145,7 +152,7 @@ func AssertInvariants(t *testing.T, msg string, vMod.Owner, ) - // nonnegative delShares + // Nonnegative delShares require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", msg, @@ -154,27 +161,25 @@ func AssertInvariants(t *testing.T, msg string, vMod.DelegatorShareExRate(pMod), vMod.Owner, ) - } - } -//________________________________________________________________________________ -// TODO refactor this random setup +// TODO: refactor this random setup -// generate a random validator +// randomValidator generates a random validator. // nolint: unparam func randomValidator(r *rand.Rand, i int) Validator { - poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) delShares := sdk.NewRat(int64(r.Int31n(10000))) var pShares PoolShares + if r.Float64() < float64(0.5) { pShares = NewBondedShares(poolSharesAmt) } else { pShares = NewUnbondedShares(poolSharesAmt) } + return Validator{ Owner: addr1, PubKey: pk1, @@ -183,7 +188,7 @@ func randomValidator(r *rand.Rand, i int) Validator { } } -// generate a random staking state +// RandomSetup generates a random staking state. func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { pool := InitialPool() pool.LooseTokens = 100000 @@ -191,6 +196,7 @@ func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { validators := make([]Validator, numValidators) for i := 0; i < numValidators; i++ { validator := randomValidator(r, i) + if validator.Status() == sdk.Bonded { pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) pool.BondedTokens += validator.PoolShares.Bonded().RoundInt64() @@ -198,7 +204,9 @@ func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) pool.UnbondedTokens += validator.PoolShares.Unbonded().RoundInt64() } + validators[i] = validator } + return pool, validators } diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index c2c19439be..c55ae725a6 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -4,13 +4,14 @@ import ( "bytes" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" tmtypes "github.com/tendermint/tendermint/types" - - sdk "github.com/cosmos/cosmos-sdk/types" ) +const doNotModifyDescVal = "[do-not-modify]" + // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this @@ -60,15 +61,13 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti } } -// only the vitals - does not check bond height of IntraTxCounter +// Equal returns a boolean reflecting if two given validators are identical. func (v Validator) Equal(c2 Validator) bool { return v.PubKey.Equals(c2.PubKey) && bytes.Equal(v.Owner, c2.Owner) && v.PoolShares.Equal(c2.PoolShares) && v.DelegatorShares.Equal(c2.DelegatorShares) && v.Description == c2.Description && - //v.BondHeight == c2.BondHeight && - //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && v.Commission.Equal(c2.Commission) && v.CommissionMax.Equal(c2.CommissionMax) && @@ -85,6 +84,7 @@ type Description struct { Details string `json:"details"` // optional details } +// NewDescription returns a new Description with the provided values. func NewDescription(moniker, identity, website, details string) Description { return Description{ Moniker: moniker, @@ -94,20 +94,22 @@ func NewDescription(moniker, identity, website, details string) Description { } } -// update the description based on input +// UpdateDescription updates the fields of a given description. An error is +// returned if the resulting description contains an invalid length. func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { - if d.Moniker == "[do-not-modify]" { + if d.Moniker == doNotModifyDescVal { d2.Moniker = d.Moniker } - if d.Identity == "[do-not-modify]" { + if d.Identity == doNotModifyDescVal { d2.Identity = d.Identity } - if d.Website == "[do-not-modify]" { + if d.Website == doNotModifyDescVal { d2.Website = d.Website } - if d.Details == "[do-not-modify]" { + if d.Details == doNotModifyDescVal { d2.Details = d.Details } + return Description{ Moniker: d2.Moniker, Identity: d2.Identity, @@ -116,7 +118,7 @@ func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) }.EnsureLength() } -// ensure the length of the description +// EnsureLength ensures the length of a validator's description. func (d Description) EnsureLength() (Description, sdk.Error) { if len(d.Moniker) > 70 { return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) @@ -130,10 +132,11 @@ func (d Description) EnsureLength() (Description, sdk.Error) { if len(d.Details) > 280 { return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) } + return d, nil } -// abci validator from stake validator type +// ABCIValidator returns an abci.Validator from a staked validator type. func (v Validator) ABCIValidator() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), @@ -141,8 +144,8 @@ func (v Validator) ABCIValidator() abci.Validator { } } -// abci validator from stake validator type -// with zero power used for validator updates +// ABCIValidatorZero returns an abci.Validator from a staked validator type +// with with zero power used for validator updates. func (v Validator) ABCIValidatorZero() abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), @@ -150,12 +153,13 @@ func (v Validator) ABCIValidatorZero() abci.Validator { } } -// abci validator from stake validator type +// Status returns the validator's bond status inferred from the pool shares. func (v Validator) Status() sdk.BondStatus { return v.PoolShares.Status } -// update the location of the shares within a validator if its bond status has changed +// UpdateStatus updates the location of the shares within a validator if it's +// bond status has changed. func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { var tokens int64 @@ -173,7 +177,8 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) case sdk.Bonded: - if NewStatus == sdk.Bonded { // return if nothing needs switching + if NewStatus == sdk.Bonded { + // Return if nothing needs switching return v, pool } pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) @@ -187,14 +192,16 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, case sdk.Bonded: pool, v.PoolShares = pool.addTokensBonded(tokens) } + return v, pool } -// Remove pool shares -// Returns corresponding tokens, which could be burned (e.g. when slashing -// a validator) or redistributed elsewhere +// RemovePoolShares removes pool shares from a validator. It returns +// corresponding tokens, which could be burned (e.g. when slashing a validator) +// or redistributed elsewhere. func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { var tokens int64 + switch v.Status() { case sdk.Unbonded: pool, tokens = pool.removeSharesUnbonded(poolShares) @@ -203,29 +210,33 @@ func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, P case sdk.Bonded: pool, tokens = pool.removeSharesBonded(poolShares) } + v.PoolShares.Amount = v.PoolShares.Amount.Sub(poolShares) return v, pool, tokens } -// TODO remove should only be tokens -// get the power or potential power for a validator -// if bonded, the power is the BondedShares -// if not bonded, the power is the amount of bonded shares which the -// the validator would have it was bonded +// EquivalentBondedShares ... +// +// TODO: Remove should only be tokens get the power or potential power for a +// validator if bonded, the power is the BondedShares if not bonded, the power +// is the amount of bonded shares which the the validator would have it was +// bonded. func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { return v.PoolShares.ToBonded(pool).Amount } //_________________________________________________________________________________________________________ -// add tokens to a validator -func (v Validator) AddTokensFromDel(pool Pool, - amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { +// AddTokensFromDel adds tokens to a validator +func (v Validator) AddTokensFromDel(pool Pool, amount int64) (Validator, Pool, sdk.Rat) { + var ( + poolShares PoolShares + equivalentBondedShares sdk.Rat + ) - exRate := v.DelegatorShareExRate(pool) // bshr/delshr + // bondedShare/delegatedShare + exRate := v.DelegatorShareExRate(pool) - var poolShares PoolShares - var equivalentBondedShares sdk.Rat switch v.Status() { case sdk.Unbonded: pool, poolShares = pool.addTokensUnbonded(amount) @@ -237,21 +248,24 @@ func (v Validator) AddTokensFromDel(pool Pool, v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) equivalentBondedShares = poolShares.ToBonded(pool).Amount - issuedDelegatorShares = equivalentBondedShares.Quo(exRate) // bshr/(bshr/delshr) = delshr + // bondedShare/(bondedShare/delegatedShare) = delegatedShare + issuedDelegatorShares := equivalentBondedShares.Quo(exRate) v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) return v, pool, issuedDelegatorShares } -// remove delegator shares from a validator -// NOTE this function assumes the shares have already been updated for the validator status -func (v Validator) RemoveDelShares(pool Pool, - delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { - +// RemoveDelShares removes delegator shares from a validator. +// +// NOTE: This function assumes the shares have already been updated for the +// validator status. +func (v Validator) RemoveDelShares(pool Pool, delShares sdk.Rat) (Validator, Pool, int64) { amount := v.DelegatorShareExRate(pool).Mul(delShares) eqBondedSharesToRemove := NewBondedShares(amount) v.DelegatorShares = v.DelegatorShares.Sub(delShares) + var createdCoins int64 + switch v.Status() { case sdk.Unbonded: unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount @@ -265,15 +279,17 @@ func (v Validator) RemoveDelShares(pool Pool, pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) } + return v, pool, createdCoins } -// get the exchange rate of tokens over delegator shares +// DelegatorShareExRate gets the exchange rate of tokens over delegator shares. // UNITS: eq-val-bonded-shares/delegator-shares func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { if v.DelegatorShares.IsZero() { return sdk.OneRat() } + eqBondedShares := v.PoolShares.ToBonded(pool).Amount return eqBondedShares.Quo(v.DelegatorShares) } @@ -293,16 +309,20 @@ func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares } func (v Validator) GetBondHeight() int64 { return v.BondHeight } -//Human Friendly pretty printer +// HumanReadableString returns a human readable string representation of a +// validator. An error is returned if the owner or the owner's public key +// cannot be converted to Bech32 format. func (v Validator) HumanReadableString() (string, error) { bechOwner, err := sdk.Bech32ifyAcc(v.Owner) if err != nil { return "", err } + bechVal, err := sdk.Bech32ifyValPub(v.PubKey) if err != nil { return "", err } + resp := "Validator \n" resp += fmt.Sprintf("Owner: %s\n", bechOwner) resp += fmt.Sprintf("Validator: %s\n", bechVal) diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 4fcfa6e17d..f978a6d6eb 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -5,12 +5,89 @@ import ( "math/rand" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" + tmtypes "github.com/tendermint/tendermint/types" ) +func TestValidatorEqual(t *testing.T) { + val1 := NewValidator(addr1, pk1, Description{}) + val2 := NewValidator(addr1, pk1, Description{}) + + ok := val1.Equal(val2) + require.True(t, ok) + + val2 = NewValidator(addr2, pk2, Description{}) + + ok = val1.Equal(val2) + require.False(t, ok) +} + +func TestUpdateDescription(t *testing.T) { + d1 := Description{ + Moniker: doNotModifyDescVal, + Identity: doNotModifyDescVal, + Website: doNotModifyDescVal, + Details: doNotModifyDescVal, + } + d2 := Description{ + Website: "https://validator.cosmos", + Details: "Test validator", + } + + d, err := d1.UpdateDescription(d2) + require.Nil(t, err) + require.Equal(t, d, d1) +} + +func TestABCIValidator(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidator() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, val.PoolShares.Bonded().RoundInt64(), abciVal.Power) +} + +func TestABCIValidatorZero(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + abciVal := val.ABCIValidatorZero() + require.Equal(t, tmtypes.TM2PB.PubKey(val.PubKey), abciVal.PubKey) + require.Equal(t, int64(0), abciVal.Power) +} + +func TestRemovePoolShares(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + + pool.BondedTokens = val.PoolShares.Bonded().RoundInt64() + pool.BondedShares = val.PoolShares.Bonded() + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, tk := val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(90), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(90), pool.BondedTokens) + require.Equal(t, int64(90), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(20), pool.LooseTokens) + require.Equal(t, int64(10), tk) + + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, tk = val.RemovePoolShares(pool, sdk.NewRat(10)) + require.Equal(t, int64(80), val.PoolShares.Amount.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens) + require.Equal(t, int64(0), pool.BondedShares.RoundInt64()) + require.Equal(t, int64(30), pool.LooseTokens) + require.Equal(t, int64(10), tk) +} + func TestAddTokensValidatorBonded(t *testing.T) { pool := InitialPool() pool.LooseTokens = 10 @@ -230,3 +307,13 @@ func TestMultiValidatorIntegrationInvariants(t *testing.T) { } } } + +func TestHumanReadableString(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := val.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +}