diff --git a/Makefile b/Makefile index fa7655f6f..5cb2b7c85 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation' | grep -v '/prometheus' | grep -v '/clitest' | grep -v '/lcd' | grep -v '/protobuf') PACKAGES_MODULES=$(shell go list ./... | grep 'modules') +PACKAGES_TYPES=$(shell go list ./... | grep 'types') PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation') all: get_tools get_vendor_deps install @@ -119,6 +120,7 @@ test_sim: test_sim_modules test_sim_benchmark test_sim_iris_nondeterminism test_ test_unit: #@go test $(PACKAGES_NOSIMULATION) @go test $(PACKAGES_MODULES) + @go test $(PACKAGES_TYPES) test_cli: @go test -timeout 20m -count 1 -p 1 client/clitest/utils.go client/clitest/bank_test.go client/clitest/distribution_test.go client/clitest/gov_test.go client/clitest/iparam_test.go client/clitest/irismon_test.go client/clitest/record_test.go client/clitest/service_test.go client/clitest/stake_test.go diff --git a/app/genesis.go b/app/genesis.go index edabe10f6..0b12b2efd 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -25,15 +25,14 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/irisnet/irishub/modules/arbitration" "github.com/irisnet/irishub/modules/guardian" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) var ( - Denom = "iris" - StakeDenom = Denom + "-" + types.Atto FeeAmt = int64(100) - IrisCt = types.NewDefaultCoinType(Denom) - FreeFermionVal, _ = IrisCt.ConvertToMinCoin(fmt.Sprintf("%d%s", FeeAmt, Denom)) - FreeFermionAcc, _ = IrisCt.ConvertToMinCoin(fmt.Sprintf("%d%s", int64(150), Denom)) + IrisCt = types.NewDefaultCoinType(stakeTypes.StakeDenomName) + FreeFermionVal, _ = IrisCt.ConvertToMinCoin(fmt.Sprintf("%d%s", FeeAmt, stakeTypes.StakeDenomName)) + FreeFermionAcc, _ = IrisCt.ConvertToMinCoin(fmt.Sprintf("%d%s", int64(150), stakeTypes.StakeDenomName)) ) const ( @@ -144,18 +143,18 @@ func IrisAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js } for _, acc := range genesisState.Accounts { - // create the genesis account, give'm few iris-atto and a buncha token with there name + // create the genesis account, give'm few stake token and a buncha token with there name for _, coin := range acc.Coins { coinName, err := types.GetCoinName(coin) if err != nil { panic(fmt.Sprintf("fatal error: failed pick out demon from coin: %s", coin)) } - if coinName != Denom { + if coinName != stakeTypes.StakeDenomName { continue } stakeToken, err := IrisCt.ConvertToMinCoin(coin) if err != nil { - panic(fmt.Sprintf("fatal error: failed to convert %s to stake token: %s", StakeDenom, coin)) + panic(fmt.Sprintf("fatal error: failed to convert %s to stake token: %s", stakeTypes.StakeDenom, coin)) } stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens. Add(sdk.NewDecFromInt(stakeToken.Amount)) // increase the supply @@ -306,7 +305,7 @@ func createStakeGenesisState() stake.GenesisState { Params: stake.Params{ UnbondingTime: defaultUnbondingTime, MaxValidators: 100, - BondDenom: StakeDenom, + BondDenom: stakeTypes.StakeDenom, }, } } @@ -315,7 +314,7 @@ func createMintGenesisState() mint.GenesisState { return mint.GenesisState{ Minter: mint.InitialMinter(), Params: mint.Params{ - MintDenom: StakeDenom, + MintDenom: stakeTypes.StakeDenom, InflationRateChange: sdk.NewDecWithPrec(13, 2), InflationMax: sdk.NewDecWithPrec(20, 2), InflationMin: sdk.NewDecWithPrec(7, 2), @@ -327,16 +326,16 @@ func createMintGenesisState() mint.GenesisState { // normalize stake token to mini-unit func normalizeNativeToken(coins []string) sdk.Coins { var accountCoins sdk.Coins - nativeCoin := sdk.NewInt64Coin(StakeDenom, 0) + nativeCoin := sdk.NewInt64Coin(stakeTypes.StakeDenom, 0) for _, coin := range coins { coinName, err := types.GetCoinName(coin) if err != nil { panic(fmt.Sprintf("fatal error: failed pick out demon from coin: %s", coin)) } - if coinName == Denom { + if coinName == stakeTypes.StakeDenomName { normalizeNativeToken, err := IrisCt.ConvertToMinCoin(coin) if err != nil { - panic(fmt.Sprintf("fatal error in converting %s to %s", coin, StakeDenom)) + panic(fmt.Sprintf("fatal error in converting %s to %s", coin, stakeTypes.StakeDenom)) } nativeCoin = nativeCoin.Plus(normalizeNativeToken) } else { diff --git a/app/sim_test.go b/app/sim_test.go index 1dde3babd..8540f8aab 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -20,11 +20,12 @@ import ( "github.com/tendermint/tendermint/libs/log" "github.com/irisnet/irishub/modules/gov" - banksim "github.com/irisnet/irishub/simulation/bank" - govsim "github.com/irisnet/irishub/simulation/gov" - "github.com/irisnet/irishub/simulation/mock/simulation" - slashingsim "github.com/irisnet/irishub/simulation/slashing" - stakesim "github.com/irisnet/irishub/simulation/stake" + banksim "github.com/irisnet/irishub/modules/bank/simulation" + govsim "github.com/irisnet/irishub/modules/gov/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" + slashingsim "github.com/irisnet/irishub/modules/slashing/simulation" + stakesim "github.com/irisnet/irishub/modules/stake/simulation" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) var ( @@ -49,7 +50,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { stakeGenesis := stake.DefaultGenesisState() fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis) - var genesisAccounts []GenesisAccount + var genesisAccounts []GenesisFileAccount amount := sdk.NewIntWithDecimal(100, 18) stakeAmount := sdk.NewIntWithDecimal(1, 2) @@ -59,13 +60,13 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { if numInitiallyBonded > numAccs { numInitiallyBonded = numAccs } - fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of iris-atto per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded) + fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of %s per account: %v, initially bonded validators: %v}\n", stakeTypes.StakeDenom, amount, numInitiallyBonded) // Randomly generate some genesis accounts for _, acc := range accs { coins := sdk.Coins{ { - Denom: "iris-atto", + Denom: stakeTypes.StakeDenom, Amount: amount, }, { @@ -73,9 +74,13 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { Amount: stakeAmount, }, } - genesisAccounts = append(genesisAccounts, GenesisAccount{ + var coinsStringArray []string + for _, coin := range coins { + coinsStringArray = append(coinsStringArray, coin.String()) + } + genesisAccounts = append(genesisAccounts, GenesisFileAccount{ Address: acc.Address, - Coins: coins, + Coins: coinsStringArray, }) } @@ -113,7 +118,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { stakeGenesis.Validators = validators stakeGenesis.Bonds = delegations - genesis := GenesisState{ + genesis := GenesisFileState{ Accounts: genesisAccounts, StakeData: stakeGenesis, MintData: mintGenesis, diff --git a/client/bank/cli/sign.go b/client/bank/cli/sign.go index 3bf6894b8..89e1588da 100644 --- a/client/bank/cli/sign.go +++ b/client/bank/cli/sign.go @@ -40,6 +40,7 @@ recommended to set such parameters manually.`, cmd.Flags().Bool(flagAppend, true, "Append the signature to the existing ones. If disabled, old signatures would be overwritten") cmd.Flags().Bool(flagPrintSigs, false, "Print the addresses that must sign the transaction and those who have already signed it, then exit") cmd.Flags().Bool(flagOffline, false, "Offline mode. Do not query local cache.") + cmd.MarkFlagRequired(client.FlagChainID) return cmd } diff --git a/client/context/query.go b/client/context/query.go index c2ebfbd74..82bfdb76c 100644 --- a/client/context/query.go +++ b/client/context/query.go @@ -2,28 +2,26 @@ package context import ( "fmt" - - sdk "github.com/irisnet/irishub/types" - "github.com/irisnet/irishub/modules/auth" - - "github.com/pkg/errors" - + "io/ioutil" + "net/http" "strings" - "github.com/irisnet/irishub/store" "github.com/irisnet/irishub/app" + "github.com/irisnet/irishub/modules/auth" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" + "github.com/irisnet/irishub/store" "github.com/irisnet/irishub/types" + sdk "github.com/irisnet/irishub/types" + "github.com/pkg/errors" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" tmliteErr "github.com/tendermint/tendermint/lite/errors" tmliteProxy "github.com/tendermint/tendermint/lite/proxy" - "github.com/tendermint/tendermint/crypto/merkle" rpcclient "github.com/tendermint/tendermint/rpc/client" tmclient "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" - "io/ioutil" - "net/http" ) // GetNode returns an RPC client. If the context's client is not defined, an @@ -284,7 +282,7 @@ func (cliCtx CLIContext) GetCoinType(coinName string) (types.CoinType, error) { if coinName == "" { return types.CoinType{}, fmt.Errorf("coin name is empty") } - if coinName == app.Denom { + if coinName == stakeTypes.StakeDenomName { coinType = app.IrisCt } else { key := types.CoinTypeKey(coinName) diff --git a/client/distribution/utils.go b/client/distribution/utils.go index f1bbc56bb..f130c59b4 100644 --- a/client/distribution/utils.go +++ b/client/distribution/utils.go @@ -3,9 +3,9 @@ package distribution import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/distribution" - "github.com/irisnet/irishub/app" "github.com/irisnet/irishub/client/context" "github.com/irisnet/irishub/client/utils" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) // distribution info for a particular validator @@ -19,8 +19,8 @@ type ValidatorDistInfoOutput struct { func ConvertToValidatorDistInfoOutput(cliCtx context.CLIContext, vdi distribution.ValidatorDistInfo) ValidatorDistInfoOutput { exRate := utils.ExRateFromStakeTokenToMainUnit(cliCtx) - delPool := utils.ConvertDecToRat(vdi.DelPool.AmountOf(app.Denom+"-"+"atto")).Mul(exRate).FloatString() + app.Denom - valCommission := utils.ConvertDecToRat(vdi.ValCommission.AmountOf(app.Denom+"-"+"atto")).Mul(exRate).FloatString() + app.Denom + delPool := utils.ConvertDecToRat(vdi.DelPool.AmountOf(stakeTypes.StakeDenom)).Mul(exRate).FloatString() + stakeTypes.StakeDenomName + valCommission := utils.ConvertDecToRat(vdi.ValCommission.AmountOf(stakeTypes.StakeDenom)).Mul(exRate).FloatString() + stakeTypes.StakeDenomName return ValidatorDistInfoOutput{ OperatorAddr: vdi.OperatorAddr, FeePoolWithdrawalHeight: vdi.FeePoolWithdrawalHeight, diff --git a/client/lcd/swaggerui/swagger.yaml b/client/lcd/swaggerui/swagger.yaml index 23386b756..0ce4c005c 100644 --- a/client/lcd/swaggerui/swagger.yaml +++ b/client/lcd/swaggerui/swagger.yaml @@ -1201,6 +1201,30 @@ paths: description: Invalid validator address 500: description: Internal Server Error + /stake/validators/{validatorAddr}/delegations: + parameters: + - in: path + name: validatorAddr + description: Bech32 OperatorAddress of validator + required: true + type: string + get: + summary: Get all delegations from a validator + tags: + - ICS21 + produces: + - application/json + responses: + 200: + description: OK + schema: + type: array + items: + $ref: "#/definitions/Delegation" + 400: + description: Invalid validator address + 500: + description: Internal Server Error /stake/validators/{validatorAddr}/unbonding_delegations: parameters: - in: path diff --git a/client/stake/cli/query.go b/client/stake/cli/query.go index 3f0ec4670..0c041c110 100644 --- a/client/stake/cli/query.go +++ b/client/stake/cli/query.go @@ -11,6 +11,7 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/stake/types" + "github.com/irisnet/irishub/modules/stake/querier" "github.com/irisnet/irishub/client/context" stakeClient "github.com/irisnet/irishub/client/stake" ) @@ -18,9 +19,9 @@ import ( // GetCmdQueryValidator implements the validator query command. func GetCmdQueryValidator(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "validator [owner-addr]", + Use: "validator [validator-address]", Short: "Query a validator", - Example: "iriscli stake validator ", + Example: "iriscli stake validator ", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { addr, err := sdk.ValAddressFromBech32(args[0]) @@ -128,8 +129,9 @@ func GetCmdQueryValidators(storeName string, cdc *codec.Codec) *cobra.Command { // GetCmdQueryValidatorUnbondingDelegations implements the query all unbonding delegatations from a validator command. func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "unbonding-delegations-from [operator-addr]", + Use: "unbonding-delegations-from [validator-address]", Short: "Query all unbonding delegatations from a validator", + Example: "iriscli stake unbonding-delegations-from ", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { valAddr, err := sdk.ValAddressFromBech32(args[0]) @@ -137,9 +139,7 @@ func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Code return err } cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.QueryValidatorParams{ - ValidatorAddr: valAddr, - } + params := querier.NewQueryValidatorParams(valAddr) bz, err := cdc.MarshalJSON(params) if err != nil { return err @@ -159,8 +159,9 @@ func GetCmdQueryValidatorUnbondingDelegations(queryRoute string, cdc *codec.Code // GetCmdQueryValidatorRedelegations implements the query all redelegatations from a validator command. func GetCmdQueryValidatorRedelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "redelegations-from [operator-addr]", + Use: "redelegations-from [validator-address]", Short: "Query all outgoing redelegatations from a validator", + Example: "iriscli stake redelegations-from ", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { valAddr, err := sdk.ValAddressFromBech32(args[0]) @@ -168,9 +169,7 @@ func GetCmdQueryValidatorRedelegations(queryRoute string, cdc *codec.Codec) *cob return err } cliCtx := context.NewCLIContext().WithCodec(cdc) - params := stake.QueryValidatorParams{ - ValidatorAddr: valAddr, - } + params := querier.NewQueryValidatorParams(valAddr) bz, err := cdc.MarshalJSON(params) if err != nil { return err @@ -250,7 +249,7 @@ func GetCmdQueryDelegation(storeName string, cdc *codec.Codec) *cobra.Command { // made from one delegator. func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "delegations [delegator-addr]", + Use: "delegations [delegator-address]", Short: "Query all delegations made from one delegator", Example: "iriscli stake delegations ", Args: cobra.ExactArgs(1), @@ -291,6 +290,36 @@ func GetCmdQueryDelegations(storeName string, cdc *codec.Codec) *cobra.Command { return cmd } +// GetCmdQueryValidatorDelegations implements the command to query all the +// delegations to a specific validator. +func GetCmdQueryValidatorDelegations(queryRoute string, cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegations-to [validator-address]", + Short: "Query all delegations made to one validator", + Example: "iriscli stake delegations-to ", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + validatorAddr, err := sdk.ValAddressFromBech32(args[0]) + if err != nil { + return err + } + params := querier.NewQueryValidatorParams(validatorAddr) + bz, err := cdc.MarshalJSON(params) + if err != nil { + return err + } + cliCtx := context.NewCLIContext().WithCodec(cdc) + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/validatorDelegations", queryRoute), bz) + if err != nil { + return err + } + fmt.Println(string(res)) + return nil + }, + } + return cmd +} + // GetCmdQueryUnbondingDelegation implements the command to query a single // unbonding-delegation record. func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.Command { @@ -355,7 +384,7 @@ func GetCmdQueryUnbondingDelegation(storeName string, cdc *codec.Codec) *cobra.C // unbonding-delegation records for a delegator. func GetCmdQueryUnbondingDelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "unbonding-delegations [delegator-addr]", + Use: "unbonding-delegations [delegator-address]", Short: "Query all unbonding-delegations records for one delegator", Example: "iriscli stake unbonding-delegation ", Args: cobra.ExactArgs(1), @@ -465,7 +494,7 @@ func GetCmdQueryRedelegation(storeName string, cdc *codec.Codec) *cobra.Command // redelegation records for a delegator. func GetCmdQueryRedelegations(storeName string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "redelegations [delegator-addr]", + Use: "redelegations [delegator-address]", Short: "Query all redelegations records for one delegator", Example: "iriscli stake redelegations ", Args: cobra.ExactArgs(1), diff --git a/client/stake/cli/sendtx.go b/client/stake/cli/sendtx.go index 493d1e021..be730345f 100644 --- a/client/stake/cli/sendtx.go +++ b/client/stake/cli/sendtx.go @@ -10,7 +10,6 @@ import ( "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/stake/types" - "github.com/irisnet/irishub/app" "github.com/irisnet/irishub/client/context" stakeClient "github.com/irisnet/irishub/client/stake" "github.com/irisnet/irishub/client/utils" @@ -299,7 +298,7 @@ func getShares( return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") } - stakeTokenDenom, err := cliCtx.GetCoinType(app.Denom) + stakeTokenDenom, err := cliCtx.GetCoinType(types.StakeDenomName) if err != nil { panic(err) } diff --git a/client/stake/lcd/query.go b/client/stake/lcd/query.go index dee8739b9..afab695b1 100644 --- a/client/stake/lcd/query.go +++ b/client/stake/lcd/query.go @@ -78,6 +78,12 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co validatorHandlerFn(cliCtx, cdc), ).Methods("GET") + // Get all delegations to a validator + r.HandleFunc( + "/stake/validators/{validatorAddr}/delegations", + validatorDelegationsHandlerFn(cliCtx, cdc), + ).Methods("GET") + // Get all unbonding delegations from a validator r.HandleFunc( "/stake/validators/{validatorAddr}/unbonding_delegations", @@ -235,6 +241,11 @@ func validatorHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle return queryValidator(cliCtx, cdc, "custom/stake/validator") } +// HTTP request handler to query all unbonding delegations from a validator +func validatorDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { + return queryValidator(cliCtx, cdc, "custom/stake/validatorDelegations") +} + // HTTP request handler to query all unbonding delegations from a validator func validatorUnbondingDelegationsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.HandlerFunc { return queryValidator(cliCtx, cdc, "custom/stake/validatorUnbondingDelegations") diff --git a/client/stake/lcd/utils.go b/client/stake/lcd/utils.go index 52bdf541c..acc76f694 100644 --- a/client/stake/lcd/utils.go +++ b/client/stake/lcd/utils.go @@ -6,9 +6,9 @@ import ( "github.com/irisnet/irishub/codec" sdk "github.com/irisnet/irishub/types" - "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/stake/tags" "github.com/irisnet/irishub/modules/stake/types" + "github.com/irisnet/irishub/modules/stake/querier" "github.com/gorilla/mux" "github.com/irisnet/irishub/client/context" stakeClient "github.com/irisnet/irishub/client/stake" @@ -63,10 +63,7 @@ func queryBonds(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string) ht return } - params := stake.QueryBondsParams{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - } + params := querier.NewQueryBondsParams(delegatorAddr, validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -132,9 +129,7 @@ func queryDelegator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.QueryDelegatorParams{ - DelegatorAddr: delegatorAddr, - } + params := querier.NewQueryDelegatorParams(delegatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { @@ -237,10 +232,7 @@ func queryValidator(cliCtx context.CLIContext, cdc *codec.Codec, endpoint string return } - params := stake.QueryValidatorParams{ - ValidatorAddr: validatorAddr, - } - + params := querier.NewQueryValidatorParams(validatorAddr) bz, err := cdc.MarshalJSON(params) if err != nil { utils.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) diff --git a/client/utils/utils.go b/client/utils/utils.go index 32f926529..5440e4a45 100644 --- a/client/utils/utils.go +++ b/client/utils/utils.go @@ -7,12 +7,12 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/auth" - "github.com/irisnet/irishub/app" "github.com/irisnet/irishub/client/context" "github.com/irisnet/irishub/client/keys" irishubType "github.com/irisnet/irishub/types" "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/common" + "github.com/irisnet/irishub/modules/stake/types" ) // SendOrPrintTx implements a utility function that @@ -260,7 +260,7 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool { } func ExRateFromStakeTokenToMainUnit(cliCtx context.CLIContext) irishubType.Rat { - stakeTokenDenom, err := cliCtx.GetCoinType(app.Denom) + stakeTokenDenom, err := cliCtx.GetCoinType(types.StakeDenomName) if err != nil { panic(err) } diff --git a/cmd/iriscli/main.go b/cmd/iriscli/main.go index 4c8375c93..da854691f 100644 --- a/cmd/iriscli/main.go +++ b/cmd/iriscli/main.go @@ -140,6 +140,7 @@ func main() { stakecmd.GetCmdQueryDelegations("stake", cdc), stakecmd.GetCmdQueryUnbondingDelegation("stake", cdc), stakecmd.GetCmdQueryUnbondingDelegations("stake", cdc), + stakecmd.GetCmdQueryValidatorDelegations("stake", cdc), stakecmd.GetCmdQueryValidatorUnbondingDelegations("stake", cdc), stakecmd.GetCmdQueryValidatorRedelegations("stake", cdc), stakecmd.GetCmdQueryRedelegation("stake", cdc), diff --git a/crypto/encode_test.go b/crypto/encode_test.go new file mode 100644 index 000000000..aef8d8e32 --- /dev/null +++ b/crypto/encode_test.go @@ -0,0 +1,123 @@ +package crypto + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + + tcrypto "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +type byter interface { + Bytes() []byte +} + +func checkAminoBinary(t *testing.T, src, dst interface{}, size int) { + // Marshal to binary bytes. + bz, err := cdc.MarshalBinaryBare(src) + require.Nil(t, err, "%+v", err) + if byterSrc, ok := src.(byter); ok { + // Make sure this is compatible with current (Bytes()) encoding. + require.Equal(t, byterSrc.Bytes(), bz, "Amino binary vs Bytes() mismatch") + } + // Make sure we have the expected length. + if size != -1 { + require.Equal(t, size, len(bz), "Amino binary size mismatch") + } + // Unmarshal. + err = cdc.UnmarshalBinaryBare(bz, dst) + require.Nil(t, err, "%+v", err) +} + +func checkAminoJSON(t *testing.T, src interface{}, dst interface{}, isNil bool) { + // Marshal to JSON bytes. + js, err := cdc.MarshalJSON(src) + require.Nil(t, err, "%+v", err) + if isNil { + require.Equal(t, string(js), `null`) + } else { + require.Contains(t, string(js), `"type":`) + require.Contains(t, string(js), `"value":`) + } + // Unmarshal. + err = cdc.UnmarshalJSON(js, dst) + require.Nil(t, err, "%+v", err) +} + +//nolint +func ExamplePrintRegisteredTypes() { + cdc.PrintTypes(os.Stdout) + // Output: | Type | Name | Prefix | Length | Notes | + //| ---- | ---- | ------ | ----- | ------ | + //| PrivKeyLedgerSecp256k1 | tendermint/PrivKeyLedgerSecp256k1 | 0x10CAB393 | variable | | + //| PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | + //| PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | + //| PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | + //| PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | + //| PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | +} + +func TestKeyEncodings(t *testing.T) { + cases := []struct { + privKey tcrypto.PrivKey + privSize, pubSize int // binary sizes with the amino overhead + }{ + { + privKey: ed25519.GenPrivKey(), + privSize: 69, + pubSize: 37, + }, + { + privKey: secp256k1.GenPrivKey(), + privSize: 37, + pubSize: 38, + }, + } + + for _, tc := range cases { + + // Check (de/en)codings of PrivKeys. + var priv2, priv3 tcrypto.PrivKey + checkAminoBinary(t, tc.privKey, &priv2, tc.privSize) + require.EqualValues(t, tc.privKey, priv2) + checkAminoJSON(t, tc.privKey, &priv3, false) // TODO also check Prefix bytes. + require.EqualValues(t, tc.privKey, priv3) + + // Check (de/en)codings of Signatures. + var sig1, sig2 []byte + sig1, err := tc.privKey.Sign([]byte("something")) + require.NoError(t, err) + checkAminoBinary(t, sig1, &sig2, -1) // Signature size changes for Secp anyways. + require.EqualValues(t, sig1, sig2) + + // Check (de/en)codings of PubKeys. + pubKey := tc.privKey.PubKey() + var pub2, pub3 tcrypto.PubKey + checkAminoBinary(t, pubKey, &pub2, tc.pubSize) + require.EqualValues(t, pubKey, pub2) + checkAminoJSON(t, pubKey, &pub3, false) // TODO also check Prefix bytes. + require.EqualValues(t, pubKey, pub3) + } +} + +func TestNilEncodings(t *testing.T) { + + // Check nil Signature. + var a, b []byte + checkAminoJSON(t, &a, &b, true) + require.EqualValues(t, a, b) + + // Check nil PubKey. + var c, d tcrypto.PubKey + checkAminoJSON(t, &c, &d, true) + require.EqualValues(t, c, d) + + // Check nil PrivKey. + var e, f tcrypto.PrivKey + checkAminoJSON(t, &e, &f, true) + require.EqualValues(t, e, f) + +} diff --git a/crypto/keys/hd/fundraiser_test.go b/crypto/keys/hd/fundraiser_test.go new file mode 100644 index 000000000..5e3cf06f3 --- /dev/null +++ b/crypto/keys/hd/fundraiser_test.go @@ -0,0 +1,82 @@ +package hd + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "io/ioutil" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/go-bip39" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +type addrData struct { + Mnemonic string + Master string + Seed string + Priv string + Pub string + Addr string +} + +func initFundraiserTestVectors(t *testing.T) []addrData { + // NOTE: atom fundraiser address + // var hdPath string = "m/44'/118'/0'/0/0" + var hdToAddrTable []addrData + + b, err := ioutil.ReadFile("test.json") + if err != nil { + t.Fatalf("could not read fundraiser test vector file (test.json): %s", err) + } + + err = json.Unmarshal(b, &hdToAddrTable) + if err != nil { + t.Fatalf("could not decode test vectors (test.json): %s", err) + } + return hdToAddrTable +} + +func TestFundraiserCompatibility(t *testing.T) { + hdToAddrTable := initFundraiserTestVectors(t) + + for i, d := range hdToAddrTable { + privB, _ := hex.DecodeString(d.Priv) + pubB, _ := hex.DecodeString(d.Pub) + addrB, _ := hex.DecodeString(d.Addr) + seedB, _ := hex.DecodeString(d.Seed) + masterB, _ := hex.DecodeString(d.Master) + + seed := bip39.NewSeed(d.Mnemonic, "") + + t.Log("================================") + t.Logf("ROUND: %d MNEMONIC: %s", i, d.Mnemonic) + + master, ch := ComputeMastersFromSeed(seed) + priv, err := DerivePrivateKeyForPath(master, ch, "44'/118'/0'/0/0") + require.NoError(t, err) + pub := secp256k1.PrivKeySecp256k1(priv).PubKey() + + t.Log("\tNODEJS GOLANG\n") + t.Logf("SEED \t%X %X\n", seedB, seed) + t.Logf("MSTR \t%X %X\n", masterB, master) + t.Logf("PRIV \t%X %X\n", privB, priv) + t.Logf("PUB \t%X %X\n", pubB, pub) + + require.Equal(t, seedB, seed) + require.Equal(t, master[:], masterB, fmt.Sprintf("Expected masters to match for %d", i)) + require.Equal(t, priv[:], privB, "Expected priv keys to match") + var pubBFixed [33]byte + copy(pubBFixed[:], pubB) + require.Equal(t, pub, secp256k1.PubKeySecp256k1(pubBFixed), fmt.Sprintf("Expected pub keys to match for %d", i)) + + addr := pub.Address() + t.Logf("ADDR \t%X %X\n", addrB, addr) + require.Equal(t, addr, crypto.Address(addrB), fmt.Sprintf("Expected addresses to match %d", i)) + + } +} diff --git a/crypto/keys/hd/hdpath_test.go b/crypto/keys/hd/hdpath_test.go new file mode 100644 index 000000000..f310fc355 --- /dev/null +++ b/crypto/keys/hd/hdpath_test.go @@ -0,0 +1,133 @@ +package hd + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/go-bip39" +) + +var defaultBIP39Passphrase = "" + +// return bip39 seed with empty passphrase +func mnemonicToSeed(mnemonic string) []byte { + return bip39.NewSeed(mnemonic, defaultBIP39Passphrase) +} + +//nolint +func ExampleStringifyPathParams() { + path := NewParams(44, 0, 0, false, 0) + fmt.Println(path.String()) + // Output: 44'/0'/0'/0/0 +} + +func TestParamsFromPath(t *testing.T) { + goodCases := []struct { + params *BIP44Params + path string + }{ + {&BIP44Params{44, 0, 0, false, 0}, "44'/0'/0'/0/0"}, + {&BIP44Params{44, 1, 0, false, 0}, "44'/1'/0'/0/0"}, + {&BIP44Params{44, 0, 1, false, 0}, "44'/0'/1'/0/0"}, + {&BIP44Params{44, 0, 0, true, 0}, "44'/0'/0'/1/0"}, + {&BIP44Params{44, 0, 0, false, 1}, "44'/0'/0'/0/1"}, + {&BIP44Params{44, 1, 1, true, 1}, "44'/1'/1'/1/1"}, + {&BIP44Params{44, 118, 52, true, 41}, "44'/118'/52'/1/41"}, + } + + for i, c := range goodCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.NoError(t, err, errStr) + assert.EqualValues(t, c.params, params, errStr) + assert.Equal(t, c.path, c.params.String()) + } + + badCases := []struct { + path string + }{ + {"43'/0'/0'/0/0"}, // doesnt start with 44 + {"44'/1'/0'/0/0/5"}, // too many fields + {"44'/0'/1'/0"}, // too few fields + {"44'/0'/0'/2/0"}, // change field can only be 0/1 + {"44/0'/0'/0/0"}, // first field needs ' + {"44'/0/0'/0/0"}, // second field needs ' + {"44'/0'/0/0/0"}, // third field needs ' + {"44'/0'/0'/0'/0"}, // fourth field must not have ' + {"44'/0'/0'/0/0'"}, // fifth field must not have ' + {"44'/-1'/0'/0/0"}, // no negatives + {"44'/0'/0'/-1/0"}, // no negatives + } + + for i, c := range badCases { + params, err := NewParamsFromPath(c.path) + errStr := fmt.Sprintf("%d %v", i, c) + assert.Nil(t, params, errStr) + assert.Error(t, err, errStr) + } + +} + +//nolint +func ExampleSomeBIP32TestVecs() { + + seed := mnemonicToSeed("barrel original fuel morning among eternal " + + "filter ball stove pluck matrix mechanic") + master, ch := ComputeMastersFromSeed(seed) + fmt.Println("keys from fundraiser test-vector (cosmos, bitcoin, ether)") + fmt.Println() + // cosmos + priv, _ := DerivePrivateKeyForPath(master, ch, FullFundraiserPath) + fmt.Println(hex.EncodeToString(priv[:])) + // bitcoin + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/0") + fmt.Println(hex.EncodeToString(priv[:])) + // ether + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/60'/0'/0/0") + fmt.Println(hex.EncodeToString(priv[:])) + + fmt.Println() + fmt.Println("keys generated via https://coinomi.com/recovery-phrase-tool.html") + fmt.Println() + + seed = mnemonicToSeed( + "advice process birth april short trust crater change bacon monkey medal garment " + + "gorilla ranch hour rival razor call lunar mention taste vacant woman sister") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/1'/1'/0/4") + fmt.Println(hex.EncodeToString(priv[:])) + + seed = mnemonicToSeed("idea naive region square margin day captain habit " + + "gun second farm pact pulse someone armed") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "44'/0'/0'/0/420") + fmt.Println(hex.EncodeToString(priv[:])) + + fmt.Println() + fmt.Println("BIP 32 example") + fmt.Println() + + // bip32 path: m/0/7 + seed = mnemonicToSeed("monitor flock loyal sick object grunt duty ride develop assault harsh history") + master, ch = ComputeMastersFromSeed(seed) + priv, _ = DerivePrivateKeyForPath(master, ch, "0/7") + fmt.Println(hex.EncodeToString(priv[:])) + + // Output: keys from fundraiser test-vector (cosmos, bitcoin, ether) + // + // bfcb217c058d8bbafd5e186eae936106ca3e943889b0b4a093ae13822fd3170c + // e77c3de76965ad89997451de97b95bb65ede23a6bf185a55d80363d92ee37c3d + // 7fc4d8a8146dea344ba04c593517d3f377fa6cded36cd55aee0a0bb968e651bc + // + // keys generated via https://coinomi.com/recovery-phrase-tool.html + // + // a61f10c5fecf40c084c94fa54273b6f5d7989386be4a37669e6d6f7b0169c163 + // 32c4599843de3ef161a629a461d12c60b009b676c35050be5f7ded3a3b23501f + // + // BIP 32 example + // + // c4c11d8c03625515905d7e89d25dfc66126fbc629ecca6db489a1a72fc4bda78 +} diff --git a/crypto/keys/keybase_test.go b/crypto/keys/keybase_test.go new file mode 100644 index 000000000..349761e8d --- /dev/null +++ b/crypto/keys/keybase_test.go @@ -0,0 +1,409 @@ +package keys + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/irisnet/irishub/crypto/keys/hd" + "github.com/irisnet/irishub/crypto/keys/mintkey" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + "github.com/irisnet/irishub/types" + dbm "github.com/tendermint/tendermint/libs/db" +) + +func init() { + mintkey.BcryptSecurityParameter = 1 +} + +// TestKeyManagement makes sure we can manipulate these keys well +func TestKeyManagement(t *testing.T) { + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := New( + db, + ) + + algo := Secp256k1 + n1, n2, n3 := "personal", "business", "other" + p1, p2 := "1234", "really-secure!@#$" + + // Check empty state + l, err := cstore.List() + require.Nil(t, err) + assert.Empty(t, l) + + _, _, err = cstore.CreateMnemonic(n1, English, p1, Ed25519) + require.Error(t, err, "ed25519 keys are currently not supported by keybase") + + // create some keys + _, err = cstore.Get(n1) + require.Error(t, err) + i, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + + require.NoError(t, err) + require.Equal(t, n1, i.GetName()) + _, _, err = cstore.CreateMnemonic(n2, English, p2, algo) + require.NoError(t, err) + + // we can get these keys + i2, err := cstore.Get(n2) + require.NoError(t, err) + _, err = cstore.Get(n3) + require.NotNil(t, err) + _, err = cstore.GetByAddress(accAddr(i2)) + require.NoError(t, err) + addr, err := types.AccAddressFromBech32("cosmos1yq8lgssgxlx9smjhes6ryjasmqmd3ts2559g0t") + require.NoError(t, err) + _, err = cstore.GetByAddress(addr) + require.NotNil(t, err) + + // list shows them in order + keyS, err := cstore.List() + require.NoError(t, err) + require.Equal(t, 2, len(keyS)) + // note these are in alphabetical order + require.Equal(t, n2, keyS[0].GetName()) + require.Equal(t, n1, keyS[1].GetName()) + require.Equal(t, i2.GetPubKey(), keyS[0].GetPubKey()) + + // deleting a key removes it + err = cstore.Delete("bad name", "foo") + require.NotNil(t, err) + err = cstore.Delete(n1, p1) + require.NoError(t, err) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 1, len(keyS)) + _, err = cstore.Get(n1) + require.Error(t, err) + + // create an offline key + o1 := "offline" + priv1 := ed25519.GenPrivKey() + pub1 := priv1.PubKey() + i, err = cstore.CreateOffline(o1, pub1) + require.Nil(t, err) + require.Equal(t, pub1, i.GetPubKey()) + require.Equal(t, o1, i.GetName()) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 2, len(keyS)) + + // delete the offline key + err = cstore.Delete(o1, "no") + require.NotNil(t, err) + err = cstore.Delete(o1, "yes") + require.NoError(t, err) + keyS, err = cstore.List() + require.NoError(t, err) + require.Equal(t, 1, len(keyS)) + + // addr cache gets nuked + err = cstore.Delete(n2, p2) + require.NoError(t, err) + require.False(t, db.Has(addrKey(i2.GetAddress()))) +} + +// TestSignVerify does some detailed checks on how we sign and validate +// signatures +func TestSignVerify(t *testing.T) { + cstore := New( + dbm.NewMemDB(), + ) + algo := Secp256k1 + + n1, n2, n3 := "some dude", "a dudette", "dude-ish" + p1, p2, p3 := "1234", "foobar", "foobar" + + // create two users and get their info + i1, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err) + + i2, _, err := cstore.CreateMnemonic(n2, English, p2, algo) + require.Nil(t, err) + + // Import a public key + armor, err := cstore.ExportPubKey(n2) + require.Nil(t, err) + cstore.ImportPubKey(n3, armor) + i3, err := cstore.Get(n3) + require.NoError(t, err) + require.Equal(t, i3.GetName(), n3) + + // let's try to sign some messages + d1 := []byte("my first message") + d2 := []byte("some other important info!") + d3 := []byte("feels like I forgot something...") + + // try signing both data with both .. + s11, pub1, err := cstore.Sign(n1, p1, d1) + require.Nil(t, err) + require.Equal(t, i1.GetPubKey(), pub1) + + s12, pub1, err := cstore.Sign(n1, p1, d2) + require.Nil(t, err) + require.Equal(t, i1.GetPubKey(), pub1) + + s21, pub2, err := cstore.Sign(n2, p2, d1) + require.Nil(t, err) + require.Equal(t, i2.GetPubKey(), pub2) + + s22, pub2, err := cstore.Sign(n2, p2, d2) + require.Nil(t, err) + require.Equal(t, i2.GetPubKey(), pub2) + + // let's try to validate and make sure it only works when everything is proper + cases := []struct { + key crypto.PubKey + data []byte + sig []byte + valid bool + }{ + // proper matches + {i1.GetPubKey(), d1, s11, true}, + // change data, pubkey, or signature leads to fail + {i1.GetPubKey(), d2, s11, false}, + {i2.GetPubKey(), d1, s11, false}, + {i1.GetPubKey(), d1, s21, false}, + // make sure other successes + {i1.GetPubKey(), d2, s12, true}, + {i2.GetPubKey(), d1, s21, true}, + {i2.GetPubKey(), d2, s22, true}, + } + + for i, tc := range cases { + valid := tc.key.VerifyBytes(tc.data, tc.sig) + require.Equal(t, tc.valid, valid, "%d", i) + } + + // Now try to sign data with a secret-less key + _, _, err = cstore.Sign(n3, p3, d3) + require.NotNil(t, err) +} + +func assertPassword(t *testing.T, cstore Keybase, name, pass, badpass string) { + getNewpass := func() (string, error) { return pass, nil } + err := cstore.Update(name, badpass, getNewpass) + require.NotNil(t, err) + err = cstore.Update(name, pass, getNewpass) + require.Nil(t, err, "%+v", err) +} + +// TestExportImport tests exporting and importing +func TestExportImport(t *testing.T) { + + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := New( + db, + ) + + info, _, err := cstore.CreateMnemonic("john", English, "secretcpw", Secp256k1) + require.NoError(t, err) + require.Equal(t, info.GetName(), "john") + + john, err := cstore.Get("john") + require.NoError(t, err) + require.Equal(t, info.GetName(), "john") + johnAddr := info.GetPubKey().Address() + + armor, err := cstore.Export("john") + require.NoError(t, err) + + err = cstore.Import("john2", armor) + require.NoError(t, err) + + john2, err := cstore.Get("john2") + require.NoError(t, err) + + require.Equal(t, john.GetPubKey().Address(), johnAddr) + require.Equal(t, john.GetName(), "john") + require.Equal(t, john, john2) +} + +// +func TestExportImportPubKey(t *testing.T) { + // make the storage with reasonable defaults + db := dbm.NewMemDB() + cstore := New( + db, + ) + + // CreateMnemonic a private-public key pair and ensure consistency + notPasswd := "n9y25ah7" + info, _, err := cstore.CreateMnemonic("john", English, notPasswd, Secp256k1) + require.Nil(t, err) + require.NotEqual(t, info, "") + require.Equal(t, info.GetName(), "john") + addr := info.GetPubKey().Address() + john, err := cstore.Get("john") + require.NoError(t, err) + require.Equal(t, john.GetName(), "john") + require.Equal(t, john.GetPubKey().Address(), addr) + + // Export the public key only + armor, err := cstore.ExportPubKey("john") + require.NoError(t, err) + // Import it under a different name + err = cstore.ImportPubKey("john-pubkey-only", armor) + require.NoError(t, err) + // Ensure consistency + john2, err := cstore.Get("john-pubkey-only") + require.NoError(t, err) + // Compare the public keys + require.True(t, john.GetPubKey().Equals(john2.GetPubKey())) + // Ensure the original key hasn't changed + john, err = cstore.Get("john") + require.NoError(t, err) + require.Equal(t, john.GetPubKey().Address(), addr) + require.Equal(t, john.GetName(), "john") + + // Ensure keys cannot be overwritten + err = cstore.ImportPubKey("john-pubkey-only", armor) + require.NotNil(t, err) +} + +// TestAdvancedKeyManagement verifies update, import, export functionality +func TestAdvancedKeyManagement(t *testing.T) { + + // make the storage with reasonable defaults + cstore := New( + dbm.NewMemDB(), + ) + + algo := Secp256k1 + n1, n2 := "old-name", "new name" + p1, p2 := "1234", "foobar" + + // make sure key works with initial password + _, _, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err, "%+v", err) + assertPassword(t, cstore, n1, p1, p2) + + // update password requires the existing password + getNewpass := func() (string, error) { return p2, nil } + err = cstore.Update(n1, "jkkgkg", getNewpass) + require.NotNil(t, err) + assertPassword(t, cstore, n1, p1, p2) + + // then it changes the password when correct + err = cstore.Update(n1, p1, getNewpass) + require.NoError(t, err) + // p2 is now the proper one! + assertPassword(t, cstore, n1, p2, p1) + + // exporting requires the proper name and passphrase + _, err = cstore.Export(n1 + ".notreal") + require.NotNil(t, err) + _, err = cstore.Export(" " + n1) + require.NotNil(t, err) + _, err = cstore.Export(n1 + " ") + require.NotNil(t, err) + _, err = cstore.Export("") + require.NotNil(t, err) + exported, err := cstore.Export(n1) + require.Nil(t, err, "%+v", err) + + // import succeeds + err = cstore.Import(n2, exported) + require.NoError(t, err) + + // second import fails + err = cstore.Import(n2, exported) + require.NotNil(t, err) +} + +// TestSeedPhrase verifies restoring from a seed phrase +func TestSeedPhrase(t *testing.T) { + + // make the storage with reasonable defaults + cstore := New( + dbm.NewMemDB(), + ) + + algo := Secp256k1 + n1, n2 := "lost-key", "found-again" + p1, p2 := "1234", "foobar" + + // make sure key works with initial password + info, mnemonic, err := cstore.CreateMnemonic(n1, English, p1, algo) + require.Nil(t, err, "%+v", err) + require.Equal(t, n1, info.GetName()) + assert.NotEmpty(t, mnemonic) + + // now, let us delete this key + err = cstore.Delete(n1, p1) + require.Nil(t, err, "%+v", err) + _, err = cstore.Get(n1) + require.NotNil(t, err) + + // let us re-create it from the mnemonic-phrase + params := *hd.NewFundraiserParams(0, 0) + newInfo, err := cstore.Derive(n2, mnemonic, defaultBIP39Passphrase, p2, params) + require.NoError(t, err) + require.Equal(t, n2, newInfo.GetName()) + require.Equal(t, info.GetPubKey().Address(), newInfo.GetPubKey().Address()) + require.Equal(t, info.GetPubKey(), newInfo.GetPubKey()) +} + +func ExampleNew() { + // Select the encryption and storage for your cryptostore + cstore := New( + dbm.NewMemDB(), + ) + + sec := Secp256k1 + + // Add keys and see they return in alphabetical order + bob, _, err := cstore.CreateMnemonic("Bob", English, "friend", sec) + if err != nil { + // this should never happen + fmt.Println(err) + } else { + // return info here just like in List + fmt.Println(bob.GetName()) + } + cstore.CreateMnemonic("Alice", English, "secret", sec) + cstore.CreateMnemonic("Carl", English, "mitm", sec) + info, _ := cstore.List() + for _, i := range info { + fmt.Println(i.GetName()) + } + + // We need to use passphrase to generate a signature + tx := []byte("deadbeef") + sig, pub, err := cstore.Sign("Bob", "friend", tx) + if err != nil { + fmt.Println("don't accept real passphrase") + } + + // and we can validate the signature with publicly available info + binfo, _ := cstore.Get("Bob") + if !binfo.GetPubKey().Equals(bob.GetPubKey()) { + fmt.Println("Get and Create return different keys") + } + + if pub.Equals(binfo.GetPubKey()) { + fmt.Println("signed by Bob") + } + if !pub.VerifyBytes(tx, sig) { + fmt.Println("invalid signature") + } + + // Output: + // Bob + // Alice + // Bob + // Carl + // signed by Bob +} + +func accAddr(info Info) types.AccAddress { + return (types.AccAddress)(info.GetPubKey().Address()) +} diff --git a/crypto/ledger_test.go b/crypto/ledger_test.go new file mode 100644 index 000000000..1aae158ef --- /dev/null +++ b/crypto/ledger_test.go @@ -0,0 +1,64 @@ +package crypto + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/encoding/amino" +) + +var ledgerEnabledEnv = "TEST_WITH_LEDGER" + +func TestRealLedgerSecp256k1(t *testing.T) { + if os.Getenv(ledgerEnabledEnv) == "" { + t.Skip(fmt.Sprintf("Set '%s' to run code on a real ledger", ledgerEnabledEnv)) + } + msg := []byte("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}") + path := DerivationPath{44, 60, 0, 0, 0} + + priv, err := NewPrivKeyLedgerSecp256k1(path) + require.Nil(t, err, "%s", err) + + pub := priv.PubKey() + sig, err := priv.Sign(msg) + require.Nil(t, err) + + valid := pub.VerifyBytes(msg, sig) + require.True(t, valid) + + // now, let's serialize the public key and make sure it still works + bs := priv.PubKey().Bytes() + pub2, err := cryptoAmino.PubKeyFromBytes(bs) + require.Nil(t, err, "%+v", err) + + // make sure we get the same pubkey when we load from disk + require.Equal(t, pub, pub2) + + // signing with the loaded key should match the original pubkey + sig, err = priv.Sign(msg) + require.Nil(t, err) + valid = pub.VerifyBytes(msg, sig) + require.True(t, valid) + + // make sure pubkeys serialize properly as well + bs = pub.Bytes() + bpub, err := cryptoAmino.PubKeyFromBytes(bs) + require.NoError(t, err) + require.Equal(t, pub, bpub) +} + +// TestRealLedgerErrorHandling calls. These tests assume +// the ledger is not plugged in.... +func TestRealLedgerErrorHandling(t *testing.T) { + if os.Getenv(ledgerEnabledEnv) != "" { + t.Skip(fmt.Sprintf("Unset '%s' to run code as if without a real Ledger", ledgerEnabledEnv)) + } + + // first, try to generate a key, must return an error + // (no panic) + path := DerivationPath{44, 60, 0, 0, 0} + _, err := NewPrivKeyLedgerSecp256k1(path) + require.Error(t, err) +} diff --git a/init/gentx.go b/init/gentx.go index 8598bdf3d..dead8e608 100644 --- a/init/gentx.go +++ b/init/gentx.go @@ -20,10 +20,11 @@ import ( "github.com/irisnet/irishub/client" signcmd "github.com/irisnet/irishub/client/bank/cli" authcmd "github.com/irisnet/irishub/client/auth/cli" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) const ( - defaultAmount = "100iris" + defaultAmount = "100" + stakeTypes.StakeDenomName defaultCommissionRate = "0.1" defaultCommissionMaxRate = "0.2" defaultCommissionMaxChangeRate = "0.01" diff --git a/init/init.go b/init/init.go index 396a9dc15..af066b4c9 100644 --- a/init/init.go +++ b/init/init.go @@ -60,9 +60,7 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { return err } - if viper.GetString(flagMoniker) != "" { - config.Moniker = viper.GetString(flagMoniker) - } + config.Moniker = viper.GetString(flagMoniker) var appState json.RawMessage genFile := config.GenesisFile() @@ -91,5 +89,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { cmd.Flags().BoolP(flagOverwrite, "o", false, "overwrite the genesis.json file") cmd.Flags().String(client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created") cmd.Flags().String(flagMoniker, "", "set the validator's moniker") + cmd.MarkFlagRequired(flagMoniker) return cmd } diff --git a/modules/auth/account_test.go b/modules/auth/account_test.go new file mode 100644 index 000000000..cc2a8b75f --- /dev/null +++ b/modules/auth/account_test.go @@ -0,0 +1,108 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + codec "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" +) + +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.AccAddress) { + key := ed25519.GenPrivKey() + pub := key.PubKey() + addr := sdk.AccAddress(pub.Address()) + return key, pub, addr +} + +func TestBaseAddressPubKey(t *testing.T) { + _, pub1, addr1 := keyPubAddr() + _, pub2, addr2 := keyPubAddr() + acc := NewBaseAccountWithAddress(addr1) + + // check the address (set) and pubkey (not set) + require.EqualValues(t, addr1, acc.GetAddress()) + require.EqualValues(t, nil, acc.GetPubKey()) + + // can't override address + err := acc.SetAddress(addr2) + require.NotNil(t, err) + require.EqualValues(t, addr1, acc.GetAddress()) + + // set the pubkey + err = acc.SetPubKey(pub1) + require.Nil(t, err) + require.Equal(t, pub1, acc.GetPubKey()) + + // can override pubkey + err = acc.SetPubKey(pub2) + require.Nil(t, err) + require.Equal(t, pub2, acc.GetPubKey()) + + //------------------------------------ + + // can set address on empty account + acc2 := BaseAccount{} + err = acc2.SetAddress(addr2) + require.Nil(t, err) + require.EqualValues(t, addr2, acc2.GetAddress()) +} + +func TestBaseAccountCoins(t *testing.T) { + _, _, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + someCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 246)} + + err := acc.SetCoins(someCoins) + require.Nil(t, err) + require.Equal(t, someCoins, acc.GetCoins()) +} + +func TestBaseAccountSequence(t *testing.T) { + _, _, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + seq := int64(7) + + err := acc.SetSequence(seq) + require.Nil(t, err) + require.Equal(t, seq, acc.GetSequence()) +} + +func TestBaseAccountMarshal(t *testing.T) { + _, pub, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + someCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 246)} + seq := int64(7) + + // set everything on the account + err := acc.SetPubKey(pub) + require.Nil(t, err) + err = acc.SetSequence(seq) + require.Nil(t, err) + err = acc.SetCoins(someCoins) + require.Nil(t, err) + + // need a codec for marshaling + cdc := codec.New() + codec.RegisterCrypto(cdc) + + b, err := cdc.MarshalBinaryLengthPrefixed(acc) + require.Nil(t, err) + + acc2 := BaseAccount{} + err = cdc.UnmarshalBinaryLengthPrefixed(b, &acc2) + require.Nil(t, err) + require.Equal(t, acc, acc2) + + // error on bad bytes + acc2 = BaseAccount{} + err = cdc.UnmarshalBinaryLengthPrefixed(b[:len(b)/2], &acc2) + require.NotNil(t, err) +} diff --git a/modules/auth/ante_test.go b/modules/auth/ante_test.go new file mode 100644 index 000000000..76e5fb888 --- /dev/null +++ b/modules/auth/ante_test.go @@ -0,0 +1,716 @@ +package auth + +import ( + "fmt" + "testing" + + codec "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/libs/log" +) + +func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg { + return sdk.NewTestMsg(addrs...) +} + +func newStdFee() StdFee { + return NewStdFee(5000, + sdk.NewInt64Coin("atom", 150), + ) +} + +// coins to more than cover the fee +func newCoins() sdk.Coins { + return sdk.Coins{ + sdk.NewInt64Coin("atom", 10000000), + } +} + +// generate a priv key and return it with its address +func privAndAddr() (crypto.PrivKey, sdk.AccAddress) { + priv := ed25519.GenPrivKey() + addr := sdk.AccAddress(priv.PubKey().Address()) + return priv, addr +} + +// run the tx through the anteHandler and ensure its valid +func checkValidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool) { + _, result, abort := anteHandler(ctx, tx, simulate) + require.False(t, abort) + require.Equal(t, sdk.ABCICodeOK, result.Code) + require.True(t, result.IsOK()) +} + +// run the tx through the anteHandler and ensure it fails with the given code +func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, tx sdk.Tx, simulate bool, code sdk.CodeType) { + newCtx, result, abort := anteHandler(ctx, tx, simulate) + require.True(t, abort) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, code), result.Code, + fmt.Sprintf("Expected %v, got %v", sdk.ToABCICode(sdk.CodespaceRoot, code), result)) + + if code == sdk.CodeOutOfGas { + stdTx, ok := tx.(StdTx) + require.True(t, ok, "tx must be in form auth.StdTx") + // GasWanted set correctly + require.Equal(t, stdTx.Fee.Gas, result.GasWanted, "Gas wanted not set correctly") + require.True(t, result.GasUsed > result.GasWanted, "GasUsed not greated than GasWanted") + // Check that context is set correctly + require.Equal(t, result.GasUsed, newCtx.GasMeter().GasConsumed(), "Context not updated correctly") + } +} + +func newTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, "") + return tx +} + +func newTestTxWithMemo(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, memo) + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} + +// All signers sign over the same StdSignDoc. Should always create invalid signatures +func newTestTxWithSignBytes(msgs []sdk.Msg, privs []crypto.PrivKey, accNums []int64, seqs []int64, fee StdFee, signBytes []byte, memo string) sdk.Tx { + sigs := make([]StdSignature, len(privs)) + for i, priv := range privs { + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + sigs[i] = StdSignature{PubKey: priv.PubKey(), Signature: sig, AccountNumber: accNums[i], Sequence: seqs[i]} + } + tx := NewStdTx(msgs, fee, sigs, memo) + return tx +} + +// Test various error cases in the AnteHandler control flow. +func TestAnteHandlerSigErrors(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() + + // msg and signatures + var tx sdk.Tx + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr1, addr3) + fee := newStdFee() + + msgs := []sdk.Msg{msg1, msg2} + + // test no signatures + privs, accNums, seqs := []crypto.PrivKey{}, []int64{}, []int64{} + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) + + // tx.GetSigners returns addresses in correct order: addr1, addr2, addr3 + expectedSigners := []sdk.AccAddress{addr1, addr2, addr3} + stdTx := tx.(StdTx) + require.Equal(t, expectedSigners, stdTx.GetSigners()) + + // Check no signatures fails + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnauthorized) + + // test num sigs dont match GetSigners + privs, accNums, seqs = []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnauthorized) + + // test an unrecognized account + privs, accNums, seqs = []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{0, 0, 0} + tx = newTestTx(ctx, msgs, privs, accNums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnknownAddress) + + // save the first account, but second is still unrecognized + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(fee.Amount) + mapper.SetAccount(ctx, acc1) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnknownAddress) +} + +// Test logic around account number checking with one signer and many signers. +func TestAnteHandlerAccountNumbers(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + msgs := []sdk.Msg{msg} + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx from wrong account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // from correct account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{0}, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx with another signer and incorrect account numbers + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr2, addr1) + msgs = []sdk.Msg{msg1, msg2} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // correct account numbers + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) +} + +// Test logic around account number checking with many signers when BlockHeight is 0. +func TestAnteHandlerAccountNumbersAtBlockHeightZero(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(0) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + msgs := []sdk.Msg{msg} + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx from wrong account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // from correct account number + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, []int64{0}, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx with another signer and incorrect account numbers + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr2, addr1) + msgs = []sdk.Msg{msg1, msg2} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{1, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // correct account numbers + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0}, []int64{2, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) +} + +// Test logic around sequence checking with one signer and many signers. +func TestAnteHandlerSequences(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + acc3 := mapper.NewAccountWithAddress(ctx, addr3) + acc3.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc3) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() + + msgs := []sdk.Msg{msg} + + // test good tx from one signer + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // test sending it again fails (replay protection) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // fix sequence, should pass + seqs = []int64{1} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // new tx with another signer and correct sequences + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr3, addr1) + msgs = []sdk.Msg{msg1, msg2} + + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{2, 0, 0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // replay fails + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // tx from just second signer with incorrect sequence fails + msg = newTestMsg(addr2) + msgs = []sdk.Msg{msg} + privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{1}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidSequence) + + // fix the sequence and it passes + tx = newTestTx(ctx, msgs, []crypto.PrivKey{priv2}, []int64{1}, []int64{1}, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // another tx from both of them that passes + msg = newTestMsg(addr1, addr2) + msgs = []sdk.Msg{msg} + privs, accnums, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{3, 2} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) +} + +// Test logic around fee deduction. +func TestAnteHandlerFees(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + + // keys and addresses + priv1, addr1 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + fee := newStdFee() + msgs := []sdk.Msg{msg} + + // signer does not have enough funds to pay the fee + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInsufficientFunds) + + acc1.SetCoins(sdk.Coins{sdk.NewInt64Coin("atom", 149)}) + mapper.SetAccount(ctx, acc1) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInsufficientFunds) + + require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(emptyCoins)) + + acc1.SetCoins(sdk.Coins{sdk.NewInt64Coin("atom", 150)}) + mapper.SetAccount(ctx, acc1) + checkValidTx(t, anteHandler, ctx, tx, false) + + require.True(t, feeCollector.GetCollectedFees(ctx).IsEqual(sdk.Coins{sdk.NewInt64Coin("atom", 150)})) +} + +// Test logic around memo gas consumption. +func TestAnteHandlerMemoGas(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + fee := NewStdFee(0, sdk.NewInt64Coin("atom", 0)) + + // tx does not have enough gas + tx = newTestTx(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeOutOfGas) + + // tx with memo doesn't have enough gas + fee = NewStdFee(801, sdk.NewInt64Coin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeOutOfGas) + + // memo too large + fee = NewStdFee(2001, sdk.NewInt64Coin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsdabcininasidniandsinasindiansdiansdinaisndiasndiadninsdabcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeMemoTooLarge) + + // tx with memo has enough gas + fee = NewStdFee(1100, sdk.NewInt64Coin("atom", 0)) + tx = newTestTxWithMemo(ctx, []sdk.Msg{msg}, privs, accnums, seqs, fee, "abcininasidniandsinasindiansdiansdinaisndiasndiadninsd") + checkValidTx(t, anteHandler, ctx, tx, false) +} + +func TestAnteHandlerMultiSigner(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + priv3, addr3 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + acc3 := mapper.NewAccountWithAddress(ctx, addr3) + acc3.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc3) + + // set up msgs and fee + var tx sdk.Tx + msg1 := newTestMsg(addr1, addr2) + msg2 := newTestMsg(addr3, addr1) + msg3 := newTestMsg(addr2, addr3) + msgs := []sdk.Msg{msg1, msg2, msg3} + fee := newStdFee() + + // signers in order + privs, accnums, seqs := []crypto.PrivKey{priv1, priv2, priv3}, []int64{0, 1, 2}, []int64{0, 0, 0} + tx = newTestTxWithMemo(ctx, msgs, privs, accnums, seqs, fee, "Check signers are in expected order and different account numbers works") + + checkValidTx(t, anteHandler, ctx, tx, false) + + // change sequence numbers + tx = newTestTx(ctx, []sdk.Msg{msg1}, []crypto.PrivKey{priv1, priv2}, []int64{0, 1}, []int64{1, 1}, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + tx = newTestTx(ctx, []sdk.Msg{msg2}, []crypto.PrivKey{priv3, priv1}, []int64{2, 0}, []int64{1, 2}, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + // expected seqs = [3, 2, 2] + tx = newTestTxWithMemo(ctx, msgs, privs, accnums, []int64{3, 2, 2}, fee, "Check signers are in expected order and different account numbers and sequence numbers works") + checkValidTx(t, anteHandler, ctx, tx, false) +} + +func TestAnteHandlerBadSignBytes(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + var tx sdk.Tx + msg := newTestMsg(addr1) + msgs := []sdk.Msg{msg} + fee := newStdFee() + fee2 := newStdFee() + fee2.Gas += 100 + fee3 := newStdFee() + fee3.Amount[0].Amount = fee3.Amount[0].Amount.AddRaw(100) + + // test good tx and signBytes + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + chainID := ctx.ChainID() + chainID2 := chainID + "somemorestuff" + codeUnauth := sdk.CodeUnauthorized + + cases := []struct { + chainID string + accnum int64 + seq int64 + fee StdFee + msgs []sdk.Msg + code sdk.CodeType + }{ + {chainID2, 0, 1, fee, msgs, codeUnauth}, // test wrong chain_id + {chainID, 0, 2, fee, msgs, codeUnauth}, // test wrong seqs + {chainID, 1, 1, fee, msgs, codeUnauth}, // test wrong accnum + {chainID, 0, 1, fee, []sdk.Msg{newTestMsg(addr2)}, codeUnauth}, // test wrong msg + {chainID, 0, 1, fee2, msgs, codeUnauth}, // test wrong fee + {chainID, 0, 1, fee3, msgs, codeUnauth}, // test wrong fee + } + + privs, seqs = []crypto.PrivKey{priv1}, []int64{1} + for _, cs := range cases { + tx := newTestTxWithSignBytes( + + msgs, privs, accnums, seqs, fee, + StdSignBytes(cs.chainID, cs.accnum, cs.seq, cs.fee, cs.msgs, ""), + "", + ) + checkInvalidTx(t, anteHandler, ctx, tx, false, cs.code) + } + + // test wrong signer if public key exist + privs, accnums, seqs = []crypto.PrivKey{priv2}, []int64{0}, []int64{1} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeUnauthorized) + + // test wrong signer if public doesn't exist + msg = newTestMsg(addr2) + msgs = []sdk.Msg{msg} + privs, accnums, seqs = []crypto.PrivKey{priv1}, []int64{1}, []int64{0} + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) + +} + +func TestAnteHandlerSetPubKey(t *testing.T) { + // setup + ms, capKey, capKey2 := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + feeCollector := NewFeeCollectionKeeper(cdc, capKey2) + anteHandler := NewAnteHandler(mapper, feeCollector) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + ctx = ctx.WithBlockHeight(1) + + // keys and addresses + priv1, addr1 := privAndAddr() + _, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) + mapper.SetAccount(ctx, acc2) + + var tx sdk.Tx + + // test good tx and set public key + msg := newTestMsg(addr1) + msgs := []sdk.Msg{msg} + privs, accnums, seqs := []crypto.PrivKey{priv1}, []int64{0}, []int64{0} + fee := newStdFee() + tx = newTestTx(ctx, msgs, privs, accnums, seqs, fee) + checkValidTx(t, anteHandler, ctx, tx, false) + + acc1 = mapper.GetAccount(ctx, addr1) + require.Equal(t, acc1.GetPubKey(), priv1.PubKey()) + + // test public key not found + msg = newTestMsg(addr2) + msgs = []sdk.Msg{msg} + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + sigs := tx.(StdTx).GetSignatures() + sigs[0].PubKey = nil + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) + + acc2 = mapper.GetAccount(ctx, addr2) + require.Nil(t, acc2.GetPubKey()) + + // test invalid signature and public key + tx = newTestTx(ctx, msgs, privs, []int64{1}, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, false, sdk.CodeInvalidPubKey) + + acc2 = mapper.GetAccount(ctx, addr2) + require.Nil(t, acc2.GetPubKey()) +} + +func TestProcessPubKey(t *testing.T) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + // keys + _, addr1 := privAndAddr() + priv2, _ := privAndAddr() + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + type args struct { + acc Account + sig StdSignature + simulate bool + } + tests := []struct { + name string + args args + wantErr bool + }{ + {"no sigs, simulate off", args{acc1, StdSignature{}, false}, true}, + {"no sigs, simulate on", args{acc1, StdSignature{}, true}, false}, + {"pubkey doesn't match addr, simulate off", args{acc1, StdSignature{PubKey: priv2.PubKey()}, false}, true}, + {"pubkey doesn't match addr, simulate on", args{acc1, StdSignature{PubKey: priv2.PubKey()}, true}, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := processPubKey(tt.args.acc, tt.args.sig, tt.args.simulate) + require.Equal(t, tt.wantErr, !err.IsOK()) + }) + } +} + +func TestConsumeSignatureVerificationGas(t *testing.T) { + type args struct { + meter sdk.GasMeter + pubkey crypto.PubKey + } + tests := []struct { + name string + args args + gasConsumed int64 + wantPanic bool + }{ + {"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), ed25519.GenPrivKey().PubKey()}, ed25519VerifyCost, false}, + {"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), secp256k1.GenPrivKey().PubKey()}, secp256k1VerifyCost, false}, + {"unknown key", args{sdk.NewInfiniteGasMeter(), nil}, 0, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic { + require.Panics(t, func() { consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) }) + } else { + consumeSignatureVerificationGas(tt.args.meter, tt.args.pubkey) + require.Equal(t, tt.args.meter.GasConsumed(), tt.gasConsumed) + } + }) + } +} + +func TestAdjustFeesByGas(t *testing.T) { + type args struct { + fee sdk.Coins + gas int64 + } + tests := []struct { + name string + args args + want sdk.Coins + }{ + {"nil coins", args{sdk.Coins{}, 10000}, sdk.Coins{}}, + {"nil coins", args{sdk.Coins{sdk.NewInt64Coin("A", 10), sdk.NewInt64Coin("B", 0)}, 10000}, sdk.Coins{sdk.NewInt64Coin("A", 20), sdk.NewInt64Coin("B", 10)}}, + {"negative coins", args{sdk.Coins{sdk.NewInt64Coin("A", -10), sdk.NewInt64Coin("B", 10)}, 10000}, sdk.Coins{sdk.NewInt64Coin("B", 20)}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.True(t, tt.want.IsEqual(adjustFeesByGas(tt.args.fee, tt.args.gas))) + }) + } +} diff --git a/modules/auth/context_test.go b/modules/auth/context_test.go new file mode 100644 index 000000000..83e7ac08f --- /dev/null +++ b/modules/auth/context_test.go @@ -0,0 +1,40 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + + sdk "github.com/irisnet/irishub/types" +) + +func TestContextWithSigners(t *testing.T) { + ms, _, _ := setupMultiStore() + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, log.NewNopLogger()) + + _, _, addr1 := keyPubAddr() + _, _, addr2 := keyPubAddr() + acc1 := NewBaseAccountWithAddress(addr1) + acc1.SetSequence(7132) + acc2 := NewBaseAccountWithAddress(addr2) + acc2.SetSequence(8821) + + // new ctx has no signers + signers := GetSigners(ctx) + require.Equal(t, 0, len(signers)) + + ctx2 := WithSigners(ctx, []Account{&acc1, &acc2}) + + // original context is unchanged + signers = GetSigners(ctx) + require.Equal(t, 0, len(signers)) + + // new context has signers + signers = GetSigners(ctx2) + require.Equal(t, 2, len(signers)) + require.Equal(t, acc1, *(signers[0].(*BaseAccount))) + require.Equal(t, acc2, *(signers[1].(*BaseAccount))) +} diff --git a/modules/auth/feekeeper_test.go b/modules/auth/feekeeper_test.go new file mode 100644 index 000000000..b6eecc1c8 --- /dev/null +++ b/modules/auth/feekeeper_test.go @@ -0,0 +1,75 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + + codec "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" +) + +var ( + emptyCoins = sdk.Coins{} + oneCoin = sdk.Coins{sdk.NewInt64Coin("foocoin", 1)} + twoCoins = sdk.Coins{sdk.NewInt64Coin("foocoin", 2)} +) + +func TestFeeCollectionKeeperGetSet(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := codec.New() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // no coins initially + currFees := fck.GetCollectedFees(ctx) + require.True(t, currFees.IsEqual(emptyCoins)) + + // set feeCollection to oneCoin + fck.setCollectedFees(ctx, oneCoin) + + // check that it is equal to oneCoin + require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) +} + +func TestFeeCollectionKeeperAdd(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := codec.New() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // no coins initially + require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) + + // add oneCoin and check that pool is now oneCoin + fck.AddCollectedFees(ctx, oneCoin) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(oneCoin)) + + // add oneCoin again and check that pool is now twoCoins + fck.AddCollectedFees(ctx, oneCoin) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) +} + +func TestFeeCollectionKeeperClear(t *testing.T) { + ms, _, capKey2 := setupMultiStore() + cdc := codec.New() + + // make context and keeper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + fck := NewFeeCollectionKeeper(cdc, capKey2) + + // set coins initially + fck.setCollectedFees(ctx, twoCoins) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(twoCoins)) + + // clear fees and see that pool is now empty + fck.ClearCollectedFees(ctx) + require.True(t, fck.GetCollectedFees(ctx).IsEqual(emptyCoins)) +} diff --git a/modules/auth/keeper_test.go b/modules/auth/keeper_test.go new file mode 100644 index 000000000..d6a90f982 --- /dev/null +++ b/modules/auth/keeper_test.go @@ -0,0 +1,204 @@ +package auth + +import ( + "testing" + + codec "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/store" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) { + db := dbm.NewMemDB() + capKey := sdk.NewKVStoreKey("capkey") + capKey2 := sdk.NewKVStoreKey("capkey2") + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + return ms, capKey, capKey2 +} + +func TestAccountMapperGetSet(t *testing.T) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + addr := sdk.AccAddress([]byte("some-address")) + + // no account before its created + acc := mapper.GetAccount(ctx, addr) + require.Nil(t, acc) + + // create account and check default values + acc = mapper.NewAccountWithAddress(ctx, addr) + require.NotNil(t, acc) + require.Equal(t, addr, acc.GetAddress()) + require.EqualValues(t, nil, acc.GetPubKey()) + require.EqualValues(t, 0, acc.GetSequence()) + + // NewAccount doesn't call Set, so it's still nil + require.Nil(t, mapper.GetAccount(ctx, addr)) + + // set some values on the account and save it + newSequence := int64(20) + acc.SetSequence(newSequence) + mapper.SetAccount(ctx, acc) + + // check the new values + acc = mapper.GetAccount(ctx, addr) + require.NotNil(t, acc) + require.Equal(t, newSequence, acc.GetSequence()) +} + +func TestAccountMapperRemoveAccount(t *testing.T) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + addr1 := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + + // create accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + + accSeq1 := int64(20) + accSeq2 := int64(40) + + acc1.SetSequence(accSeq1) + acc2.SetSequence(accSeq2) + mapper.SetAccount(ctx, acc1) + mapper.SetAccount(ctx, acc2) + + acc1 = mapper.GetAccount(ctx, addr1) + require.NotNil(t, acc1) + require.Equal(t, accSeq1, acc1.GetSequence()) + + // remove one account + mapper.RemoveAccount(ctx, acc1) + acc1 = mapper.GetAccount(ctx, addr1) + require.Nil(t, acc1) + + acc2 = mapper.GetAccount(ctx, addr2) + require.NotNil(t, acc2) + require.Equal(t, accSeq2, acc2.GetSequence()) +} + +func BenchmarkAccountMapperGetAccountFound(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + mapper.SetAccount(ctx, acc) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + mapper.GetAccount(ctx, sdk.AccAddress(arr)) + } +} + +func BenchmarkAccountMapperGetAccountFoundWithCoins(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } + + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + acc.SetCoins(coins) + mapper.SetAccount(ctx, acc) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + mapper.GetAccount(ctx, sdk.AccAddress(arr)) + } +} + +func BenchmarkAccountMapperSetAccount(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + mapper.SetAccount(ctx, acc) + } +} + +func BenchmarkAccountMapperSetAccountWithCoins(b *testing.B) { + ms, capKey, _ := setupMultiStore() + cdc := codec.New() + RegisterBaseAccount(cdc) + + // make context and mapper + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + mapper := NewAccountKeeper(cdc, capKey, ProtoBaseAccount) + + coins := sdk.Coins{ + sdk.NewCoin("LTC", sdk.NewInt(1000)), + sdk.NewCoin("BTC", sdk.NewInt(1000)), + sdk.NewCoin("ETH", sdk.NewInt(1000)), + sdk.NewCoin("XRP", sdk.NewInt(1000)), + sdk.NewCoin("BCH", sdk.NewInt(1000)), + sdk.NewCoin("EOS", sdk.NewInt(1000)), + } + + b.ResetTimer() + // assumes b.N < 2**24 + for i := 0; i < b.N; i++ { + arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)} + addr := sdk.AccAddress(arr) + acc := mapper.NewAccountWithAddress(ctx, addr) + acc.SetCoins(coins) + mapper.SetAccount(ctx, acc) + } +} diff --git a/modules/auth/stdtx_test.go b/modules/auth/stdtx_test.go new file mode 100644 index 000000000..a65a4dc71 --- /dev/null +++ b/modules/auth/stdtx_test.go @@ -0,0 +1,53 @@ +package auth + +import ( + "fmt" + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +var ( + priv = ed25519.GenPrivKey() + addr = sdk.AccAddress(priv.PubKey().Address()) +) + +func TestStdTx(t *testing.T) { + msgs := []sdk.Msg{sdk.NewTestMsg(addr)} + fee := newStdFee() + sigs := []StdSignature{} + + tx := NewStdTx(msgs, fee, sigs, "") + require.Equal(t, msgs, tx.GetMsgs()) + require.Equal(t, sigs, tx.GetSignatures()) + + feePayer := tx.GetSigners()[0] + require.Equal(t, addr, feePayer) +} + +func TestStdSignBytes(t *testing.T) { + type args struct { + chainID string + accnum int64 + sequence int64 + fee StdFee + msgs []sdk.Msg + memo string + } + defaultFee := newStdFee() + tests := []struct { + args args + want string + }{ + { + args{"1234", 3, 6, defaultFee, []sdk.Msg{sdk.NewTestMsg(addr)}, "memo"}, + fmt.Sprintf("{\"account_number\":\"3\",\"chain_id\":\"1234\",\"fee\":{\"amount\":[{\"amount\":\"150\",\"denom\":\"atom\"}],\"gas\":\"5000\"},\"memo\":\"memo\",\"msgs\":[[\"%s\"]],\"sequence\":\"6\"}", addr), + }, + } + for i, tc := range tests { + got := string(StdSignBytes(tc.args.chainID, tc.args.accnum, tc.args.sequence, tc.args.fee, tc.args.msgs, tc.args.memo)) + require.Equal(t, tc.want, got, "Got unexpected result on test case i: %d", i) + } +} diff --git a/modules/bank/keeper_test.go b/modules/bank/keeper_test.go new file mode 100644 index 000000000..332353b08 --- /dev/null +++ b/modules/bank/keeper_test.go @@ -0,0 +1,206 @@ +package bank + +import ( + "testing" + + codec "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/store" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) { + db := dbm.NewMemDB() + authKey := sdk.NewKVStoreKey("authkey") + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + return ms, authKey +} + +func TestKeeper(t *testing.T) { + ms, authKey := setupMultiStore() + + cdc := codec.New() + auth.RegisterBaseAccount(cdc) + + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + addr3 := sdk.AccAddress([]byte("addr3")) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) + + // Test GetCoins/SetCoins + accountKeeper.SetAccount(ctx, acc) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + + // Test HasCoins + require.True(t, bankKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, bankKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + require.False(t, bankKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)})) + require.False(t, bankKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)})) + + // Test AddCoins + bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 25)})) + + bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 15)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 15), sdk.NewInt64Coin("foocoin", 25)})) + + // Test SubtractCoins + bankKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) + bankKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 15)})) + + bankKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 11)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 15)})) + + bankKeeper.SubtractCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 10)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 15)})) + require.False(t, bankKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 1)})) + + // Test SendCoins + bankKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + + _, err2 := bankKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 50)}) + assert.Implements(t, (*sdk.Error)(nil), err2) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + + bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 30)}) + bankKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 5)}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)})) + + // Test InputOutputCoins + input1 := NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)}) + output1 := NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)}) + bankKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 7)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 8)})) + + inputs := []Input{ + NewInput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 3)}), + NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("barcoin", 3), sdk.NewInt64Coin("foocoin", 2)}), + } + + outputs := []Output{ + NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 1)}), + NewOutput(addr3, sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)}), + } + bankKeeper.InputOutputCoins(ctx, inputs, outputs) + require.True(t, bankKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 21), sdk.NewInt64Coin("foocoin", 4)})) + require.True(t, bankKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 7), sdk.NewInt64Coin("foocoin", 6)})) + require.True(t, bankKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)})) + +} + +func TestSendKeeper(t *testing.T) { + ms, authKey := setupMultiStore() + + cdc := codec.New() + auth.RegisterBaseAccount(cdc) + + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + sendKeeper := NewBaseSendKeeper(accountKeeper) + + addr := sdk.AccAddress([]byte("addr1")) + addr2 := sdk.AccAddress([]byte("addr2")) + addr3 := sdk.AccAddress([]byte("addr3")) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) + + // Test GetCoins/SetCoins + accountKeeper.SetAccount(ctx, acc) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + + // Test HasCoins + require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)})) + require.False(t, sendKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)})) + + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)}) + + // Test SendCoins + sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + + _, err2 := sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 50)}) + assert.Implements(t, (*sdk.Error)(nil), err2) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + + bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 30)}) + sendKeeper.SendCoins(ctx, addr, addr2, sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 5)}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 5)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 10)})) + + // Test InputOutputCoins + input1 := NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)}) + output1 := NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 2)}) + sendKeeper.InputOutputCoins(ctx, []Input{input1}, []Output{output1}) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 20), sdk.NewInt64Coin("foocoin", 7)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 10), sdk.NewInt64Coin("foocoin", 8)})) + + inputs := []Input{ + NewInput(addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 3)}), + NewInput(addr2, sdk.Coins{sdk.NewInt64Coin("barcoin", 3), sdk.NewInt64Coin("foocoin", 2)}), + } + + outputs := []Output{ + NewOutput(addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 1)}), + NewOutput(addr3, sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)}), + } + sendKeeper.InputOutputCoins(ctx, inputs, outputs) + require.True(t, sendKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 21), sdk.NewInt64Coin("foocoin", 4)})) + require.True(t, sendKeeper.GetCoins(ctx, addr2).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 7), sdk.NewInt64Coin("foocoin", 6)})) + require.True(t, sendKeeper.GetCoins(ctx, addr3).IsEqual(sdk.Coins{sdk.NewInt64Coin("barcoin", 2), sdk.NewInt64Coin("foocoin", 5)})) + +} + +func TestViewKeeper(t *testing.T) { + ms, authKey := setupMultiStore() + + cdc := codec.New() + auth.RegisterBaseAccount(cdc) + + ctx := sdk.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + accountKeeper := auth.NewAccountKeeper(cdc, authKey, auth.ProtoBaseAccount) + bankKeeper := NewBaseKeeper(accountKeeper) + viewKeeper := NewBaseViewKeeper(accountKeeper) + + addr := sdk.AccAddress([]byte("addr1")) + acc := accountKeeper.NewAccountWithAddress(ctx, addr) + + // Test GetCoins/SetCoins + accountKeeper.SetAccount(ctx, acc) + require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{})) + + bankKeeper.SetCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)}) + require.True(t, viewKeeper.GetCoins(ctx, addr).IsEqual(sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + + // Test HasCoins + require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 10)})) + require.True(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 5)})) + require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("foocoin", 15)})) + require.False(t, viewKeeper.HasCoins(ctx, addr, sdk.Coins{sdk.NewInt64Coin("barcoin", 5)})) +} diff --git a/modules/bank/msgs_test.go b/modules/bank/msgs_test.go new file mode 100644 index 000000000..a899f8eb5 --- /dev/null +++ b/modules/bank/msgs_test.go @@ -0,0 +1,269 @@ +package bank + +import ( + "fmt" + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestNewMsgSend(t *testing.T) {} + +func TestMsgSendRoute(t *testing.T) { + // Construct a MsgSend + addr1 := sdk.AccAddress([]byte("input")) + addr2 := sdk.AccAddress([]byte("output")) + coins := sdk.Coins{sdk.NewInt64Coin("atom", 10)} + var msg = MsgSend{ + Inputs: []Input{NewInput(addr1, coins)}, + Outputs: []Output{NewOutput(addr2, coins)}, + } + + // TODO some failures for bad result + require.Equal(t, msg.Route(), "bank") +} + +func TestInputValidation(t *testing.T) { + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + someCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123)} + multiCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)} + + var emptyAddr sdk.AccAddress + emptyCoins := sdk.Coins{} + emptyCoins2 := sdk.Coins{sdk.NewInt64Coin("eth", 0)} + someEmptyCoins := sdk.Coins{sdk.NewInt64Coin("eth", 10), sdk.NewInt64Coin("atom", 0)} + minusCoins := sdk.Coins{sdk.NewInt64Coin("eth", -34)} + someMinusCoins := sdk.Coins{sdk.NewInt64Coin("atom", 20), sdk.NewInt64Coin("eth", -34)} + unsortedCoins := sdk.Coins{sdk.NewInt64Coin("eth", 1), sdk.NewInt64Coin("atom", 1)} + + cases := []struct { + valid bool + txIn Input + }{ + // auth works with different apps + {true, NewInput(addr1, someCoins)}, + {true, NewInput(addr2, someCoins)}, + {true, NewInput(addr2, multiCoins)}, + + {false, NewInput(emptyAddr, someCoins)}, // empty address + {false, NewInput(addr1, emptyCoins)}, // invalid coins + {false, NewInput(addr1, emptyCoins2)}, // invalid coins + {false, NewInput(addr1, someEmptyCoins)}, // invalid coins + {false, NewInput(addr1, minusCoins)}, // negative coins + {false, NewInput(addr1, someMinusCoins)}, // negative coins + {false, NewInput(addr1, unsortedCoins)}, // unsorted coins + } + + for i, tc := range cases { + err := tc.txIn.ValidateBasic() + if tc.valid { + require.Nil(t, err, "%d: %+v", i, err) + } else { + require.NotNil(t, err, "%d", i) + } + } +} + +func TestOutputValidation(t *testing.T) { + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + someCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123)} + multiCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20)} + + var emptyAddr sdk.AccAddress + emptyCoins := sdk.Coins{} + emptyCoins2 := sdk.Coins{sdk.NewInt64Coin("eth", 0)} + someEmptyCoins := sdk.Coins{sdk.NewInt64Coin("eth", 10), sdk.NewInt64Coin("atom", 0)} + minusCoins := sdk.Coins{sdk.NewInt64Coin("eth", -34)} + someMinusCoins := sdk.Coins{sdk.NewInt64Coin("atom", 20), sdk.NewInt64Coin("eth", -34)} + unsortedCoins := sdk.Coins{sdk.NewInt64Coin("eth", 1), sdk.NewInt64Coin("atom", 1)} + + cases := []struct { + valid bool + txOut Output + }{ + // auth works with different apps + {true, NewOutput(addr1, someCoins)}, + {true, NewOutput(addr2, someCoins)}, + {true, NewOutput(addr2, multiCoins)}, + + {false, NewOutput(emptyAddr, someCoins)}, // empty address + {false, NewOutput(addr1, emptyCoins)}, // invalid coins + {false, NewOutput(addr1, emptyCoins2)}, // invalid coins + {false, NewOutput(addr1, someEmptyCoins)}, // invalid coins + {false, NewOutput(addr1, minusCoins)}, // negative coins + {false, NewOutput(addr1, someMinusCoins)}, // negative coins + {false, NewOutput(addr1, unsortedCoins)}, // unsorted coins + } + + for i, tc := range cases { + err := tc.txOut.ValidateBasic() + if tc.valid { + require.Nil(t, err, "%d: %+v", i, err) + } else { + require.NotNil(t, err, "%d", i) + } + } +} + +func TestMsgSendValidation(t *testing.T) { + addr1 := sdk.AccAddress([]byte{1, 2}) + addr2 := sdk.AccAddress([]byte{7, 8}) + atom123 := sdk.Coins{sdk.NewInt64Coin("atom", 123)} + atom124 := sdk.Coins{sdk.NewInt64Coin("atom", 124)} + eth123 := sdk.Coins{sdk.NewInt64Coin("eth", 123)} + atom123eth123 := sdk.Coins{sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 123)} + + input1 := NewInput(addr1, atom123) + input2 := NewInput(addr1, eth123) + output1 := NewOutput(addr2, atom123) + output2 := NewOutput(addr2, atom124) + output3 := NewOutput(addr2, eth123) + outputMulti := NewOutput(addr2, atom123eth123) + + var emptyAddr sdk.AccAddress + + cases := []struct { + valid bool + tx MsgSend + }{ + {false, MsgSend{}}, // no input or output + {false, MsgSend{Inputs: []Input{input1}}}, // just input + {false, MsgSend{Outputs: []Output{output1}}}, // just output + {false, MsgSend{ + Inputs: []Input{NewInput(emptyAddr, atom123)}, // invalid input + Outputs: []Output{output1}}}, + {false, MsgSend{ + Inputs: []Input{input1}, + Outputs: []Output{{emptyAddr, atom123}}}, // invalid output + }, + {false, MsgSend{ + Inputs: []Input{input1}, + Outputs: []Output{output2}}, // amounts dont match + }, + {false, MsgSend{ + Inputs: []Input{input1}, + Outputs: []Output{output3}}, // amounts dont match + }, + {false, MsgSend{ + Inputs: []Input{input1}, + Outputs: []Output{outputMulti}}, // amounts dont match + }, + {false, MsgSend{ + Inputs: []Input{input2}, + Outputs: []Output{output1}}, // amounts dont match + }, + + {true, MsgSend{ + Inputs: []Input{input1}, + Outputs: []Output{output1}}, + }, + {true, MsgSend{ + Inputs: []Input{input1, input2}, + Outputs: []Output{outputMulti}}, + }, + } + + for i, tc := range cases { + err := tc.tx.ValidateBasic() + if tc.valid { + require.Nil(t, err, "%d: %+v", i, err) + } else { + require.NotNil(t, err, "%d", i) + } + } +} + +func TestMsgSendGetSignBytes(t *testing.T) { + addr1 := sdk.AccAddress([]byte("input")) + addr2 := sdk.AccAddress([]byte("output")) + coins := sdk.Coins{sdk.NewInt64Coin("atom", 10)} + var msg = MsgSend{ + Inputs: []Input{NewInput(addr1, coins)}, + Outputs: []Output{NewOutput(addr2, coins)}, + } + res := msg.GetSignBytes() + + expected := `{"inputs":[{"address":"faa1d9h8qat5umnd2g","coins":[{"amount":"10","denom":"atom"}]}],"outputs":[{"address":"faa1da6hgur4wstza6a7","coins":[{"amount":"10","denom":"atom"}]}]}` + require.Equal(t, expected, string(res)) +} + +func TestMsgSendGetSigners(t *testing.T) { + var msg = MsgSend{ + Inputs: []Input{ + NewInput(sdk.AccAddress([]byte("input1")), nil), + NewInput(sdk.AccAddress([]byte("input2")), nil), + NewInput(sdk.AccAddress([]byte("input3")), nil), + }, + } + res := msg.GetSigners() + // TODO: fix this ! + require.Equal(t, fmt.Sprintf("%v", res), "[696E70757431 696E70757432 696E70757433]") +} + +/* +// what to do w/ this test? +func TestMsgSendSigners(t *testing.T) { + signers := []sdk.AccAddress{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + } + + someCoins := sdk.Coins{sdk.NewInt64Coin("atom", 123)} + inputs := make([]Input, len(signers)) + for i, signer := range signers { + inputs[i] = NewInput(signer, someCoins) + } + tx := NewMsgSend(inputs, nil) + + require.Equal(t, signers, tx.Signers()) +} +*/ + +// ---------------------------------------- +// MsgIssue Tests + +func TestNewMsgIssue(t *testing.T) { + // TODO +} + +func TestMsgIssueRoute(t *testing.T) { + // Construct an MsgIssue + addr := sdk.AccAddress([]byte("loan-from-bank")) + coins := sdk.Coins{sdk.NewInt64Coin("atom", 10)} + var msg = MsgIssue{ + Banker: sdk.AccAddress([]byte("input")), + Outputs: []Output{NewOutput(addr, coins)}, + } + + // TODO some failures for bad result + require.Equal(t, msg.Route(), "bank") +} + +func TestMsgIssueValidation(t *testing.T) { + // TODO +} + +func TestMsgIssueGetSignBytes(t *testing.T) { + addr := sdk.AccAddress([]byte("loan-from-bank")) + coins := sdk.Coins{sdk.NewInt64Coin("atom", 10)} + var msg = MsgIssue{ + Banker: sdk.AccAddress([]byte("input")), + Outputs: []Output{NewOutput(addr, coins)}, + } + res := msg.GetSignBytes() + + expected := `{"banker":"faa1d9h8qat5umnd2g","outputs":[{"address":"faa1d3hkzm3dveex7mfdvfsku6crf5s7g","coins":[{"amount":"10","denom":"atom"}]}]}` + require.Equal(t, expected, string(res)) +} + +func TestMsgIssueGetSigners(t *testing.T) { + var msg = MsgIssue{ + Banker: sdk.AccAddress([]byte("onlyone")), + } + res := msg.GetSigners() + require.Equal(t, fmt.Sprintf("%v", res), "[6F6E6C796F6E65]") +} diff --git a/simulation/bank/invariants.go b/modules/bank/simulation/invariants.go similarity index 93% rename from simulation/bank/invariants.go rename to modules/bank/simulation/invariants.go index ffed586ed..6705aac30 100644 --- a/simulation/bank/invariants.go +++ b/modules/bank/simulation/invariants.go @@ -6,8 +6,8 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/auth" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" abci "github.com/tendermint/tendermint/abci/types" "github.com/irisnet/irishub/baseapp" diff --git a/simulation/bank/msgs.go b/modules/bank/simulation/msgs.go similarity index 98% rename from simulation/bank/msgs.go rename to modules/bank/simulation/msgs.go index eb1dbb220..db7168065 100644 --- a/simulation/bank/msgs.go +++ b/modules/bank/simulation/msgs.go @@ -9,8 +9,8 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/auth" "github.com/irisnet/irishub/modules/bank" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" "github.com/tendermint/tendermint/crypto" "github.com/irisnet/irishub/baseapp" diff --git a/simulation/bank/sim_test.go b/modules/bank/simulation/sim_test.go similarity index 79% rename from simulation/bank/sim_test.go rename to modules/bank/simulation/sim_test.go index 4a4672b99..de42eb644 100644 --- a/simulation/bank/sim_test.go +++ b/modules/bank/simulation/sim_test.go @@ -7,8 +7,9 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/bank" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) func TestBankWithRandomMessages(t *testing.T) { @@ -26,7 +27,7 @@ func TestBankWithRandomMessages(t *testing.T) { } appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { - simulation.RandomSetGenesis(r, mapp, accs, []string{"iris-atto"}) + simulation.RandomSetGenesis(r, mapp, accs, []string{stakeTypes.StakeDenom}) return json.RawMessage("{}") } diff --git a/modules/distribution/keeper/allocation_test.go b/modules/distribution/keeper/allocation_test.go new file mode 100644 index 000000000..6fe3569d7 --- /dev/null +++ b/modules/distribution/keeper/allocation_test.go @@ -0,0 +1,110 @@ +package keeper + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAllocateTokensBasic(t *testing.T) { + + // no community tax on inputs + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(10) + totalPowerDec := sdk.NewDecFromInt(sdk.NewIntWithDecimal(totalPower, 18)) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(totalPower, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // verify everything has been set in staking correctly + validator, found := sk.GetValidator(ctx, valOpAddr1) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status) + assert.True(sdk.DecEq(t, totalPowerDec, validator.Tokens)) + assert.True(sdk.DecEq(t, totalPowerDec, validator.DelegatorShares)) + bondedTokens := sk.TotalPower(ctx) + assert.True(sdk.DecEq(t, totalPowerDec, bondedTokens)) + + // initial fee pool should be empty + feePool := keeper.GetFeePool(ctx) + require.Nil(t, feePool.ValPool) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // verify that these fees have been received by the feePool + percentProposer := sdk.NewDecWithPrec(5, 2) + percentRemaining := sdk.OneDec().Sub(percentProposer) + feePool = keeper.GetFeePool(ctx) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.ValPool)) + require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) +} + +func TestAllocateTokensWithCommunityTax(t *testing.T) { + communityTax := sdk.NewDecWithPrec(1, 2) //1% + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), communityTax) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(10) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(totalPower, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // verify that these fees have been received by the feePool + feePool := keeper.GetFeePool(ctx) + // 5% goes to proposer, 1% community tax + percentProposer := sdk.NewDecWithPrec(5, 2) + percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.ValPool)) + require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) +} + +func TestAllocateTokensWithPartialPrecommitPower(t *testing.T) { + communityTax := sdk.NewDecWithPrec(1, 2) + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), communityTax) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(100) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(totalPower, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + percentPrecommitVotes := sdk.NewDecWithPrec(25, 2) + keeper.AllocateTokens(ctx, percentPrecommitVotes, valConsAddr1) + + // verify that these fees have been received by the feePool + feePool := keeper.GetFeePool(ctx) + // 1% + 4%*0.25 to proposer + 1% community tax = 97% + percentProposer := sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(4, 2).Mul(percentPrecommitVotes)) + percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.ValPool)) + require.True(sdk.DecEq(t, expRes, feePool.ValPool[0].Amount)) +} diff --git a/modules/distribution/keeper/delegation_test.go b/modules/distribution/keeper/delegation_test.go new file mode 100644 index 000000000..d3b9e3b9d --- /dev/null +++ b/modules/distribution/keeper/delegation_test.go @@ -0,0 +1,253 @@ +package keeper + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" + "github.com/stretchr/testify/require" +) + +func TestWithdrawDelegationRewardBasic(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + sk.SetLastTotalPower(ctx, sdk.NewInt(10)) + sk.SetLastValidatorPower(ctx, valOpAddr1, sdk.NewInt(10)) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawDelegationRewardWithCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100*90% tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, sdk.NewIntWithDecimal(20, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(80, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // delegator 1 withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Quo(sdk.NewDec(4))).TruncateInt() // 90 + 100*90% tokens * 10/40 + require.True(sdk.IntEq(t, expRes, amt)) +} + +// this test demonstrates how two delegators with the same power can end up +// with different rewards in the end +func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with no commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.ZeroDec()) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(90, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + ctx = ctx.WithBlockHeight(1) + + // delegator 1 withdraw delegation early, delegator 2 just keeps it's accum + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes1 := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Quo(sdk.NewDec(3))).TruncateInt() // 90 + 100 * 10/30 + require.True(sdk.IntEq(t, expRes1, amt)) + + // allocate 200 denom of fees + feeInputs = sdk.NewIntWithDecimal(180, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + ctx = ctx.WithBlockHeight(2) + + // delegator 2 now withdraws everything it's entitled to + keeper.WithdrawDelegationReward(ctx, delAddr2, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + // existingTokens + (100+200 * (10/(20+30)) + withdrawnFromVal := sdk.NewDecFromInt(sdk.NewIntWithDecimal(60, 18).Add(sdk.NewIntWithDecimal(180, 18))).Mul(sdk.NewDec(2)).Quo(sdk.NewDec(5)) + expRes2 := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(withdrawnFromVal).TruncateInt() + require.True(sdk.IntEq(t, expRes2, amt)) + + // finally delegator 1 withdraws the remainder of its reward + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + remainingInVal := sdk.NewDecFromInt(sdk.NewIntWithDecimal(60, 18).Add(sdk.NewIntWithDecimal(180, 18))).Sub(withdrawnFromVal) + expRes3 := sdk.NewDecFromInt(expRes1).Add(remainingInVal.Mul(sdk.NewDec(1)).Quo(sdk.NewDec(3))).TruncateInt() + require.True(sdk.IntEq(t, expRes3, amt)) + + // verify the final withdraw amounts are different + require.True(t, expRes2.GT(expRes3)) +} + +func TestWithdrawDelegationRewardsAll(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //make some validators with different commissions + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr2, valConsPk2, sdk.NewIntWithDecimal(50, 18), sdk.NewDecWithPrec(2, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr3, valConsPk3, sdk.NewIntWithDecimal(40, 18), sdk.NewDecWithPrec(3, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + // delegate to all the validators + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr2, sdk.NewIntWithDecimal(20, 18)) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr3, sdk.NewIntWithDecimal(30, 18)) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + + // Update sk's LastValidatorPower/LastTotalPowers. + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // 40 tokens left after delegating 60 of them + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(40, 18), amt) + + // total power of each validator: + // validator 1: 10 (self) + 10 (delegator) = 20 + // validator 2: 50 (self) + 20 (delegator) = 70 + // validator 3: 40 (self) + 30 (delegator) = 70 + // grand total: 160 + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(1000, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationRewardsAll(ctx, delAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + // orig-amount + fees *(1-proposerReward)* (val1Portion * delegatorPotion * (1-val1Commission) ... etc) + // + fees *(proposerReward) * (delegatorPotion * (1-val1Commission)) + // 40 + 1000 *(1- 0.95)* (20/160 * 10/20 * 0.9 + 70/160 * 20/70 * 0.8 + 70/160 * 30/70 * 0.7) + // 40 + 1000 *( 0.05) * (10/20 * 0.9) + feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) + feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) + feesInVal1 := feesInNonProposer.Mul(sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)).Quo(sdk.NewDecFromInt(sdk.NewIntWithDecimal(160, 18)))).Mul(sdk.NewDecWithPrec(9, 1)) + feesInVal2 := feesInNonProposer.Mul(sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)).Quo(sdk.NewDecFromInt(sdk.NewIntWithDecimal(160, 18)))).Mul(sdk.NewDecWithPrec(8, 1)) + feesInVal3 := feesInNonProposer.Mul(sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18)).Quo(sdk.NewDecFromInt(sdk.NewIntWithDecimal(160, 18)))).Mul(sdk.NewDecWithPrec(7, 1)) + feesInVal1Proposer := feesInProposer.Mul(sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)).Quo(sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)))).Mul(sdk.NewDecWithPrec(9, 1)) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(40, 18)).Add(feesInVal1).Add(feesInVal2).Add(feesInVal3).Add(feesInVal1Proposer).TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} diff --git a/modules/distribution/keeper/keeper_test.go b/modules/distribution/keeper/keeper_test.go new file mode 100644 index 000000000..f7d188a8b --- /dev/null +++ b/modules/distribution/keeper/keeper_test.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/distribution/types" + "github.com/stretchr/testify/require" +) + +func TestSetGetPreviousProposerConsAddr(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, sdk.ZeroInt()) + + keeper.SetPreviousProposerConsAddr(ctx, valConsAddr1) + res := keeper.GetPreviousProposerConsAddr(ctx) + require.True(t, res.Equals(valConsAddr1), "expected: %v got: %v", valConsAddr1.String(), res.String()) +} + +func TestSetGetCommunityTax(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, sdk.ZeroInt()) + + someDec := sdk.NewDec(333) + keeper.SetCommunityTax(ctx, someDec) + res := keeper.GetCommunityTax(ctx) + require.True(sdk.DecEq(t, someDec, res)) +} + +func TestSetGetFeePool(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, sdk.ZeroInt()) + + fp := types.InitialFeePool() + fp.TotalValAccum.UpdateHeight = 777 + + keeper.SetFeePool(ctx, fp) + res := keeper.GetFeePool(ctx) + require.Equal(t, fp.TotalValAccum, res.TotalValAccum) +} diff --git a/modules/distribution/keeper/test_common.go b/modules/distribution/keeper/test_common.go index 70d79c17a..73aff71cb 100644 --- a/modules/distribution/keeper/test_common.go +++ b/modules/distribution/keeper/test_common.go @@ -71,7 +71,7 @@ func MakeTestCodec() *codec.Codec { } // test input with default values -func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( +func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins sdk.Int) ( sdk.Context, auth.AccountKeeper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { communityTax := sdk.NewDecWithPrec(2, 2) @@ -79,7 +79,7 @@ func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( } // hogpodge of all sorts of input required for testing -func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, +func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins sdk.Int, communityTax sdk.Dec) ( sdk.Context, auth.AccountKeeper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { @@ -119,10 +119,10 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, for _, addr := range addrs { pool := sk.GetPool(ctx) _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ - {sk.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, + {sk.GetParams(ctx).BondDenom, initCoins}, }) require.Nil(t, err) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDecFromInt(initCoins)) sk.SetPool(ctx, pool) } diff --git a/modules/distribution/keeper/validator_test.go b/modules/distribution/keeper/validator_test.go new file mode 100644 index 000000000..8cf1ed9f9 --- /dev/null +++ b/modules/distribution/keeper/validator_test.go @@ -0,0 +1,192 @@ +package keeper + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" + "github.com/stretchr/testify/require" +) + +func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw self-delegation reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18))).TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw self-delegation reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + commissionRate := sdk.NewDecWithPrec(1, 1) + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), commissionRate) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + commissionTaken := sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Mul(commissionRate) + afterCommission := sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Sub(commissionTaken) + selfDelegationReward := afterCommission.Quo(sdk.NewDec(2)) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)).Add(commissionTaken).Add(selfDelegationReward).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //make some validators with different commissions + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr2, valConsPk2, sdk.NewIntWithDecimal(50, 18), sdk.NewDecWithPrec(2, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr3, valConsPk3, sdk.NewIntWithDecimal(40, 18), sdk.NewDecWithPrec(3, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(1000, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + + feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) + feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)). // orig tokens (100 - 10) + Add(feesInNonProposer.Quo(sdk.NewDec(10))). // validator 1 has 1/10 total power + Add(feesInProposer). + TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, sdk.NewIntWithDecimal(100, 18), sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + commissionRate := sdk.NewDecWithPrec(1, 1) + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, sdk.NewIntWithDecimal(10, 18), sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, sdk.NewIntWithDecimal(10, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(90, 18), amt) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, sdk.NewIntWithDecimal(20, 18)) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, sdk.NewIntWithDecimal(80, 18), amt) + + // allocate 100 denom of fees + feeInputs := sdk.NewIntWithDecimal(100, 18) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateTokens(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + + commissionTaken := sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Mul(commissionRate) + afterCommission := sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)).Sub(commissionTaken) + expRes := sdk.NewDecFromInt(sdk.NewIntWithDecimal(90, 18)). + Add(afterCommission.Quo(sdk.NewDec(4))). + Add(commissionTaken). + TruncateInt() // 90 + 100*90% tokens * 10/40 + require.True(sdk.IntEq(t, expRes, amt)) +} diff --git a/modules/distribution/simulation/invariants.go b/modules/distribution/simulation/invariants.go new file mode 100644 index 000000000..9503981ed --- /dev/null +++ b/modules/distribution/simulation/invariants.go @@ -0,0 +1,50 @@ +package simulation + +import ( + "fmt" + + "github.com/irisnet/irishub/baseapp" + sdk "github.com/irisnet/irishub/types" + distr "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/modules/mock/simulation" + abci "github.com/tendermint/tendermint/abci/types" +) + +// AllInvariants runs all invariants of the distribution module +// Currently: total supply, positive power +func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { + err := ValAccumInvariants(d, sk)(app) + if err != nil { + return err + } + return nil + } +} + +// ValAccumInvariants checks that the fee pool accum == sum all validators' accum +func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { + + return func(app *baseapp.BaseApp) error { + mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} + ctx := app.NewContext(false, mockHeader) + height := ctx.BlockHeight() + + valAccum := sdk.ZeroDec() + k.IterateValidatorDistInfos(ctx, func(_ int64, vdi distr.ValidatorDistInfo) bool { + lastValPower := sk.GetLastValidatorPower(ctx, vdi.OperatorAddr) + valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower))) + return false + }) + + lastTotalPower := sdk.NewDecFromInt(sk.GetLastTotalPower(ctx)) + totalAccum := k.GetFeePool(ctx).GetTotalValAccum(height, lastTotalPower) + + if !totalAccum.Equal(valAccum) { + return fmt.Errorf("validator accum invariance: \n\tfee pool totalAccum: %v"+ + "\n\tvalidator accum \t%v\n", totalAccum.String(), valAccum.String()) + } + + return nil + } +} diff --git a/modules/distribution/simulation/msgs.go b/modules/distribution/simulation/msgs.go new file mode 100644 index 000000000..0cf4d20f2 --- /dev/null +++ b/modules/distribution/simulation/msgs.go @@ -0,0 +1,122 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/irisnet/irishub/baseapp" + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/distribution" + "github.com/irisnet/irishub/modules/mock/simulation" +) + +// SimulateMsgSetWithdrawAddress +func SimulateMsgSetWithdrawAddress(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { + handler := distribution.NewHandler(k) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { + + accountOrigin := simulation.RandomAcc(r, accs) + accountDestination := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgSetWithdrawAddress(accountOrigin.Address, accountDestination.Address) + + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + write() + } + + event(fmt.Sprintf("distribution/MsgSetWithdrawAddress/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgSetWithdrawAddress: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// SimulateMsgWithdrawDelegatorRewardsAll +func SimulateMsgWithdrawDelegatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { + handler := distribution.NewHandler(k) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { + + account := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgWithdrawDelegatorRewardsAll(account.Address) + + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + write() + } + + event(fmt.Sprintf("distribution/MsgWithdrawDelegatorRewardsAll/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgWithdrawDelegatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// SimulateMsgWithdrawDelegatorReward +func SimulateMsgWithdrawDelegatorReward(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { + handler := distribution.NewHandler(k) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { + + delegatorAccount := simulation.RandomAcc(r, accs) + validatorAccount := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgWithdrawDelegatorReward(delegatorAccount.Address, sdk.ValAddress(validatorAccount.Address)) + + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + write() + } + + event(fmt.Sprintf("distribution/MsgWithdrawDelegatorReward/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgWithdrawDelegatorReward: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} + +// SimulateMsgWithdrawValidatorRewardsAll +func SimulateMsgWithdrawValidatorRewardsAll(m auth.AccountKeeper, k distribution.Keeper) simulation.Operation { + handler := distribution.NewHandler(k) + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { + + account := simulation.RandomAcc(r, accs) + msg := distribution.NewMsgWithdrawValidatorRewardsAll(sdk.ValAddress(account.Address)) + + if msg.ValidateBasic() != nil { + return "", nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + write() + } + + event(fmt.Sprintf("distribution/MsgWithdrawValidatorRewardsAll/%v", result.IsOK())) + + action = fmt.Sprintf("TestMsgWithdrawValidatorRewardsAll: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) + return action, nil, nil + } +} diff --git a/modules/distribution/types/dec_coin_test.go b/modules/distribution/types/dec_coin_test.go new file mode 100644 index 000000000..b338a7c6d --- /dev/null +++ b/modules/distribution/types/dec_coin_test.go @@ -0,0 +1,49 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPlusDecCoin(t *testing.T) { + decCoinA1 := DecCoin{"A", sdk.NewDecWithPrec(11, 1)} + decCoinA2 := DecCoin{"A", sdk.NewDecWithPrec(22, 1)} + decCoinB1 := DecCoin{"B", sdk.NewDecWithPrec(11, 1)} + + // regular add + res := decCoinA1.Plus(decCoinA1) + require.Equal(t, decCoinA2, res, "sum of coins is incorrect") + + // bad denom add + assert.Panics(t, func() { + decCoinA1.Plus(decCoinB1) + }, "expected panic on sum of different denoms") + +} + +func TestPlusCoins(t *testing.T) { + one := sdk.NewDec(1) + zero := sdk.NewDec(0) + negone := sdk.NewDec(-1) + two := sdk.NewDec(2) + + cases := []struct { + inputOne DecCoins + inputTwo DecCoins + expected DecCoins + }{ + {DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", one}, {"B", one}}, DecCoins{{"A", two}, {"B", two}}}, + {DecCoins{{"A", zero}, {"B", one}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"B", one}}}, + {DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins(nil)}, + {DecCoins{{"A", one}, {"B", zero}}, DecCoins{{"A", negone}, {"B", zero}}, DecCoins(nil)}, + {DecCoins{{"A", negone}, {"B", zero}}, DecCoins{{"A", zero}, {"B", zero}}, DecCoins{{"A", negone}}}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Plus(tc.inputTwo) + require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) + } +} diff --git a/modules/distribution/types/delegator_info_test.go b/modules/distribution/types/delegator_info_test.go new file mode 100644 index 000000000..9cceb20a0 --- /dev/null +++ b/modules/distribution/types/delegator_info_test.go @@ -0,0 +1,60 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/assert" +) + +func TestWithdrawRewards(t *testing.T) { + + // initialize + height := int64(0) + fp := InitialFeePool() + vi := NewValidatorDistInfo(valAddr1, height) + commissionRate := sdk.NewDecWithPrec(2, 2) + validatorTokens := sdk.NewDec(10) + validatorDelShares := sdk.NewDec(10) + totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power + + di1 := NewDelegationDistInfo(delAddr1, valAddr1, height) + di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator + + di2 := NewDelegationDistInfo(delAddr2, valAddr1, height) + di2Shares := sdk.NewDec(5) + + // simulate adding some stake for inflation + height = 10 + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} + + // withdraw rewards + wc := NewWithdrawContext(fp, height, + totalBondedTokens, validatorTokens, commissionRate) + di1, vi, fp, rewardRecv1 := di1.WithdrawRewards(wc, vi, + validatorDelShares, di1Shares) + + assert.Equal(t, height, di1.DelPoolWithdrawalHeight) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(49), rewardRecv1[0].Amount)) + + // add more blocks and inflation + height = 20 + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) + + // withdraw rewards + wc = NewWithdrawContext(fp, height, + totalBondedTokens, validatorTokens, commissionRate) + di2, vi, fp, rewardRecv2 := di2.WithdrawRewards(wc, vi, + validatorDelShares, di2Shares) + + assert.Equal(t, height, di2.DelPoolWithdrawalHeight) + assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.ValCommission[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount)) +} diff --git a/modules/distribution/types/fee_pool_test.go b/modules/distribution/types/fee_pool_test.go new file mode 100644 index 000000000..af113ceff --- /dev/null +++ b/modules/distribution/types/fee_pool_test.go @@ -0,0 +1,19 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestUpdateTotalValAccum(t *testing.T) { + + fp := InitialFeePool() + + fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3)) + require.True(sdk.DecEq(t, sdk.NewDec(15), fp.TotalValAccum.Accum)) + + fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2)) + require.True(sdk.DecEq(t, sdk.NewDec(21), fp.TotalValAccum.Accum)) +} diff --git a/modules/distribution/types/msg_test.go b/modules/distribution/types/msg_test.go new file mode 100644 index 000000000..f8a2a09f8 --- /dev/null +++ b/modules/distribution/types/msg_test.go @@ -0,0 +1,93 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgSetWithdrawAddress(t *testing.T) { + tests := []struct { + delegatorAddr sdk.AccAddress + withdrawAddr sdk.AccAddress + expectPass bool + }{ + {delAddr1, delAddr2, true}, + {delAddr1, delAddr1, true}, + {emptyDelAddr, delAddr1, false}, + {delAddr1, emptyDelAddr, false}, + {emptyDelAddr, emptyDelAddr, false}, + } + + for i, tc := range tests { + msg := NewMsgSetWithdrawAddress(tc.delegatorAddr, tc.withdrawAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) + } + } +} + +// test ValidateBasic for MsgEditValidator +func TestMsgWithdrawDelegatorReward(t *testing.T) { + tests := []struct { + delegatorAddr sdk.AccAddress + validatorAddr sdk.ValAddress + expectPass bool + }{ + {delAddr1, valAddr1, true}, + {emptyDelAddr, valAddr1, false}, + {delAddr1, emptyValAddr, false}, + {emptyDelAddr, emptyValAddr, false}, + } + for i, tc := range tests { + msg := NewMsgWithdrawDelegatorReward(tc.delegatorAddr, tc.validatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) + } + } +} + +// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf +func TestMsgWithdrawDelegatorRewardsAll(t *testing.T) { + tests := []struct { + delegatorAddr sdk.AccAddress + expectPass bool + }{ + {delAddr1, true}, + {emptyDelAddr, false}, + } + for i, tc := range tests { + msg := NewMsgWithdrawDelegatorRewardsAll(tc.delegatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) + } + } +} + +// test ValidateBasic for MsgDelegate +func TestMsgWithdrawValidatorRewardsAll(t *testing.T) { + tests := []struct { + validatorAddr sdk.ValAddress + expectPass bool + }{ + {valAddr1, true}, + {emptyValAddr, false}, + } + for i, tc := range tests { + msg := NewMsgWithdrawValidatorRewardsAll(tc.validatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) + } + } +} diff --git a/modules/distribution/types/total_accum_test.go b/modules/distribution/types/total_accum_test.go new file mode 100644 index 000000000..2e86353d7 --- /dev/null +++ b/modules/distribution/types/total_accum_test.go @@ -0,0 +1,19 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestTotalAccumUpdateForNewHeight(t *testing.T) { + + ta := NewTotalAccum(0) + + ta = ta.UpdateForNewHeight(5, sdk.NewDec(3)) + require.True(sdk.DecEq(t, sdk.NewDec(15), ta.Accum)) + + ta = ta.UpdateForNewHeight(8, sdk.NewDec(2)) + require.True(sdk.DecEq(t, sdk.NewDec(21), ta.Accum)) +} diff --git a/modules/distribution/types/validator_info_test.go b/modules/distribution/types/validator_info_test.go new file mode 100644 index 000000000..ef5c8130c --- /dev/null +++ b/modules/distribution/types/validator_info_test.go @@ -0,0 +1,90 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTakeFeePoolRewards(t *testing.T) { + + // initialize + height := int64(0) + fp := InitialFeePool() + vi1 := NewValidatorDistInfo(valAddr1, height) + vi2 := NewValidatorDistInfo(valAddr2, height) + vi3 := NewValidatorDistInfo(valAddr3, height) + commissionRate1 := sdk.NewDecWithPrec(2, 2) + commissionRate2 := sdk.NewDecWithPrec(3, 2) + commissionRate3 := sdk.NewDecWithPrec(4, 2) + validatorTokens1 := sdk.NewDec(10) + validatorTokens2 := sdk.NewDec(40) + validatorTokens3 := sdk.NewDec(50) + totalBondedTokens := validatorTokens1.Add(validatorTokens2).Add(validatorTokens3) + + // simulate adding some stake for inflation + height = 10 + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} + + vi1, fp = vi1.TakeFeePoolRewards(NewWithdrawContext( + fp, height, totalBondedTokens, validatorTokens1, commissionRate1)) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.ValCommission[0].Amount)) + + vi2, fp = vi2.TakeFeePoolRewards(NewWithdrawContext( + fp, height, totalBondedTokens, validatorTokens2, commissionRate2)) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, vi2.ValCommission[0].Amount, sdk.NewDec(12))) + + // add more blocks and inflation + height = 20 + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) + + vi3, fp = vi3.TakeFeePoolRewards(NewWithdrawContext( + fp, height, totalBondedTokens, validatorTokens3, commissionRate3)) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, vi3.ValCommission[0].Amount, sdk.NewDec(40))) +} + +func TestWithdrawCommission(t *testing.T) { + + // initialize + height := int64(0) + fp := InitialFeePool() + vi := NewValidatorDistInfo(valAddr1, height) + commissionRate := sdk.NewDecWithPrec(2, 2) + validatorTokens := sdk.NewDec(10) + totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power + + // simulate adding some stake for inflation + height = 10 + fp.ValPool = DecCoins{NewDecCoin("stake", 1000)} + + // for a more fun staring condition, have an non-withdraw update + vi, fp = vi.TakeFeePoolRewards(NewWithdrawContext( + fp, height, totalBondedTokens, validatorTokens, commissionRate)) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.ValCommission[0].Amount)) + + // add more blocks and inflation + height = 20 + fp.ValPool[0].Amount = fp.ValPool[0].Amount.Add(sdk.NewDec(1000)) + + vi, fp, commissionRecv := vi.WithdrawCommission(NewWithdrawContext( + fp, height, totalBondedTokens, validatorTokens, commissionRate)) + require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.DelPool[0].Amount)) + assert.Zero(t, len(vi.ValCommission)) + assert.True(sdk.DecEq(t, sdk.NewDec(4), commissionRecv[0].Amount)) +} diff --git a/modules/gov/params/gov_params.go b/modules/gov/params/gov_params.go index 93b5cda85..755a19d4e 100644 --- a/modules/gov/params/gov_params.go +++ b/modules/gov/params/gov_params.go @@ -9,6 +9,7 @@ import ( "github.com/irisnet/irishub/types" "strconv" "time" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) var DepositProcedureParameter DepositProcedureParam @@ -44,7 +45,7 @@ func (param *DepositProcedureParam) InitGenesis(genesisState interface{}) { if value, ok := genesisState.(DepositProcedure); ok { param.Value = value } else { - var minDeposit, _ = types.NewDefaultCoinType("iris").ConvertToMinCoin(fmt.Sprintf("%d%s", 10, "iris")) + var minDeposit, _ = types.NewDefaultCoinType(stakeTypes.StakeDenomName).ConvertToMinCoin(fmt.Sprintf("%d%s", 10, stakeTypes.StakeDenomName)) param.Value = DepositProcedure{ MinDeposit: sdk.Coins{minDeposit}, @@ -100,12 +101,12 @@ func (param *DepositProcedureParam) Valid(jsonStr string) sdk.Error { if err = json.Unmarshal([]byte(jsonStr), ¶m.Value); err == nil { - if param.Value.MinDeposit[0].Denom != "iris-atto" { - return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMinDepositDenom, fmt.Sprintf("It should be iris-atto!")) + if param.Value.MinDeposit[0].Denom != stakeTypes.StakeDenom { + return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMinDepositDenom, fmt.Sprintf("It should be %s!", stakeTypes.StakeDenom)) } - LowerBound, _ := types.NewDefaultCoinType("iris").ConvertToMinCoin(fmt.Sprintf("%d%s", LOWER_BOUND_AMOUNT, "iris")) - UpperBound, _ := types.NewDefaultCoinType("iris").ConvertToMinCoin(fmt.Sprintf("%d%s", UPPER_BOUND_AMOUNT, "iris")) + LowerBound, _ := types.NewDefaultCoinType(stakeTypes.StakeDenomName).ConvertToMinCoin(fmt.Sprintf("%d%s", LOWER_BOUND_AMOUNT, stakeTypes.StakeDenomName)) + UpperBound, _ := types.NewDefaultCoinType(stakeTypes.StakeDenomName).ConvertToMinCoin(fmt.Sprintf("%d%s", UPPER_BOUND_AMOUNT, stakeTypes.StakeDenomName)) if param.Value.MinDeposit[0].Amount.LT(LowerBound.Amount) || param.Value.MinDeposit[0].Amount.GT(UpperBound.Amount) { return sdk.NewError(params.DefaultCodespace, params.CodeInvalidMinDepositAmount, fmt.Sprintf("MinDepositAmount"+param.Value.MinDeposit[0].String()+" should be larger than 10iris and less than 10000iris")) diff --git a/simulation/gov/invariants.go b/modules/gov/simulation/invariants.go similarity index 85% rename from simulation/gov/invariants.go rename to modules/gov/simulation/invariants.go index 7a288b21f..c924954d3 100644 --- a/simulation/gov/invariants.go +++ b/modules/gov/simulation/invariants.go @@ -2,7 +2,7 @@ package simulation import ( "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" ) // AllInvariants tests all governance invariants diff --git a/simulation/gov/msgs.go b/modules/gov/simulation/msgs.go similarity index 99% rename from simulation/gov/msgs.go rename to modules/gov/simulation/msgs.go index 3f1eb0328..84a3be975 100644 --- a/simulation/gov/msgs.go +++ b/modules/gov/simulation/msgs.go @@ -10,7 +10,7 @@ import ( "time" "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" ) const ( diff --git a/simulation/gov/sim_test.go b/modules/gov/simulation/sim_test.go similarity index 95% rename from simulation/gov/sim_test.go rename to modules/gov/simulation/sim_test.go index d7f77f09e..5617b4653 100644 --- a/simulation/gov/sim_test.go +++ b/modules/gov/simulation/sim_test.go @@ -11,8 +11,8 @@ import ( "github.com/irisnet/irishub/modules/bank" "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/gov" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" ) // TestGovWithRandomMessages diff --git a/modules/gov/test_common.go b/modules/gov/test_common.go index 660eb4a9c..9b3200232 100644 --- a/modules/gov/test_common.go +++ b/modules/gov/test_common.go @@ -13,10 +13,11 @@ import ( "fmt" sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/bank" - "github.com/irisnet/irishub/simulation/mock" + "github.com/irisnet/irishub/modules/mock" "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/gov/params" "github.com/irisnet/irishub/types" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) // initialize the mock application for this module @@ -42,8 +43,8 @@ func getMockApp(t *testing.T, numGenAccs int) (*mock.App, Keeper, stake.Keeper, mapp.SetInitChainer(getInitChainer(mapp, gk, sk)) require.NoError(t, mapp.CompleteSetup(keyGov)) - - coin, _ := types.NewDefaultCoinType("iris").ConvertToMinCoin(fmt.Sprintf("%d%s", 1042, "iris")) + + coin, _ := types.NewDefaultCoinType(stakeTypes.StakeDenomName).ConvertToMinCoin(fmt.Sprintf("%d%s", 1042, stakeTypes.StakeDenomName)) genAccs, addrs, pubKeys, privKeys := mock.CreateGenAccounts(numGenAccs, sdk.Coins{coin}) mock.SetGenesis(mapp, genAccs) @@ -67,15 +68,15 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk mapp.InitChainer(ctx, req) stakeGenesis := stake.DefaultGenesisState() - stakeGenesis.Params.BondDenom = "iris-atto" + stakeGenesis.Params.BondDenom = stakeTypes.StakeDenom stakeGenesis.Pool.LooseTokens = sdk.NewDecFromInt(sdk.NewInt(100000)) validators, err := stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) if err != nil { panic(err) } - ct := types.NewDefaultCoinType("iris") - minDeposit, _ := ct.ConvertToMinCoin(fmt.Sprintf("%d%s", 10, "iris")) + ct := types.NewDefaultCoinType(stakeTypes.StakeDenomName) + minDeposit, _ := ct.ConvertToMinCoin(fmt.Sprintf("%d%s", 10, stakeTypes.StakeDenomName)) InitGenesis(ctx, keeper, GenesisState{ StartingProposalID: 1, DepositProcedure: govparams.DepositProcedure{ diff --git a/modules/mint/minter_test.go b/modules/mint/minter_test.go new file mode 100644 index 000000000..9eb9f1b9f --- /dev/null +++ b/modules/mint/minter_test.go @@ -0,0 +1,53 @@ +package mint + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +func TestNextInflation(t *testing.T) { + minter := InitialMinter() + params := DefaultParams() + + // Governing Mechanism: + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + bondedRatio, setInflation, expChange sdk.Dec + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {sdk.OneDec(), sdk.NewDecWithPrec(20, 2), + sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(10, 2), + sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {sdk.OneDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()}, + {sdk.OneDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)}, + + // test 20% maximum stop (testing with 0% bonded) + {sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()}, + {sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)}, + + // perfect balance shouldn't change inflation + {sdk.NewDecWithPrec(67, 2), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()}, + } + for i, tc := range tests { + minter.Inflation = tc.setInflation + + inflation := minter.NextInflation(params, tc.bondedRatio) + diffInflation := inflation.Sub(tc.setInflation) + + require.True(t, diffInflation.Equal(tc.expChange), + "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + } +} diff --git a/modules/mint/params.go b/modules/mint/params.go index 187dce424..4e66c1677 100644 --- a/modules/mint/params.go +++ b/modules/mint/params.go @@ -4,6 +4,7 @@ import ( "fmt" sdk "github.com/irisnet/irishub/types" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) // mint parameters @@ -18,7 +19,7 @@ type Params struct { // default minting module parameters func DefaultParams() Params { return Params{ - MintDenom: "steak", + MintDenom: stakeTypes.StakeDenom, InflationRateChange: sdk.NewDecWithPrec(13, 2), InflationMax: sdk.NewDecWithPrec(20, 2), InflationMin: sdk.NewDecWithPrec(7, 2), diff --git a/simulation/mock/app.go b/modules/mock/app.go similarity index 96% rename from simulation/mock/app.go rename to modules/mock/app.go index 9bbd57d33..eb5fcf2ff 100644 --- a/simulation/mock/app.go +++ b/modules/mock/app.go @@ -21,13 +21,11 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/irisnet/irishub/modules/arbitration/params" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) const ( chainID = "" - Denom = "iris" - MiniDenom = "iris-atto" - DefaultStakeDenom = "steak" ) const ( @@ -46,7 +44,7 @@ const ( ) var ( - IrisCt = types.NewDefaultCoinType(Denom) + IrisCt = types.NewDefaultCoinType(stakeTypes.StakeDenomName) ) // App extends an ABCI application, but with most of its parameters exported. @@ -125,6 +123,7 @@ func NewApp() *App { app.Cdc, app.KeyParams, app.TkeyParams, ) + app.FeeManager = bam.NewFeeManager(app.ParamsKeeper.Subspace("Fee")) app.SetInitChainer(app.InitChainer) app.SetAnteHandler(auth.NewAnteHandler(app.AccountKeeper, app.FeeCollectionKeeper)) @@ -195,6 +194,13 @@ func (app *App) InitChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.Respo app.AccountKeeper.SetAccount(ctx, acc) } + feeTokenGensisConfig := bam.FeeGenesisStateConfig{ + FeeTokenNative: IrisCt.MinUnit.Denom, + GasPriceThreshold: 0, // for mock test + } + + bam.InitGenesis(ctx, app.FeeManager, feeTokenGensisConfig) + return abci.ResponseInitChain{} } @@ -234,7 +240,7 @@ func SetGenesis(app *App, accs []auth.Account) { func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKey) auth.StdTx { // Make the transaction free fee := auth.StdFee{ - Amount: sdk.Coins{sdk.NewInt64Coin("iris-atto", 40000000000000000)}, + Amount: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 0)}, Gas: 20000, } diff --git a/modules/mock/app_test.go b/modules/mock/app_test.go new file mode 100644 index 000000000..052e0bfc6 --- /dev/null +++ b/modules/mock/app_test.go @@ -0,0 +1,278 @@ +package mock + +import ( + "testing" + + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" +) + +type ( + expectedBalance struct { + addr sdk.AccAddress + coins sdk.Coins + } + + appTestCase struct { + expSimPass bool + expPass bool + msgs []sdk.Msg + accNums []int64 + accSeqs []int64 + privKeys []crypto.PrivKey + expectedBalances []expectedBalance + } +) + +var ( + priv1 = ed25519.GenPrivKey() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + priv2 = ed25519.GenPrivKey() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + priv4 = ed25519.GenPrivKey() + addr4 = sdk.AccAddress(priv4.PubKey().Address()) + + coins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 10)} + halfCoins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 5)} + manyCoins = sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 1), sdk.NewInt64Coin("barcoin", 1)} + freeFee = auth.NewStdFee(100000, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 0)}...) + + sendMsg1 = bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{bank.NewOutput(addr2, coins)}, + } + sendMsg2 = bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(addr1, coins)}, + Outputs: []bank.Output{ + bank.NewOutput(addr2, halfCoins), + bank.NewOutput(addr3, halfCoins), + }, + } + sendMsg3 = bank.MsgSend{ + Inputs: []bank.Input{ + bank.NewInput(addr1, coins), + bank.NewInput(addr4, coins), + }, + Outputs: []bank.Output{ + bank.NewOutput(addr2, coins), + bank.NewOutput(addr3, coins), + }, + } + sendMsg4 = bank.MsgSend{ + Inputs: []bank.Input{ + bank.NewInput(addr2, coins), + }, + Outputs: []bank.Output{ + bank.NewOutput(addr1, coins), + }, + } + sendMsg5 = bank.MsgSend{ + Inputs: []bank.Input{ + bank.NewInput(addr1, manyCoins), + }, + Outputs: []bank.Output{ + bank.NewOutput(addr2, manyCoins), + }, + } +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) *App { + mapp, err := getBenchmarkMockApp() + require.NoError(t, err) + return mapp +} + +func TestMsgSendWithAccounts(t *testing.T) { + mapp := getMockApp(t) + acc := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 67)}, + } + + SetGenesis(mapp, []auth.Account{acc}) + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + + res1 := mapp.AccountKeeper.GetAccount(ctxCheck, addr1) + require.NotNil(t, res1) + require.Equal(t, acc, res1.(*auth.BaseAccount)) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 57)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 10)}}, + }, + }, + { + msgs: []sdk.Msg{sendMsg1, sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: false, + expPass: false, + privKeys: []crypto.PrivKey{priv1}, + }, + } + + for _, tc := range testCases { + SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + + for _, eb := range tc.expectedBalances { + CheckBalance(t, mapp, eb.addr, eb.coins) + } + } + + // bumping the tx nonce number without resigning should be an auth error + mapp.BeginBlock(abci.RequestBeginBlock{}) + + tx := GenTx([]sdk.Msg{sendMsg1}, []int64{0}, []int64{0}, priv1) + tx.Signatures[0].Sequence = 1 + + res := mapp.Deliver(tx) + require.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the bumped sequence should work + SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{sendMsg1, sendMsg2}, []int64{0}, []int64{1}, true, true, priv1) +} + +func TestMsgSendMultipleOut(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + + SetGenesis(mapp, []auth.Account{acc1, acc2}) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{sendMsg2}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 32)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 47)}}, + {addr3, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 5)}}, + }, + }, + } + + for _, tc := range testCases { + SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + + for _, eb := range tc.expectedBalances { + CheckBalance(t, mapp, eb.addr, eb.coins) + } + } +} + +func TestSengMsgMultipleInOut(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + acc4 := &auth.BaseAccount{ + Address: addr4, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + + SetGenesis(mapp, []auth.Account{acc1, acc2, acc4}) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{sendMsg3}, + accNums: []int64{0, 0}, + accSeqs: []int64{0, 0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1, priv4}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 32)}}, + {addr4, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 32)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 52)}}, + {addr3, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 10)}}, + }, + }, + } + + for _, tc := range testCases { + SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + + for _, eb := range tc.expectedBalances { + CheckBalance(t, mapp, eb.addr, eb.coins) + } + } +} + +func TestMsgSendDependent(t *testing.T) { + mapp := getMockApp(t) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}, + } + + SetGenesis(mapp, []auth.Account{acc1}) + + testCases := []appTestCase{ + { + msgs: []sdk.Msg{sendMsg1}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv1}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 32)}}, + {addr2, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 10)}}, + }, + }, + { + msgs: []sdk.Msg{sendMsg4}, + accNums: []int64{0}, + accSeqs: []int64{0}, + expSimPass: true, + expPass: true, + privKeys: []crypto.PrivKey{priv2}, + expectedBalances: []expectedBalance{ + {addr1, sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 42)}}, + }, + }, + } + + for _, tc := range testCases { + SignCheckDeliver(t, mapp.BaseApp, tc.msgs, tc.accNums, tc.accSeqs, tc.expSimPass, tc.expPass, tc.privKeys...) + + for _, eb := range tc.expectedBalances { + CheckBalance(t, mapp, eb.addr, eb.coins) + } + } +} diff --git a/modules/mock/bench_test.go b/modules/mock/bench_test.go new file mode 100644 index 000000000..da33e0c24 --- /dev/null +++ b/modules/mock/bench_test.go @@ -0,0 +1,54 @@ +package mock + +import ( + "testing" + + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + sdk "github.com/irisnet/irishub/types" + abci "github.com/tendermint/tendermint/abci/types" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" +) + +// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking +// Any long term API support commitments do not apply to this function. +func getBenchmarkMockApp() (*App, error) { + mapp := NewApp() + + bank.RegisterCodec(mapp.Cdc) + bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper) + mapp.Router().AddRoute("bank", []*sdk.KVStoreKey{mapp.KeyAccount}, bank.NewHandler(bankKeeper)) + + err := mapp.CompleteSetup() + return mapp, err +} + +func BenchmarkOneBankSendTxPerBlock(b *testing.B) { + benchmarkApp, _ := getBenchmarkMockApp() + + // Add an account at genesis + acc := &auth.BaseAccount{ + Address: addr1, + // Some value conceivably higher than the benchmarks would ever go + Coins: sdk.Coins{sdk.NewInt64Coin(stakeTypes.StakeDenom, 100000000000)}, + } + accs := []auth.Account{acc} + + // Construct genesis state + SetGenesis(benchmarkApp, accs) + // Precompute all txs + txs := GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []int64{0}, []int64{int64(0)}, b.N, priv1) + b.ResetTimer() + // Run this with a profiler, so its easy to distinguish what time comes from + // Committing, and what time comes from Check/Deliver Tx. + for i := 0; i < b.N; i++ { + benchmarkApp.BeginBlock(abci.RequestBeginBlock{}) + x := benchmarkApp.Check(txs[i]) + if !x.IsOK() { + panic("something is broken in checking transaction") + } + benchmarkApp.Deliver(txs[i]) + benchmarkApp.EndBlock(abci.RequestEndBlock{}) + benchmarkApp.Commit() + } +} diff --git a/simulation/mock/simulation/params.go b/modules/mock/simulation/params.go similarity index 100% rename from simulation/mock/simulation/params.go rename to modules/mock/simulation/params.go diff --git a/simulation/mock/simulation/random_simulate_blocks.go b/modules/mock/simulation/random_simulate_blocks.go similarity index 100% rename from simulation/mock/simulation/random_simulate_blocks.go rename to modules/mock/simulation/random_simulate_blocks.go diff --git a/simulation/mock/simulation/transition_matrix.go b/modules/mock/simulation/transition_matrix.go similarity index 100% rename from simulation/mock/simulation/transition_matrix.go rename to modules/mock/simulation/transition_matrix.go diff --git a/simulation/mock/simulation/types.go b/modules/mock/simulation/types.go similarity index 100% rename from simulation/mock/simulation/types.go rename to modules/mock/simulation/types.go diff --git a/simulation/mock/simulation/util.go b/modules/mock/simulation/util.go similarity index 99% rename from simulation/mock/simulation/util.go rename to modules/mock/simulation/util.go index 0daed7da3..fac09fa7c 100644 --- a/simulation/mock/simulation/util.go +++ b/modules/mock/simulation/util.go @@ -15,7 +15,7 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock" + "github.com/irisnet/irishub/modules/mock" "math/big" ) diff --git a/simulation/mock/test_utils.go b/modules/mock/test_utils.go similarity index 100% rename from simulation/mock/test_utils.go rename to modules/mock/test_utils.go diff --git a/modules/params/keeper_test.go b/modules/params/keeper_test.go new file mode 100644 index 000000000..3b6678193 --- /dev/null +++ b/modules/params/keeper_test.go @@ -0,0 +1,217 @@ +package params + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/irisnet/irishub/codec" + "github.com/irisnet/irishub/store" + sdk "github.com/irisnet/irishub/types" +) + +func defaultContext(key sdk.StoreKey, tkey sdk.StoreKey) sdk.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.MountStoreWithDB(tkey, sdk.StoreTypeTransient, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + return ctx +} + +type invalid struct{} + +type s struct { + I int +} + +func createTestCodec() *codec.Codec { + cdc := codec.New() + sdk.RegisterCodec(cdc) + cdc.RegisterConcrete(s{}, "test/s", nil) + cdc.RegisterConcrete(invalid{}, "test/invalid", nil) + return cdc +} + +func TestKeeper(t *testing.T) { + kvs := []struct { + key string + param int64 + }{ + {"key1", 10}, + {"key2", 55}, + {"key3", 182}, + {"key4", 17582}, + {"key5", 2768554}, + {"key6", 1157279}, + {"key7", 9058701}, + } + + table := NewTypeTable( + []byte("key1"), int64(0), + []byte("key2"), int64(0), + []byte("key3"), int64(0), + []byte("key4"), int64(0), + []byte("key5"), int64(0), + []byte("key6"), int64(0), + []byte("key7"), int64(0), + []byte("extra1"), bool(false), + []byte("extra2"), string(""), + ) + + cdc := codec.New() + skey := sdk.NewKVStoreKey("test") + tkey := sdk.NewTransientStoreKey("transient_test") + ctx := defaultContext(skey, tkey) + keeper := NewKeeper(cdc, skey, tkey) + space := keeper.Subspace("test").WithTypeTable(table) + store := ctx.KVStore(skey).Prefix([]byte("test/")) + + // Set params + for i, kv := range kvs { + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) + } + + // Test space.Get + for i, kv := range kvs { + var param int64 + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "space.Get panics, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) + } + + // Test space.GetRaw + for i, kv := range kvs { + var param int64 + bz := space.GetRaw(ctx, []byte(kv.key)) + err := cdc.UnmarshalJSON(bz, ¶m) + require.Nil(t, err, "err is not nil, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) + } + + // Test store.Get equals space.Get + for i, kv := range kvs { + var param int64 + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "KVStore.Get returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, ¶m) + require.NoError(t, err, "UnmarshalJSON returns error, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) + } + + // Test invalid space.Get + for i, kv := range kvs { + var param bool + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "invalid space.Get not panics, tc #%d", i) + } + + // Test invalid space.Set + for i, kv := range kvs { + require.Panics(t, func() { space.Set(ctx, []byte(kv.key), true) }, "invalid space.Set not panics, tc #%d", i) + } + + // Test GetSubspace + for i, kv := range kvs { + var gparam, param int64 + gspace, ok := keeper.GetSubspace("test") + require.True(t, ok, "cannot retrieve subspace, tc #%d", i) + + require.NotPanics(t, func() { gspace.Get(ctx, []byte(kv.key), &gparam) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, gparam, param, "GetSubspace().Get not equal with space.Get, tc #%d", i) + + require.NotPanics(t, func() { gspace.Set(ctx, []byte(kv.key), int64(i)) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, int64(i), param, "GetSubspace().Set not equal with space.Get, tc #%d", i) + } +} + +func indirect(ptr interface{}) interface{} { + return reflect.ValueOf(ptr).Elem().Interface() +} + +func TestSubspace(t *testing.T) { + cdc := createTestCodec() + key := sdk.NewKVStoreKey("test") + tkey := sdk.NewTransientStoreKey("transient_test") + ctx := defaultContext(key, tkey) + keeper := NewKeeper(cdc, key, tkey) + + kvs := []struct { + key string + param interface{} + zero interface{} + ptr interface{} + }{ + {"string", "test", "", new(string)}, + {"bool", true, false, new(bool)}, + {"int16", int16(1), int16(0), new(int16)}, + {"int32", int32(1), int32(0), new(int32)}, + {"int64", int64(1), int64(0), new(int64)}, + {"uint16", uint16(1), uint16(0), new(uint16)}, + {"uint32", uint32(1), uint32(0), new(uint32)}, + {"uint64", uint64(1), uint64(0), new(uint64)}, + {"int", sdk.NewInt(1), *new(sdk.Int), new(sdk.Int)}, + {"uint", sdk.NewUint(1), *new(sdk.Uint), new(sdk.Uint)}, + {"dec", sdk.NewDec(1), *new(sdk.Dec), new(sdk.Dec)}, + {"struct", s{1}, s{0}, new(s)}, + } + + table := NewTypeTable( + []byte("string"), string(""), + []byte("bool"), bool(false), + []byte("int16"), int16(0), + []byte("int32"), int32(0), + []byte("int64"), int64(0), + []byte("uint16"), uint16(0), + []byte("uint32"), uint32(0), + []byte("uint64"), uint64(0), + []byte("int"), sdk.Int{}, + []byte("uint"), sdk.Uint{}, + []byte("dec"), sdk.Dec{}, + []byte("struct"), s{}, + ) + + store := ctx.KVStore(key).Prefix([]byte("test/")) + space := keeper.Subspace("test").WithTypeTable(table) + + // Test space.Set, space.Modified + for i, kv := range kvs { + require.False(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns true before setting, tc #%d", i) + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) + require.True(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns false after setting, tc #%d", i) + } + + // Test space.Get, space.GetIfExists + for i, kv := range kvs { + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "space.GetIfExists panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "space.GetIfExists unmarshalls when no value exists, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "invalid space.Get unmarshalls when no value exists, tc #%d", i) + + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "space.GetIfExists panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), kv.ptr) }, "space.Get panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) + + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "invalid space.Get unmarshalls when no value existt, tc #%d", i) + + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), nil) }, "invalid space.Get not panics when the pointer is nil, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid space.Get not panics when the pointer is different type, tc #%d", i) + } + + // Test store.Get equals space.Get + for i, kv := range kvs { + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "store.Get() returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, kv.ptr) + require.NoError(t, err, "cdc.UnmarshalJSON() returns error, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) + } +} diff --git a/modules/params/subspace/table_test.go b/modules/params/subspace/table_test.go new file mode 100644 index 000000000..8f9142e1e --- /dev/null +++ b/modules/params/subspace/table_test.go @@ -0,0 +1,35 @@ +package subspace + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type testparams struct { + i int64 + b bool +} + +func (tp *testparams) KeyValuePairs() KeyValuePairs { + return KeyValuePairs{ + {[]byte("i"), &tp.i}, + {[]byte("b"), &tp.b}, + } +} + +func TestTypeTable(t *testing.T) { + table := NewTypeTable() + + require.Panics(t, func() { table.RegisterType([]byte(""), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("!@#$%"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello,"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), nil) }) + + require.NotPanics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + require.NotPanics(t, func() { table.RegisterType([]byte("world"), int64(0)) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + + require.NotPanics(t, func() { table.RegisterParamSet(&testparams{}) }) + require.Panics(t, func() { table.RegisterParamSet(&testparams{}) }) +} diff --git a/modules/record/msgs_test.go b/modules/record/msgs_test.go index 178704a98..1c31ae581 100644 --- a/modules/record/msgs_test.go +++ b/modules/record/msgs_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/irisnet/irishub/simulation/mock" + "github.com/irisnet/irishub/modules/mock" "github.com/stretchr/testify/require" sdk "github.com/irisnet/irishub/types" diff --git a/modules/record/test_common.go b/modules/record/test_common.go index e4858733c..a39ce9c73 100644 --- a/modules/record/test_common.go +++ b/modules/record/test_common.go @@ -9,7 +9,7 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/stake" - "github.com/irisnet/irishub/simulation/mock" + "github.com/irisnet/irishub/modules/mock" "github.com/irisnet/irishub/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" diff --git a/modules/service/test_common.go b/modules/service/test_common.go index 7a8b2d9ad..8f7487769 100644 --- a/modules/service/test_common.go +++ b/modules/service/test_common.go @@ -13,7 +13,7 @@ import ( sdk "github.com/irisnet/irishub/types" "github.com/irisnet/irishub/modules/bank" - "github.com/irisnet/irishub/simulation/mock" + "github.com/irisnet/irishub/modules/mock" "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/types" "fmt" diff --git a/modules/slashing/app_test.go b/modules/slashing/app_test.go new file mode 100644 index 000000000..63228020d --- /dev/null +++ b/modules/slashing/app_test.go @@ -0,0 +1,124 @@ +package slashing + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/params" + "github.com/irisnet/irishub/modules/stake" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/ed25519" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" +) + +var ( + priv1 = ed25519.GenPrivKey() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + coins = sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} +) + +// initialize the mock application for this module +func getMockApp(t *testing.T) (*mock.App, stake.Keeper, Keeper) { + mApp := mock.NewApp() + + RegisterCodec(mApp.Cdc) + keySlashing := sdk.NewKVStoreKey("slashing") + bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) + + paramsKeeper := params.NewKeeper(mApp.Cdc, mApp.KeyParams, mApp.TkeyParams) + stakeKeeper := stake.NewKeeper(mApp.Cdc, mApp.KeyStake, mApp.TkeyStake, bankKeeper, paramsKeeper.Subspace(stake.DefaultParamspace), mApp.RegisterCodespace(stake.DefaultCodespace)) + keeper := NewKeeper(mApp.Cdc, keySlashing, stakeKeeper, paramsKeeper.Subspace(DefaultParamspace), mApp.RegisterCodespace(DefaultCodespace)) + mApp.Router().AddRoute("stake", []*sdk.KVStoreKey{mApp.KeyStake, mApp.KeyAccount, mApp.KeyParams}, stake.NewHandler(stakeKeeper)) + mApp.Router().AddRoute("slashing", []*sdk.KVStoreKey{mApp.KeyStake, keySlashing, mApp.KeyParams}, NewHandler(keeper)) + + mApp.SetEndBlocker(getEndBlocker(stakeKeeper)) + mApp.SetInitChainer(getInitChainer(mApp, stakeKeeper)) + + require.NoError(t, mApp.CompleteSetup(keySlashing)) + + return mApp, stakeKeeper, keeper +} + +// stake endblocker +func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { + return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := stake.EndBlocker(ctx, keeper) + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + } +} + +// overwrite the mock init chainer +func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { + return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + mapp.InitChainer(ctx, req) + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(100000, 18)) + validators, err := stake.InitGenesis(ctx, keeper, stakeGenesis) + if err != nil { + panic(err) + } + + return abci.ResponseInitChain{ + Validators: validators, + } + } +} + +func checkValidator(t *testing.T, mapp *mock.App, keeper stake.Keeper, + addr sdk.AccAddress, expFound bool) stake.Validator { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, sdk.ValAddress(addr1)) + require.Equal(t, expFound, found) + return validator +} + +func checkValidatorSigningInfo(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.ConsAddress, expFound bool) ValidatorSigningInfo { + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + signingInfo, found := keeper.getValidatorSigningInfo(ctxCheck, addr) + require.Equal(t, expFound, found) + return signingInfo +} + +func TestSlashingMsgs(t *testing.T) { + mapp, stakeKeeper, keeper := getMockApp(t) + + genCoin := sdk.NewCoin(stakeTypes.StakeDenom, sdk.NewIntWithDecimal(42,18)) + bondCoin := sdk.NewCoin(stakeTypes.StakeDenom, sdk.NewIntWithDecimal(10,18)) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + accs := []auth.Account{acc1} + mock.SetGenesis(mapp, accs) + + description := stake.NewDescription("foo_moniker", "", "", "") + commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + + createValidatorMsg := stake.NewMsgCreateValidator( + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commission, + ) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) + mock.CheckBalance(t, mapp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mapp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mapp, stakeKeeper, addr1, true) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) + require.Equal(t, sdk.Bonded, validator.Status) + require.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.BondedTokens())) + unjailMsg := MsgUnjail{ValidatorAddr: sdk.ValAddress(validator.ConsPubKey.Address())} + + // no signing info yet + checkValidatorSigningInfo(t, mapp, keeper, sdk.ConsAddress(addr1), false) + + // unjail should fail with unknown validator + res := mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unjailMsg}, []int64{0}, []int64{1}, false, false, priv1) + require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), res.Code) +} diff --git a/modules/slashing/genesis.go b/modules/slashing/genesis.go index 673c4ca41..43a31f567 100644 --- a/modules/slashing/genesis.go +++ b/modules/slashing/genesis.go @@ -73,13 +73,13 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { keeper.iterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { bechAddr := address.String() signingInfos[bechAddr] = info - array := []MissedBlock{} + localMissedBlocks := []MissedBlock{} keeper.iterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { - array = append(array, MissedBlock{index, missed}) + localMissedBlocks = append(localMissedBlocks, MissedBlock{index, missed}) return false }) - missedBlocks[bechAddr] = array + missedBlocks[bechAddr] = localMissedBlocks return false }) diff --git a/modules/slashing/handler_test.go b/modules/slashing/handler_test.go new file mode 100644 index 000000000..65cf7f16d --- /dev/null +++ b/modules/slashing/handler_test.go @@ -0,0 +1,93 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" +) + +func TestCannotUnjailUnlessJailed(t *testing.T) { + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + slh := NewHandler(keeper) + amtInt := sdk.NewIntWithDecimal(100, 18) + addr, val, amt := addrs[0], pks[0], amtInt + msg := NewTestMsgCreateValidator(addr, val, amt) + got := stake.NewHandler(sk)(ctx, msg) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))), sk.Validator(ctx, addr).GetPower()) + + // assert non-jailed validator can't be unjailed + got = slh(ctx, NewMsgUnjail(addr)) + require.False(t, got.IsOK(), "allowed unjail of non-jailed validator") + require.Equal(t, sdk.ToABCICode(DefaultCodespace, CodeValidatorNotJailed), got.Code) +} + +func TestJailedValidatorDelegations(t *testing.T) { + ctx, _, stakeKeeper, _, slashingKeeper := createTestInput(t, DefaultParams()) + + stakeParams := stakeKeeper.GetParams(ctx) + stakeParams.UnbondingTime = 0 + stakeKeeper.SetParams(ctx, stakeParams) + + // create a validator + amount := sdk.NewIntWithDecimal(10, 18) + valPubKey, bondAmount := pks[0], amount + valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) + + msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) + + // end block + stake.EndBlocker(ctx, stakeKeeper) + + // set dummy signing info + newInfo := ValidatorSigningInfo{ + StartHeight: int64(0), + IndexOffset: int64(0), + JailedUntil: time.Unix(0, 0), + MissedBlocksCounter: int64(0), + } + slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) + + // delegate tokens to the validator + delAddr := sdk.AccAddress(addrs[2]) + msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + got = stake.NewHandler(stakeKeeper)(ctx, msgDelegate) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + unbondShares := sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + + // unbond validator total self-delegations (which should jail the validator) + msgBeginUnbonding := stake.NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) + got = stake.NewHandler(stakeKeeper)(ctx, msgBeginUnbonding) + require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got: %v", got) + + err := stakeKeeper.CompleteUnbonding(ctx, sdk.AccAddress(valAddr), valAddr) + require.Nil(t, err, "expected complete unbonding validator to be ok, got: %v", err) + + // verify validator still exists and is jailed + validator, found := stakeKeeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.True(t, validator.GetJailed()) + + // verify the validator cannot unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.False(t, got.IsOK(), "expected jailed validator to not be able to unjail, got: %v", got) + + // self-delegate to validator + msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + got = stake.NewHandler(stakeKeeper)(ctx, msgSelfDelegate) + require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify the validator can now unjail itself + got = NewHandler(slashingKeeper)(ctx, NewMsgUnjail(valAddr)) + require.True(t, got.IsOK(), "expected jailed validator to be able to unjail, got: %v", got) +} diff --git a/modules/slashing/hooks_test.go b/modules/slashing/hooks_test.go new file mode 100644 index 000000000..0637afff6 --- /dev/null +++ b/modules/slashing/hooks_test.go @@ -0,0 +1,26 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +func TestHookOnValidatorBonded(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + addr := sdk.ConsAddress(addrs[0]) + keeper.onValidatorBonded(ctx, addr, nil) + period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) + require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), 0, sdk.ZeroDec()}, period) +} + +func TestHookOnValidatorBeginUnbonding(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + addr := sdk.ConsAddress(addrs[0]) + keeper.onValidatorBonded(ctx, addr, nil) + keeper.onValidatorBeginUnbonding(ctx, addr, addrs[0]) + period := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, ctx.BlockHeight()) + require.Equal(t, ValidatorSlashingPeriod{addr, ctx.BlockHeight(), ctx.BlockHeight(), sdk.ZeroDec()}, period) +} diff --git a/modules/slashing/keeper_test.go b/modules/slashing/keeper_test.go new file mode 100644 index 000000000..a07c5b118 --- /dev/null +++ b/modules/slashing/keeper_test.go @@ -0,0 +1,472 @@ +package slashing + +import ( + "testing" + "time" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +// Have to change these parameters for tests +// lest the tests take forever +func keeperTestParams() Params { + params := DefaultParams() + params.SignedBlocksWindow = 1000 + params.DowntimeUnbondDuration = 60 * 60 + params.DoubleSignUnbondDuration = 60 * 60 + return params +} + +// ______________________________________________________________ + +// Test that a validator is slashed correctly +// when we discover evidence of infraction +func TestHandleDoubleSign(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + // validator added pre-genesis + ctx = ctx.WithBlockHeight(-1) + amtInt := sdk.NewIntWithDecimal(100, 18) + operatorAddr, val, amt := addrs[0], pks[0], amtInt + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, sdk.NewDecFromInt(amtInt.Div(sdk.NewIntWithDecimal(1, 18))), sk.Validator(ctx, operatorAddr).GetPower()) + + // handle a signature to set signing info + keeper.handleValidatorSignature(ctx, val.Address(), amtInt.Div(sdk.NewIntWithDecimal(1, 18)).Int64(), true) + + // double sign less than max age + keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt.Div(sdk.NewIntWithDecimal(1, 18)).Int64()) + + // should be jailed + require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) + // unjail to measure power + sk.Unjail(ctx, sdk.ConsAddress(val.Address())) + // power should be reduced + require.Equal( + t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), + sk.Validator(ctx, operatorAddr).GetPower(), + ) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.MaxEvidenceAge(ctx))}) + + // double sign past max age + keeper.handleDoubleSign(ctx, val.Address(), 0, time.Unix(0, 0), amtInt.Div(sdk.NewIntWithDecimal(1, 18)).Int64()) + require.Equal( + t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))), + sk.Validator(ctx, operatorAddr).GetPower(), + ) +} + +// Test that the amount a validator is slashed for multiple double signs +// is correctly capped by the slashing period in which they were committed +func TestSlashingPeriodCap(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + amtInt := sdk.NewIntWithDecimal(100, 18) + operatorAddr, amt := addrs[0], amtInt + valConsPubKey, valConsAddr := pks[0], pks[0].Address() + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(operatorAddr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))), sk.Validator(ctx, operatorAddr).GetPower()) + + // handle a signature to set signing info + keeper.handleValidatorSignature(ctx, valConsAddr, amt.Div(sdk.NewIntWithDecimal(1, 18)).Int64(), true) + + // double sign less than max age + keeper.handleDoubleSign(ctx, valConsAddr, 1, time.Unix(0, 0), amt.Div(sdk.NewIntWithDecimal(1, 18)).Int64()) + // should be jailed + require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) + // end block + stake.EndBlocker(ctx, sk) + // update block height + ctx = ctx.WithBlockHeight(int64(2)) + // unjail to measure power + sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) + // end block + stake.EndBlocker(ctx, sk) + // power should be reduced + expectedPower := sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) + require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) + + // double sign again, same slashing period + keeper.handleDoubleSign(ctx, valConsAddr, 1, time.Unix(0, 0), amt.Div(sdk.NewIntWithDecimal(1, 18)).Int64()) + // should be jailed + require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) + // end block + stake.EndBlocker(ctx, sk) + // update block height + ctx = ctx.WithBlockHeight(int64(3)) + // unjail to measure power + sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) + // end block + stake.EndBlocker(ctx, sk) + // power should be equal, no more should have been slashed + expectedPower = sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))).Mul(sdk.NewDec(19).Quo(sdk.NewDec(20))) + require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) + + // double sign again, new slashing period + keeper.handleDoubleSign(ctx, valConsAddr, 3, time.Unix(0, 0), amt.Div(sdk.NewIntWithDecimal(1, 18)).Int64()) + // should be jailed + require.True(t, sk.Validator(ctx, operatorAddr).GetJailed()) + // unjail to measure power + sk.Unjail(ctx, sdk.ConsAddress(valConsAddr)) + // end block + stake.EndBlocker(ctx, sk) + // power should be reduced + expectedPower = sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))).Mul(sdk.NewDec(18).Quo(sdk.NewDec(20))) + require.Equal(t, expectedPower, sk.Validator(ctx, operatorAddr).GetPower()) +} + +// Test a validator through uptime, downtime, revocation, +// unrevocation, starting height reset, and revocation again +func TestHandleAbsentValidator(t *testing.T) { + + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewIntWithDecimal(amtInt, 18) + sh := stake.NewHandler(sk) + slh := NewHandler(keeper) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.True(t, sdk.NewDec(amtInt).Equal(sk.Validator(ctx, addr).GetPower())) + // will exist since the validator has been bonded + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.IndexOffset) + require.Equal(t, int64(0), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + height := int64(0) + + // 1000 first blocks OK + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + } + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // 500 blocks missed + for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx)); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + } + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx), info.MissedBlocksCounter) + + // validator should be bonded still + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool := sk.GetPool(ctx) + require.Equal(t, sdk.NewDecFromInt(amt), pool.BondedTokens) + + // 501st block missed + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // counter now reset to zero + require.Equal(t, int64(0), info.MissedBlocksCounter) + + // end block + stake.EndBlocker(ctx, sk) + + // validator should have been jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) + + slashAmt := sdk.NewDecFromInt(amt).Mul(keeper.SlashFractionDowntime(ctx)) + + // validator should have been slashed + require.Equal(t, sdk.NewDecFromInt(amt).Sub(slashAmt), validator.GetTokens()) + + // 502nd block *also* missed (since the LastCommit would have still included the just-unbonded validator) + height++ + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // end block + stake.EndBlocker(ctx, sk) + + // validator should not have been slashed any more, since it was already jailed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.NewDecFromInt(amt).Sub(slashAmt), validator.GetTokens()) + + // 502nd block *double signed* (oh no!) + keeper.handleDoubleSign(ctx, val.Address(), height, ctx.BlockHeader().Time, amtInt) + + // validator should have been slashed + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + secondSlashAmt := sdk.NewDecFromInt(amt).Mul(keeper.SlashFractionDoubleSign(ctx)) + require.Equal(t, sdk.NewDecFromInt(amt).Sub(slashAmt).Sub(secondSlashAmt), validator.GetTokens()) + + // unrevocation should fail prior to jail expiration + got = slh(ctx, NewMsgUnjail(addr)) + require.False(t, got.IsOK()) + + // unrevocation should succeed after jail expiration + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(1, 0).Add(keeper.DowntimeUnbondDuration(ctx))}) + got = slh(ctx, NewMsgUnjail(addr)) + require.True(t, got.IsOK()) + + // end block + stake.EndBlocker(ctx, sk) + + // validator should be rebonded now + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // validator should have been slashed + pool = sk.GetPool(ctx) + require.Equal(t, sdk.NewDecFromInt(amt).Sub(slashAmt).Sub(secondSlashAmt), pool.BondedTokens) + + // validator start height should not have been changed + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, int64(0), info.StartHeight) + // we've missed 2 blocks more than the maximum, so the counter was reset to 0 at 1 block more and is now 1 + require.Equal(t, int64(1), info.MissedBlocksCounter) + + // validator should not be immediately jailed again + height++ + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + + // 500 signed blocks + nextHeight := height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height < nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + } + + // end block + stake.EndBlocker(ctx, sk) + + // validator should be jailed again after 500 unsigned blocks + nextHeight = height + keeper.MinSignedPerWindow(ctx) + 1 + for ; height <= nextHeight; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + } + + // end block + stake.EndBlocker(ctx, sk) + + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) +} + +// Test a new validator entering the validator set +// Ensure that SigningInfo.StartHeight is set correctly +// and that they are not immediately jailed +func TestHandleNewValidator(t *testing.T) { + // initial setup + ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) + addr, val, amt := addrs[0], pks[0], sdk.NewIntWithDecimal(100, 18) + sh := stake.NewHandler(sk) + + // 1000 first blocks not a validator + ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 1) + + // Validator created + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))), sk.Validator(ctx, addr).GetPower()) + + // Now a validator, for two blocks + keeper.handleValidatorSignature(ctx, val.Address(), 100, true) + ctx = ctx.WithBlockHeight(keeper.SignedBlocksWindow(ctx) + 2) + keeper.handleValidatorSignature(ctx, val.Address(), 100, false) + + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(val.Address())) + require.True(t, found) + require.Equal(t, keeper.SignedBlocksWindow(ctx)+1, info.StartHeight) + require.Equal(t, int64(2), info.IndexOffset) + require.Equal(t, int64(1), info.MissedBlocksCounter) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + + // validator should be bonded still, should not have been jailed or slashed + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Bonded, validator.GetStatus()) + pool := sk.GetPool(ctx) + require.Equal(t, sdk.NewDecFromInt(amt), pool.BondedTokens) +} + +// Test a jailed validator being "down" twice +// Ensure that they're only slashed once +func TestHandleAlreadyJailed(t *testing.T) { + + // initial setup + ctx, _, sk, _, keeper := createTestInput(t, DefaultParams()) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewIntWithDecimal(amtInt, 18) + sh := stake.NewHandler(sk) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + + // 1000 first blocks OK + height := int64(0) + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + } + + // 501 blocks missed + for ; height < keeper.SignedBlocksWindow(ctx)+(keeper.SignedBlocksWindow(ctx)-keeper.MinSignedPerWindow(ctx))+1; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + } + + // end block + stake.EndBlocker(ctx, sk) + + // validator should have been jailed and slashed + validator, _ := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) + + // validator should have been slashed + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(amtInt-1, 18)), validator.GetTokens()) + + // another block missed + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, false) + + // validator should not have been slashed twice + validator, _ = sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(val)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(amtInt-1, 18)), validator.GetTokens()) + +} + +// Test a validator dipping in and out of the validator set +// Ensure that missed blocks are tracked correctly and that +// the start height of the signing info is reset correctly +func TestValidatorDippingInAndOut(t *testing.T) { + + // initial setup + // keeperTestParams set the SignedBlocksWindow to 1000 and MaxMissedBlocksPerWindow to 500 + ctx, _, sk, _, keeper := createTestInput(t, keeperTestParams()) + params := sk.GetParams(ctx) + params.MaxValidators = 1 + sk.SetParams(ctx, params) + amtInt := int64(100) + addr, val, amt := addrs[0], pks[0], sdk.NewIntWithDecimal(amtInt, 18) + consAddr := sdk.ConsAddress(addr) + sh := stake.NewHandler(sk) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + + // 100 first blocks OK + height := int64(0) + for ; height < int64(100); height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), amtInt, true) + } + + // validator kicked out of validator set + newAmt := int64(101) + got = sh(ctx, NewTestMsgCreateValidator(addrs[1], pks[1], sdk.NewIntWithDecimal(newAmt, 18))) + require.True(t, got.IsOK()) + validatorUpdates := stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ := sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // 600 more blocks happened + height = int64(700) + ctx = ctx.WithBlockHeight(height) + + // validator added back in + got = sh(ctx, newTestMsgDelegate(sdk.AccAddress(addrs[2]), addrs[0], sdk.NewIntWithDecimal(2, 18))) + require.True(t, got.IsOK()) + validatorUpdates = stake.EndBlocker(ctx, sk) + require.Equal(t, 2, len(validatorUpdates)) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + newAmt = int64(102) + + // validator misses a block + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + height++ + + // shouldn't be jailed/kicked yet + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 500 more blocks, 501 total + latest := height + for ; height < latest+500; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + } + + // should now be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + + // check all the signing information + signInfo, found := keeper.getValidatorSigningInfo(ctx, consAddr) + require.True(t, found) + require.Equal(t, int64(0), signInfo.MissedBlocksCounter) + require.Equal(t, int64(0), signInfo.IndexOffset) + // array should be cleared + for offset := int64(0); offset < keeper.SignedBlocksWindow(ctx); offset++ { + missed := keeper.getValidatorMissedBlockBitArray(ctx, consAddr, offset) + require.False(t, missed) + } + + // some blocks pass + height = int64(5000) + ctx = ctx.WithBlockHeight(height) + + // validator rejoins and starts signing again + sk.Unjail(ctx, consAddr) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, true) + height++ + + // validator should not be kicked since we reset counter/array when it was jailed + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Bonded, validator.Status) + + // validator misses 501 blocks + latest = height + for ; height < latest+501; height++ { + ctx = ctx.WithBlockHeight(height) + keeper.handleValidatorSignature(ctx, val.Address(), newAmt, false) + } + + // validator should now be jailed & kicked + stake.EndBlocker(ctx, sk) + validator, _ = sk.GetValidator(ctx, addr) + require.Equal(t, sdk.Unbonding, validator.Status) + +} diff --git a/modules/slashing/msg_test.go b/modules/slashing/msg_test.go new file mode 100644 index 000000000..5208f1c3b --- /dev/null +++ b/modules/slashing/msg_test.go @@ -0,0 +1,16 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +func TestMsgUnjailGetSignBytes(t *testing.T) { + addr := sdk.AccAddress("abcd") + msg := NewMsgUnjail(sdk.ValAddress(addr)) + bytes := msg.GetSignBytes() + require.Equal(t, string(bytes), `{"address":"fva1v93xxeq8upjwp"}`) +} diff --git a/modules/slashing/signing_info.go b/modules/slashing/signing_info.go index 50ede4084..60e0af151 100644 --- a/modules/slashing/signing_info.go +++ b/modules/slashing/signing_info.go @@ -84,10 +84,10 @@ func (k Keeper) setValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.Con func (k Keeper) clearValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, GetValidatorMissedBlockBitArrayPrefixKey(address)) + defer iter.Close() for ; iter.Valid(); iter.Next() { store.Delete(iter.Key()) } - iter.Close() } // Construct a new `ValidatorSigningInfo` struct diff --git a/modules/slashing/signing_info_test.go b/modules/slashing/signing_info_test.go new file mode 100644 index 000000000..b83ede81b --- /dev/null +++ b/modules/slashing/signing_info_test.go @@ -0,0 +1,38 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +func TestGetSetValidatorSigningInfo(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) + require.False(t, found) + newInfo := ValidatorSigningInfo{ + StartHeight: int64(4), + IndexOffset: int64(3), + JailedUntil: time.Unix(2, 0), + MissedBlocksCounter: int64(10), + } + keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) + info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) + require.True(t, found) + require.Equal(t, info.StartHeight, int64(4)) + require.Equal(t, info.IndexOffset, int64(3)) + require.Equal(t, info.JailedUntil, time.Unix(2, 0).UTC()) + require.Equal(t, info.MissedBlocksCounter, int64(10)) +} + +func TestGetSetValidatorMissedBlockBitArray(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + missed := keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.False(t, missed) // treat empty key as not missed + keeper.setValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0, true) + missed = keeper.getValidatorMissedBlockBitArray(ctx, sdk.ConsAddress(addrs[0]), 0) + require.True(t, missed) // now should be missed +} diff --git a/simulation/slashing/invariants.go b/modules/slashing/simulation/invariants.go similarity index 81% rename from simulation/slashing/invariants.go rename to modules/slashing/simulation/invariants.go index 105690095..3cb6d57d8 100644 --- a/simulation/slashing/invariants.go +++ b/modules/slashing/simulation/invariants.go @@ -2,7 +2,7 @@ package simulation import ( "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" ) // TODO Any invariants to check here? diff --git a/simulation/slashing/msgs.go b/modules/slashing/simulation/msgs.go similarity index 94% rename from simulation/slashing/msgs.go rename to modules/slashing/simulation/msgs.go index f2db55f38..b0e9811af 100644 --- a/simulation/slashing/msgs.go +++ b/modules/slashing/simulation/msgs.go @@ -5,7 +5,7 @@ import ( "math/rand" "github.com/irisnet/irishub/baseapp" sdk "github.com/irisnet/irishub/types" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" "github.com/irisnet/irishub/modules/slashing" ) diff --git a/modules/slashing/slashing_period_test.go b/modules/slashing/slashing_period_test.go new file mode 100644 index 000000000..74041fd7a --- /dev/null +++ b/modules/slashing/slashing_period_test.go @@ -0,0 +1,86 @@ +package slashing + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" +) + +func TestGetSetValidatorSlashingPeriod(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + addr := sdk.ConsAddress(addrs[0]) + height := int64(5) + require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) }) + newPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height, + EndHeight: height + 10, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + + // Get at start height + retrieved := keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) + require.Equal(t, newPeriod, retrieved) + + // Get after start height (works) + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(6)) + require.Equal(t, newPeriod, retrieved) + + // Get before start height (panic) + require.Panics(t, func() { keeper.getValidatorSlashingPeriodForHeight(ctx, addr, int64(0)) }) + + // Get after end height (panic) + newPeriod.EndHeight = int64(4) + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + require.Panics(t, func() { keeper.capBySlashingPeriod(ctx, addr, sdk.ZeroDec(), height) }) + + // Back to old end height + newPeriod.EndHeight = height + 10 + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + + // Set a new, later period + anotherPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height + 1, + EndHeight: height + 11, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, anotherPeriod) + + // Old period retrieved for prior height + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height) + require.Equal(t, newPeriod, retrieved) + + // New period retrieved at new height + retrieved = keeper.getValidatorSlashingPeriodForHeight(ctx, addr, height+1) + require.Equal(t, anotherPeriod, retrieved) +} + +func TestValidatorSlashingPeriodCap(t *testing.T) { + ctx, _, _, _, keeper := createTestInput(t, DefaultParams()) + addr := sdk.ConsAddress(addrs[0]) + height := int64(5) + newPeriod := ValidatorSlashingPeriod{ + ValidatorAddr: addr, + StartHeight: height, + EndHeight: height + 10, + SlashedSoFar: sdk.ZeroDec(), + } + keeper.addOrUpdateValidatorSlashingPeriod(ctx, newPeriod) + half := sdk.NewDec(1).Quo(sdk.NewDec(2)) + + // First slash should be full + fractionA := keeper.capBySlashingPeriod(ctx, addr, half, height) + require.True(t, fractionA.Equal(half)) + + // Second slash should be capped + fractionB := keeper.capBySlashingPeriod(ctx, addr, half, height) + require.True(t, fractionB.Equal(sdk.ZeroDec())) + + // Third slash should be capped to difference + fractionC := keeper.capBySlashingPeriod(ctx, addr, sdk.OneDec(), height) + require.True(t, fractionC.Equal(half)) +} diff --git a/modules/slashing/test_common.go b/modules/slashing/test_common.go index dcffb72f6..0dbf5a81b 100644 --- a/modules/slashing/test_common.go +++ b/modules/slashing/test_common.go @@ -21,6 +21,7 @@ import ( "github.com/irisnet/irishub/modules/bank" "github.com/irisnet/irishub/modules/params" "github.com/irisnet/irishub/modules/stake" + stakeTypes "github.com/irisnet/irishub/modules/stake/types" ) // TODO remove dependencies on staking (should only refer to validator set type from sdk) @@ -36,7 +37,7 @@ var ( sdk.ValAddress(pks[1].Address()), sdk.ValAddress(pks[2].Address()), } - initCoins = sdk.NewInt(200) + initCoins = sdk.NewIntWithDecimal(200, 18) ) func createTestCodec() *codec.Codec { @@ -75,7 +76,7 @@ func createTestInput(t *testing.T, defaults Params) (sdk.Context, bank.Keeper, s sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, paramsKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) genesis := stake.DefaultGenesisState() - genesis.Pool.LooseTokens = sdk.NewDec(initCoins.MulRaw(int64(len(addrs))).Int64()) + genesis.Pool.LooseTokens = sdk.NewDecFromInt(initCoins.MulRaw(int64(len(addrs)))) _, err = stake.InitGenesis(ctx, sk, genesis) require.Nil(t, err) @@ -120,7 +121,7 @@ func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt DelegatorAddr: sdk.AccAddress(address), ValidatorAddr: address, PubKey: pubKey, - Delegation: sdk.NewCoin("steak", amt), + Delegation: sdk.NewCoin(stakeTypes.StakeDenom, amt), } } @@ -128,6 +129,6 @@ func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, delAmoun return stake.MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.NewCoin("steak", delAmount), + Delegation: sdk.NewCoin(stakeTypes.StakeDenom, delAmount), } } diff --git a/modules/slashing/tick_test.go b/modules/slashing/tick_test.go new file mode 100644 index 000000000..c8f8476d7 --- /dev/null +++ b/modules/slashing/tick_test.go @@ -0,0 +1,86 @@ +package slashing + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake" +) + +func TestBeginBlocker(t *testing.T) { + ctx, ck, sk, _, keeper := createTestInput(t, DefaultParams()) + addr, pk, amt := addrs[2], pks[2], sdk.NewIntWithDecimal(100, 18) + + // bond the validator + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) + require.True(t, got.IsOK()) + stake.EndBlocker(ctx, sk) + require.Equal(t, ck.GetCoins(ctx, sdk.AccAddress(addr)), sdk.Coins{{sk.GetParams(ctx).BondDenom, initCoins.Sub(amt)}}) + require.Equal(t, sdk.NewDecFromInt(amt.Div(sdk.NewIntWithDecimal(1, 18))), sk.Validator(ctx, addr).GetPower()) + + val := abci.Validator{ + Address: pk.Address(), + Power: amt.Div(sdk.NewIntWithDecimal(1, 18)).Int64(), + } + + // mark the validator as having signed + req := abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: true, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + + info, found := keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(pk.Address())) + require.True(t, found) + require.Equal(t, ctx.BlockHeight(), info.StartHeight) + require.Equal(t, int64(1), info.IndexOffset) + require.Equal(t, time.Unix(0, 0).UTC(), info.JailedUntil) + require.Equal(t, int64(0), info.MissedBlocksCounter) + + height := int64(0) + + // for 1000 blocks, mark the validator as having signed + for ; height < keeper.SignedBlocksWindow(ctx); height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: true, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + } + + // for 500 blocks, mark the validator as having not signed + for ; height < ((keeper.SignedBlocksWindow(ctx) * 2) - keeper.MinSignedPerWindow(ctx) + 1); height++ { + ctx = ctx.WithBlockHeight(height) + req = abci.RequestBeginBlock{ + LastCommitInfo: abci.LastCommitInfo{ + Votes: []abci.VoteInfo{{ + Validator: val, + SignedLastBlock: false, + }}, + }, + } + BeginBlocker(ctx, req, keeper) + } + + // end block + stake.EndBlocker(ctx, sk) + + // validator should be jailed + validator, found := sk.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)) + require.True(t, found) + require.Equal(t, sdk.Unbonding, validator.GetStatus()) +} diff --git a/modules/stake/app_test.go b/modules/stake/app_test.go new file mode 100644 index 000000000..f360442a4 --- /dev/null +++ b/modules/stake/app_test.go @@ -0,0 +1,172 @@ +package stake + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/auth" + "github.com/irisnet/irishub/modules/bank" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/params" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/irisnet/irishub/modules/stake/types" +) + +// getMockApp returns an initialized mock application for this module. +func getMockApp(t *testing.T) (*mock.App, Keeper) { + mApp := mock.NewApp() + + RegisterCodec(mApp.Cdc) + + bankKeeper := bank.NewBaseKeeper(mApp.AccountKeeper) + pk := params.NewKeeper(mApp.Cdc, mApp.KeyParams, mApp.TkeyParams) + + keeper := NewKeeper(mApp.Cdc, mApp.KeyStake, mApp.TkeyStake, bankKeeper, pk.Subspace(DefaultParamspace), mApp.RegisterCodespace(DefaultCodespace)) + + mApp.Router().AddRoute("stake", []*sdk.KVStoreKey{mApp.KeyStake, mApp.KeyAccount, mApp.KeyParams}, NewHandler(keeper)) + mApp.SetEndBlocker(getEndBlocker(keeper)) + mApp.SetInitChainer(getInitChainer(mApp, keeper)) + + require.NoError(t, mApp.CompleteSetup()) + + return mApp, keeper +} + +// 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, + } + } +} + +// 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 = sdk.NewDecFromInt(sdk.NewIntWithDecimal(100000, 18)) + + validators, err := InitGenesis(ctx, keeper, stakeGenesis) + if err != nil { + panic(err) + } + return abci.ResponseInitChain{ + Validators: validators, + } + } +} + +//__________________________________________________________________________________________ + +func checkValidator(t *testing.T, mapp *mock.App, keeper Keeper, + addr sdk.ValAddress, expFound bool) Validator { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + validator, found := keeper.GetValidator(ctxCheck, addr) + + require.Equal(t, expFound, found) + return validator +} + +func checkDelegation( + t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress, expFound bool, expShares sdk.Dec, +) { + + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + delegation, found := keeper.GetDelegation(ctxCheck, delegatorAddr, validatorAddr) + if expFound { + require.True(t, found) + require.True(sdk.DecEq(t, expShares, delegation.Shares)) + + return + } + + require.False(t, found) +} + +func TestStakeMsgs(t *testing.T) { + mApp, keeper := getMockApp(t) + + genCoin := sdk.NewCoin(types.StakeDenom, sdk.NewIntWithDecimal(42,18)) + bondCoin := sdk.NewCoin(types.StakeDenom, sdk.NewIntWithDecimal(10,18)) + + acc1 := &auth.BaseAccount{ + Address: addr1, + Coins: sdk.Coins{genCoin}, + } + acc2 := &auth.BaseAccount{ + Address: addr2, + Coins: sdk.Coins{genCoin}, + } + 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 + description := NewDescription("foo_moniker", "", "", "") + createValidatorMsg := NewMsgCreateValidator( + sdk.ValAddress(addr1), priv1.PubKey(), bondCoin, description, commissionMsg, + ) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsg}, []int64{0}, []int64{0}, true, true, priv1) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator := checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) + require.Equal(t, sdk.ValAddress(addr1), validator.OperatorAddr) + require.Equal(t, sdk.Bonded, validator.Status) + require.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.BondedTokens())) + + // addr1 create validator on behalf of addr2 + createValidatorMsgOnBehalfOf := NewMsgCreateValidatorOnBehalfOf( + addr1, sdk.ValAddress(addr2), priv2.PubKey(), bondCoin, description, commissionMsg, + ) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{createValidatorMsgOnBehalfOf}, []int64{0, 0}, []int64{1, 0}, true, true, priv1, priv2) + mock.CheckBalance(t, mApp, addr1, sdk.Coins{genCoin.Minus(bondCoin).Minus(bondCoin)}) + mApp.BeginBlock(abci.RequestBeginBlock{}) + + validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr2), true) + require.Equal(t, sdk.ValAddress(addr2), validator.OperatorAddr) + require.Equal(t, sdk.Bonded, validator.Status) + require.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.Tokens)) + + // check the bond that should have been created as well + checkDelegation(t, mApp, keeper, addr1, sdk.ValAddress(addr1), true, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18))) + + // edit the validator + description = NewDescription("bar_moniker", "", "", "") + editValidatorMsg := NewMsgEditValidator(sdk.ValAddress(addr1), description, nil) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{editValidatorMsg}, []int64{0}, []int64{2}, true, true, priv1) + validator = checkValidator(t, mApp, keeper, sdk.ValAddress(addr1), true) + require.Equal(t, description, validator.Description) + + // delegate + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin}) + delegateMsg := NewMsgDelegate(addr2, sdk.ValAddress(addr1), bondCoin) + + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{delegateMsg}, []int64{0}, []int64{1}, true, true, priv2) + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) + checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), true, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18))) + + // begin unbonding + beginUnbondingMsg := NewMsgBeginUnbonding(addr2, sdk.ValAddress(addr1), sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18))) + mock.SignCheckDeliver(t, mApp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{0}, []int64{2}, true, true, priv2) + + // delegation should exist anymore + checkDelegation(t, mApp, keeper, addr2, sdk.ValAddress(addr1), false, sdk.Dec{}) + + // balance should be the same because bonding not yet complete + mock.CheckBalance(t, mApp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) +} diff --git a/modules/stake/genesis.go b/modules/stake/genesis.go index c231bca66..793f39ae1 100644 --- a/modules/stake/genesis.go +++ b/modules/stake/genesis.go @@ -29,17 +29,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.SetIntraTxCounter(ctx, data.IntraTxCounter) keeper.SetLastTotalPower(ctx, data.LastTotalPower) - // We only need to set this if we're starting from a list of validators, not a state export - setBondIntraTxCounter := true - for _, validator := range data.Validators { - if validator.BondIntraTxCounter != 0 { - setBondIntraTxCounter = false - } - } - for i, validator := range data.Validators { // set the intra-tx counter to the order the validators are presented, if necessary - if setBondIntraTxCounter { + if !data.Exported { validator.BondIntraTxCounter = int16(i) } keeper.SetValidator(ctx, validator) @@ -76,7 +68,15 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.InsertRedelegationQueue(ctx, red) } - res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // don't need to run Tendermint updates if we exported + if data.Exported { + for _, lv := range data.LastValidatorPowers { + keeper.SetLastValidatorPower(ctx, lv.Address, lv.Power) + } + } else { + res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + } + return } @@ -101,15 +101,23 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { return false }) + var lastValidatorPowers []types.LastValidatorPower + keeper.IterateLastValidatorPowers(ctx, func(addr sdk.ValAddress, power sdk.Int) (stop bool) { + lastValidatorPowers = append(lastValidatorPowers, types.LastValidatorPower{addr, power}) + return false + }) + return types.GenesisState{ Pool: pool, Params: params, IntraTxCounter: intraTxCounter, LastTotalPower: lastTotalPower, + LastValidatorPowers: lastValidatorPowers, Validators: validators, Bonds: bonds, UnbondingDelegations: unbondingDelegations, Redelegations: redelegations, + Exported: true, } } diff --git a/modules/stake/genesis_test.go b/modules/stake/genesis_test.go new file mode 100644 index 000000000..0d57d8eea --- /dev/null +++ b/modules/stake/genesis_test.go @@ -0,0 +1,150 @@ +package stake + +import ( + "fmt" + "testing" + + "github.com/tendermint/tendermint/crypto/ed25519" + + "github.com/stretchr/testify/assert" + + sdk "github.com/irisnet/irishub/types" + keep "github.com/irisnet/irishub/modules/stake/keeper" + "github.com/irisnet/irishub/modules/stake/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestInitGenesis(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + pool := keeper.GetPool(ctx) + pool.BondedTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(2, 18)) + + params := keeper.GetParams(ctx) + validators := make([]Validator, 2) + var delegations []Delegation + + // initialize the validators + validators[0].OperatorAddr = sdk.ValAddress(keep.Addrs[0]) + validators[0].ConsPubKey = keep.PKs[0] + validators[0].Description = Description{Moniker: "hoop"} + validators[0].Status = sdk.Bonded + validators[0].Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1, 18)) + validators[0].DelegatorShares = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1, 18)) + validators[1].OperatorAddr = sdk.ValAddress(keep.Addrs[1]) + validators[1].ConsPubKey = keep.PKs[1] + validators[1].Description = Description{Moniker: "bloop"} + validators[1].Status = sdk.Bonded + validators[1].Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1, 18)) + validators[1].DelegatorShares = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1, 18)) + + genesisState := types.NewGenesisState(pool, params, validators, delegations) + vals, err := InitGenesis(ctx, keeper, genesisState) + require.NoError(t, err) + + actualGenesis := ExportGenesis(ctx, keeper) + require.Equal(t, genesisState.Pool, actualGenesis.Pool) + require.Equal(t, genesisState.Params, actualGenesis.Params) + require.Equal(t, genesisState.Bonds, actualGenesis.Bonds) + require.EqualValues(t, keeper.GetAllValidators(ctx), actualGenesis.Validators) + + // now make sure the validators are bonded and intra-tx counters are correct + resVal, found := keeper.GetValidator(ctx, sdk.ValAddress(keep.Addrs[0])) + require.True(t, found) + require.Equal(t, sdk.Bonded, resVal.Status) + require.Equal(t, int16(0), resVal.BondIntraTxCounter) + + resVal, found = keeper.GetValidator(ctx, sdk.ValAddress(keep.Addrs[1])) + require.True(t, found) + require.Equal(t, sdk.Bonded, resVal.Status) + require.Equal(t, int16(1), resVal.BondIntraTxCounter) + + abcivals := make([]abci.ValidatorUpdate, len(vals)) + for i, val := range validators { + abcivals[i] = val.ABCIValidatorUpdate() + } + + require.Equal(t, abcivals, vals) +} + +func TestInitGenesisLargeValidatorSet(t *testing.T) { + size := 200 + require.True(t, size > 100) + + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + // Assigning 2 to the first 100 vals, 1 to the rest + pool := keeper.GetPool(ctx) + pool.BondedTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(int64(200 + (size - 100)), 18)) + + params := keeper.GetParams(ctx) + delegations := []Delegation{} + validators := make([]Validator, size) + + for i := range validators { + validators[i] = NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], Description{Moniker: fmt.Sprintf("#%d", i)}) + + validators[i].Status = sdk.Bonded + if i < 100 { + validators[i].Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(2, 18)) + validators[i].DelegatorShares = sdk.NewDecFromInt(sdk.NewIntWithDecimal(2, 18)) + } else { + validators[i].Tokens = sdk.OneDec() + validators[i].DelegatorShares = sdk.OneDec() + } + } + + genesisState := types.NewGenesisState(pool, params, validators, delegations) + vals, err := InitGenesis(ctx, keeper, genesisState) + require.NoError(t, err) + + abcivals := make([]abci.ValidatorUpdate, 100) + for i, val := range validators[:100] { + abcivals[i] = val.ABCIValidatorUpdate() + } + + require.Equal(t, abcivals, vals) +} + +func TestValidateGenesis(t *testing.T) { + genValidators1 := make([]types.Validator, 1, 5) + pk := ed25519.GenPrivKey().PubKey() + genValidators1[0] = types.NewValidator(sdk.ValAddress(pk.Address()), pk, types.NewDescription("", "", "", "")) + genValidators1[0].Tokens = sdk.OneDec() + genValidators1[0].DelegatorShares = sdk.OneDec() + + tests := []struct { + name string + mutate func(*types.GenesisState) + wantErr bool + }{ + {"default", func(*types.GenesisState) {}, false}, + // validate genesis validators + {"duplicate validator", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators = append((*data).Validators, genValidators1[0]) + }, true}, + {"no delegator shares", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators[0].DelegatorShares = sdk.ZeroDec() + }, true}, + {"jailed and bonded validator", func(data *types.GenesisState) { + (*data).Validators = genValidators1 + (*data).Validators[0].Jailed = true + (*data).Validators[0].Status = sdk.Bonded + }, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + genesisState := types.DefaultGenesisState() + tt.mutate(&genesisState) + if tt.wantErr { + assert.Error(t, ValidateGenesis(genesisState)) + } else { + assert.NoError(t, ValidateGenesis(genesisState)) + } + }) + } +} diff --git a/modules/stake/handler_test.go b/modules/stake/handler_test.go new file mode 100644 index 000000000..1d8545d2a --- /dev/null +++ b/modules/stake/handler_test.go @@ -0,0 +1,1020 @@ +package stake + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" + keep "github.com/irisnet/irishub/modules/stake/keeper" + "github.com/irisnet/irishub/modules/stake/types" +) + +//______________________________________________________________________ + +// retrieve params which are instant +func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params { + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + return params +} + +//______________________________________________________________________ + +func TestValidatorByPowerIndex(t *testing.T) { + validatorAddr, validatorAddr3 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) + + initBond := int64(1000000) + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(initBond,18)) + _ = setInstantUnbondPeriod(keeper, ctx) + + // create validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(initBond,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // verify the self-delegation exists + bond, found := keeper.GetDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found) + gotBond := bond.Shares + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(initBond,18)), gotBond, + "initBond: %v\ngotBond: %v\nbond: %v\n", + sdk.NewDecFromInt(sdk.NewIntWithDecimal(initBond,18)), gotBond, bond) + + // verify that the by power index exists + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + pool := keeper.GetPool(ctx) + power := keep.GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) + + // create a second validator keep it bonded + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], sdk.NewIntWithDecimal(1000000,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + + // must end-block + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // slash and jail the first validator + consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, initBond, sdk.NewDecWithPrec(5, 1)) + keeper.Jail(ctx, consAddr0) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.Equal(t, sdk.Unbonding, validator.Status) // ensure is unbonding + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(500000,18)), validator.Tokens) // ensure tokens slashed + keeper.Unjail(ctx, consAddr0) + + // the old power record should have been deleted as the power changed + require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) + + // but the new power record should have been created + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + pool = keeper.GetPool(ctx) + power2 := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) + + // now the new record power index should be the same as the original record + power3 := GetValidatorsByPowerIndexKey(validator, pool) + require.Equal(t, power2, power3) + + // unbond self-delegation + msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(initBond,18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + EndBlocker(ctx, keeper) + + // verify that by power key nolonger exists + _, found = keeper.GetValidator(ctx, validatorAddr) + require.False(t, found) + require.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power3)) +} + +func TestDuplicatesMsgCreateValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + addr1, addr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) + pk1, pk2 := keep.PKs[0], keep.PKs[1] + + msgCreateValidator1 := NewTestMsgCreateValidator(addr1, pk1, sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator1, keeper) + require.True(t, got.IsOK(), "%v", got) + + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + validator, found := keeper.GetValidator(ctx, addr1) + require.True(t, found) + assert.Equal(t, sdk.Bonded, validator.Status) + assert.Equal(t, addr1, validator.OperatorAddr) + assert.Equal(t, pk1, validator.ConsPubKey) + assert.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.BondedTokens()) + assert.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.DelegatorShares) + assert.Equal(t, Description{}, validator.Description) + + // two validators can't have the same operator address + msgCreateValidator2 := NewTestMsgCreateValidator(addr1, pk2, sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator2, keeper) + require.False(t, got.IsOK(), "%v", got) + + // two validators can't have the same pubkey + msgCreateValidator3 := NewTestMsgCreateValidator(addr2, pk1, sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator3, keeper) + require.False(t, got.IsOK(), "%v", got) + + // must have different pubkey and operator + msgCreateValidator4 := NewTestMsgCreateValidator(addr2, pk2, sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator4, keeper) + require.True(t, got.IsOK(), "%v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found = keeper.GetValidator(ctx, addr2) + + require.True(t, found) + assert.Equal(t, sdk.Bonded, validator.Status) + assert.Equal(t, addr2, validator.OperatorAddr) + assert.Equal(t, pk2, validator.ConsPubKey) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.DelegatorShares)) + assert.Equal(t, Description{}, validator.Description) +} + +func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + delegatorAddr := keep.Addrs[1] + pk := keep.PKs[0] + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, sdk.NewIntWithDecimal(10, 18)) + got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) + require.True(t, got.IsOK(), "%v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, validatorAddr) + + require.True(t, found) + assert.Equal(t, sdk.Bonded, validator.Status) + assert.Equal(t, validatorAddr, validator.OperatorAddr) + assert.Equal(t, pk, validator.ConsPubKey) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10,18)), validator.DelegatorShares)) + assert.Equal(t, Description{}, validator.Description) + + // one validator cannot be created twice even from different delegator + msgCreateValidatorOnBehalfOf.DelegatorAddr = keep.Addrs[2] + msgCreateValidatorOnBehalfOf.PubKey = keep.PKs[1] + got = handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) + require.False(t, got.IsOK(), "%v", got) +} + +func TestLegacyValidatorDelegations(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + setInstantUnbondPeriod(keeper, ctx) + + bondAmount := sdk.NewIntWithDecimal(10,18) + valAddr := sdk.ValAddress(keep.Addrs[0]) + valConsPubKey, valConsAddr := keep.PKs[0], sdk.ConsAddress(keep.PKs[0].Address()) + delAddr := keep.Addrs[1] + + // create validator + msgCreateVal := NewTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) + got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + + // must end-block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // verify the validator exists and has the correct attributes + validator, found := keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status) + require.Equal(t, sdk.NewDecFromInt(bondAmount), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(bondAmount), validator.BondedTokens()) + + // delegate tokens to the validator + msgDelegate := NewTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(2))), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(2))), validator.BondedTokens()) + + // unbond validator total self-delegations (which should jail the validator) + unbondShares := sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(valAddr), valAddr, unbondShares) + + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected begin unbonding validator msg to be ok, got %v", got) + + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + // verify the validator record still exists, is jailed, and has correct tokens + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.True(t, validator.Jailed) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.Tokens) + + // verify delegation still exists + bond, found := keeper.GetDelegation(ctx, delAddr, valAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount), bond.Shares) + require.Equal(t, sdk.NewDecFromInt(bondAmount), validator.DelegatorShares) + + // verify a delegator cannot create a new delegation to the now jailed validator + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.False(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify the validator can still self-delegate + msgSelfDelegate := NewTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgSelfDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(2))), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(2))), validator.Tokens) + + // unjail the validator now that is has non-zero self-delegated shares + keeper.Unjail(ctx, valConsAddr) + + // verify the validator can now accept delegations + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + // verify validator bonded shares + validator, found = keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(3))), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(3))), validator.Tokens) + + // verify new delegation + bond, found = keeper.GetDelegation(ctx, delAddr, valAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(2))), bond.Shares) + require.Equal(t, sdk.NewDecFromInt(bondAmount.Mul(sdk.NewInt(3))), validator.DelegatorShares) +} + +func TestIncrementsMsgDelegate(t *testing.T) { + initBond := sdk.NewIntWithDecimal(1000, 18) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := keeper.GetParams(ctx) + + bondAmount := sdk.NewIntWithDecimal(10, 18) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // first create validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) + + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status) + require.Equal(t, sdk.NewDecFromInt(bondAmount), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(bondAmount), validator.BondedTokens(), "validator: %v", validator) + + _, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) + require.False(t, found) + + bond, found := keeper.GetDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(bondAmount), bond.Shares) + + pool := keeper.GetPool(ctx) + exRate := validator.DelegatorShareExRate() + require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v", exRate) + require.Equal(t, sdk.NewDecFromInt(bondAmount), pool.BondedTokens) + + // just send the same msgbond multiple times + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) + + for i := 0; i < 5; i++ { + ctx = ctx.WithBlockHeight(int64(i)) + + got := handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + + //Check that the accounts and the bond account have the appropriate values + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) + require.True(t, found) + + exRate := validator.DelegatorShareExRate() + require.True(t, exRate.Equal(sdk.OneDec()), "expected exRate 1 got %v, i = %v", exRate, i) + + expBond := bondAmount.Mul(sdk.NewInt(int64(i+1))) + expDelegatorShares := bondAmount.Mul(sdk.NewInt(int64(i+2))) // (1 self delegation) + expDelegatorAcc := initBond.Sub(expBond) + + require.Equal(t, bond.Height, int64(i), "Incorrect bond height") + + gotBond := bond.Shares + gotDelegatorShares := validator.DelegatorShares + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + require.Equal(t, sdk.NewDecFromInt(expBond), gotBond, + "i: %v\nexpBond: %v\ngotBond: %v\nvalidator: %v\nbond: %v\n", + i, sdk.NewDecFromInt(expBond), gotBond, validator, bond) + require.Equal(t, sdk.NewDecFromInt(expDelegatorShares), gotDelegatorShares, + "i: %v\nexpDelegatorShares: %v\ngotDelegatorShares: %v\nvalidator: %v\nbond: %v\n", + i, sdk.NewDecFromInt(expDelegatorShares), gotDelegatorShares, validator, bond) + require.Equal(t, expDelegatorAcc, gotDelegatorAcc, + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, validator, bond) + } +} + +func TestIncrementsMsgUnbond(t *testing.T) { + initBond := sdk.NewIntWithDecimal(1000, 18) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) + denom := params.BondDenom + + // create validator, delegate + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) + + // initial balance + amt1 := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(denom) + + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, initBond) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + + // balance should have been subtracted after delegation + amt2 := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(denom) + require.Equal(t, amt1.Sub(initBond).Int64(), amt2.Int64(), "expected coins to be subtracted") + + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(initBond.Mul(sdk.NewInt(2))), validator.DelegatorShares) + require.Equal(t, sdk.NewDecFromInt(initBond.Mul(sdk.NewInt(2))), validator.BondedTokens()) + + // just send the same msgUnbond multiple times + // TODO use decimals here + unbondShares := sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + numUnbonds := 5 + for i := 0; i < numUnbonds; i++ { + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + //Check that the accounts and the bond account have the appropriate values + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) + require.True(t, found) + + expBond := initBond.Sub(sdk.NewDecFromInt(sdk.NewInt(int64(i+1))).Mul(unbondShares).TruncateInt()) + expDelegatorShares := sdk.NewInt(2).Mul(initBond).Sub(sdk.NewDecFromInt(sdk.NewInt(int64(i+1))).Mul(unbondShares).TruncateInt()) + expDelegatorAcc := initBond.Sub(expBond) + + gotBond := bond.Shares + gotDelegatorShares := validator.DelegatorShares + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + require.Equal(t, sdk.NewDecFromInt(expBond), gotBond, + "i: %v\nexpBond: %v\ngotBond: %v\nvalidator: %v\nbond: %v\n", + i, sdk.NewDecFromInt(expBond), gotBond, validator, bond) + require.Equal(t, sdk.NewDecFromInt(expDelegatorShares), gotDelegatorShares, + "i: %v\nexpDelegatorShares: %v\ngotDelegatorShares: %v\nvalidator: %v\nbond: %v\n", + i, sdk.NewDecFromInt(expDelegatorShares), gotDelegatorShares, validator, bond) + require.Equal(t, expDelegatorAcc, gotDelegatorAcc, + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, validator, bond) + } + + // these are more than we have bonded now + errorCases := []sdk.Int{ + //1<<64 - 1, // more than int64 + //1<<63 + 1, // more than int64 + //sdk.NewInt(1<<63 - 1), + //sdk.NewInt(1 << 31), + initBond, + } + for _, c := range errorCases { + unbondShares := sdk.NewDecFromInt(c) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.False(t, got.IsOK(), "expected unbond msg to fail") + } + + leftBonded := initBond.Sub(sdk.NewDecFromInt(sdk.NewInt(int64(numUnbonds))).Mul(unbondShares).TruncateInt()) + + // should be unable to unbond one more than we have + unbondShares = sdk.NewDecFromInt(leftBonded.Add(sdk.NewInt(1))) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.False(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares.String(), leftBonded) + + // should be able to unbond just what we have + unbondShares = sdk.NewDecFromInt(leftBonded) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded) +} + +func TestMultipleMsgCreateValidator(t *testing.T) { + initBond := sdk.NewIntWithDecimal(1000, 18) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) + + validatorAddrs := []sdk.ValAddress{sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), sdk.ValAddress(keep.Addrs[2])} + delegatorAddrs := []sdk.AccAddress{keep.Addrs[3], keep.Addrs[4], keep.Addrs[5]} + + // bond them all + for i, validatorAddr := range validatorAddrs { + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], sdk.NewIntWithDecimal(10, 18)) + got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + + //Check that the account is bonded + validators := keeper.GetValidators(ctx, 100) + require.Equal(t, (i + 1), len(validators)) + val := validators[i] + balanceExpd := initBond.Sub(sdk.NewIntWithDecimal(10, 18)) + balanceGot := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom) + require.Equal(t, i+1, len(validators), "expected %d validators got %d, validators: %v", i+1, len(validators), validators) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), val.DelegatorShares, "expected %d shares, got %d", 10, val.DelegatorShares) + require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } + + // unbond them all by removing delegation + for i, validatorAddr := range validatorAddrs { + _, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddrs[i], validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) // remove delegation + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + // Jump to finishTime for unbonding period and remove from unbonding queue + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + // Check that the validator is deleted from state + validators := keeper.GetValidators(ctx, 100) + require.Equal(t, len(validatorAddrs)-(i+1), len(validators), + "expected %d validators got %d", len(validatorAddrs)-(i+1), len(validators)) + + _, found = keeper.GetValidator(ctx, validatorAddr) + require.False(t, found) + + expBalance := initBond + gotBalance := accMapper.GetAccount(ctx, delegatorAddrs[i]).GetCoins().AmountOf(params.BondDenom) + require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance) + } +} + +func TestMultipleMsgDelegate(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr, delegatorAddrs := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1:] + _ = setInstantUnbondPeriod(keeper, ctx) + + //first make a validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + // delegate multiple parties + for i, delegatorAddr := range delegatorAddrs { + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewIntWithDecimal(10, 18)) + got := handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + + //Check that the account is bonded + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) + require.True(t, found) + require.NotNil(t, bond, "expected delegatee bond %d to exist", bond) + } + + // unbond them all + for i, delegatorAddr := range delegatorAddrs { + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + //Check that the account is unbonded + _, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) + require.False(t, found) + } +} + +func TestJailValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + _ = setInstantUnbondPeriod(keeper, ctx) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewIntWithDecimal(10, 18)) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + // unbond the validators bond portion + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.Jailed, "%v", validator) + + // test that this address cannot yet be bonded too because is jailed + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.False(t, got.IsOK(), "expected error, got %v", got) + + // test that the delegator can still withdraw their bonds + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + + // verify that the pubkey can now be reused + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) +} + +func TestValidatorQueue(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewIntWithDecimal(10, 18)) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + EndBlocker(ctx, keeper) + + // unbond the all self-delegation to put validator in unbonding state + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error: %v", got) + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime) + EndBlocker(ctx, keeper) + origHeader := ctx.BlockHeader() + + validator, found := keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should still be unbonding at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonding, "%v", validator) + + // should be in unbonded state at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + validator, found = keeper.GetValidator(ctx, validatorAddr) + require.True(t, found) + require.True(t, validator.GetStatus() == sdk.Unbonded, "%v", validator) +} + +func TestUnbondingPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + EndBlocker(ctx, keeper) + + // begin unbonding + msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") + origHeader := ctx.BlockHeader() + + _, found := keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") + + // cannot complete unbonding at same time + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") + + // cannot complete unbonding at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.True(t, found, "should not have unbonded") + + // can complete unbonding at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + _, found = keeper.GetUnbondingDelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr) + require.False(t, found, "should have unbonded") +} + +func TestUnbondingFromUnbondingValidator(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] + + // create the validator + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // bond a delegator + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewIntWithDecimal(10, 18)) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected ok, got %v", got) + + // unbond the validators bond portion + msgBeginUnbondingValidator := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr), validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + + // change the ctx to Block Time one second before the validator would have unbonded + var finishTime time.Time + types.MsgCdc.MustUnmarshalBinaryLengthPrefixed(got.Data, &finishTime) + ctx = ctx.WithBlockTime(finishTime.Add(time.Second * -1)) + + // unbond the delegator from the validator + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + + // move the Block time forward by one second + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(time.Second * 1)) + + // Run the EndBlocker + EndBlocker(ctx, keeper) + + // Check to make sure that the unbonding delegation is no longer in state + // (meaning it was deleted in the above EndBlocker) + _, found := keeper.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + require.False(t, found, "should be removed from state") +} + +func TestRedelegationPeriod(t *testing.T) { + ctx, AccMapper, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr, validatorAddr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) + denom := keeper.GetParams(ctx).BondDenom + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 * time.Second + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + + // initial balance + amt1 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) + + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // balance should have been subtracted after creation + amt2 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) + require.Equal(t, amt1.Sub(sdk.NewIntWithDecimal(10, 18)), amt2, "expected coins to be subtracted") + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + bal1 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins() + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // origin account should not lose tokens as with a regular delegation + bal2 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins() + require.Equal(t, bal1, bal2) + + origHeader := ctx.BlockHeader() + + // cannot complete redelegation at same time + EndBlocker(ctx, keeper) + _, found := keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.True(t, found, "should not have unbonded") + + // cannot complete redelegation at time 6 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 6)) + EndBlocker(ctx, keeper) + _, found = keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.True(t, found, "should not have unbonded") + + // can complete redelegation at time 7 seconds later + ctx = ctx.WithBlockTime(origHeader.Time.Add(time.Second * 7)) + EndBlocker(ctx, keeper) + _, found = keeper.GetRedelegation(ctx, sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2) + require.False(t, found, "should have unbonded") +} + +func TestTransitiveRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + validatorAddr3 := sdk.ValAddress(keep.Addrs[2]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegation to next validator while first delegation exists + msgBeginRedelegate = NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr2, validatorAddr3, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // complete first redelegation + EndBlocker(ctx, keeper) + + // now should be able to redelegate from the second validator to the third + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestConflictingRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 1 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond them + EndBlocker(ctx, keeper) + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18))) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegate again while first redelegation still exists + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // progress forward in time + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second)) + + // complete first redelegation + EndBlocker(ctx, keeper) + + // now should be able to redelegate again + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestUnbondingWhenExcessValidators(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + validatorAddr1 := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + validatorAddr3 := sdk.ValAddress(keep.Addrs[2]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 0 + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // add three validators + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr1, keep.PKs[0], sdk.NewIntWithDecimal(50,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(keeper.GetLastValidators(ctx))) + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], sdk.NewIntWithDecimal(30,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) + + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) + + // unbond the valdator-2 + msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + // because there are extra validators waiting to get in, the queued + // validator (aka. validator-1) should make it into the bonded group, thus + // the total number of validators should stay the same + vals := keeper.GetLastValidators(ctx) + require.Equal(t, 2, len(vals), "vals %v", vals) + val1, found := keeper.GetValidator(ctx, validatorAddr1) + require.True(t, found) + require.Equal(t, sdk.Bonded, val1.Status, "%v", val1) +} + +func TestBondUnbondRedelegateSlashTwice(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + valA, valB, del := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), keep.Addrs[2] + consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) + + msgCreateValidator := NewTestMsgCreateValidator(valA, keep.PKs[0], sdk.NewIntWithDecimal(10,18)) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = NewTestMsgCreateValidator(valB, keep.PKs[1], sdk.NewIntWithDecimal(10,18)) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // delegate 10 stake + msgDelegate := NewTestMsgDelegate(del, valA, sdk.NewIntWithDecimal(10, 18)) + got = handleMsgDelegate(ctx, msgDelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgDelegate") + + // apply Tendermint updates + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + + // a block passes + ctx = ctx.WithBlockHeight(1) + + // begin unbonding 4 stake + msgBeginUnbonding := NewMsgBeginUnbonding(del, valA, sdk.NewDecFromInt(sdk.NewIntWithDecimal(4, 18))) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginUnbonding") + + // begin redelegate 6 stake + msgBeginRedelegate := NewMsgBeginRedelegate(del, valA, valB, sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgBeginRedelegate") + + // destination delegation should have 6 shares + delegation, found := keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), delegation.Shares) + + // must apply validator updates + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + + // slash the validator by half + keeper.Slash(ctx, consAddr0, 0, 20, sdk.NewDecWithPrec(5, 1)) + + // unbonding delegation should have been slashed by half + unbonding, found := keeper.GetUnbondingDelegation(ctx, del, valA) + require.True(t, found) + require.Equal(t, sdk.NewIntWithDecimal(2, 18), unbonding.Balance.Amount) + + // redelegation should have been slashed by half + redelegation, found := keeper.GetRedelegation(ctx, del, valA, valB) + require.True(t, found) + require.Equal(t, sdk.NewIntWithDecimal(3, 18), redelegation.Balance.Amount) + + // destination delegation should have been slashed by half + delegation, found = keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(3, 18)), delegation.Shares) + + // validator power should have been reduced by half + validator, found := keeper.GetValidator(ctx, valA) + require.True(t, found) + require.Equal(t, sdk.NewDec(5), validator.GetPower()) + + // slash the validator for an infraction committed after the unbonding and redelegation begin + ctx = ctx.WithBlockHeight(3) + keeper.Slash(ctx, consAddr0, 2, 10, sdk.NewDecWithPrec(5, 1)) + + // unbonding delegation should be unchanged + unbonding, found = keeper.GetUnbondingDelegation(ctx, del, valA) + require.True(t, found) + require.Equal(t, sdk.NewIntWithDecimal(2, 18), unbonding.Balance.Amount) + + // redelegation should be unchanged + redelegation, found = keeper.GetRedelegation(ctx, del, valA, valB) + require.True(t, found) + require.Equal(t, sdk.NewIntWithDecimal(3, 18), redelegation.Balance.Amount) + + // destination delegation should be unchanged + delegation, found = keeper.GetDelegation(ctx, del, valB) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(3, 18)), delegation.Shares) + + // end blocker + EndBlocker(ctx, keeper) + + // validator power should have been reduced to zero + // validator should be in unbonding state + validator, _ = keeper.GetValidator(ctx, valA) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} diff --git a/modules/stake/keeper/delegation.go b/modules/stake/keeper/delegation.go index 752bc506d..de882efe7 100644 --- a/modules/stake/keeper/delegation.go +++ b/modules/stake/keeper/delegation.go @@ -37,6 +37,20 @@ func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegati return delegations } +// return all delegations to a specific validator. Useful for querier. +func (k Keeper) GetValidatorDelegations(ctx sdk.Context, valAddr sdk.ValAddress) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + delegation := types.MustUnmarshalDelegation(k.cdc, iterator.Key(), iterator.Value()) + if delegation.GetValidatorAddr().Equals(valAddr) { + delegations = append(delegations, delegation) + } + } + return delegations +} + // return a given amount of all the delegations from a delegator func (k Keeper) GetDelegatorDelegations(ctx sdk.Context, delegator sdk.AccAddress, maxRetrieve uint16) (delegations []types.Delegation) { diff --git a/modules/stake/keeper/delegation_test.go b/modules/stake/keeper/delegation_test.go new file mode 100644 index 000000000..fb7d866e5 --- /dev/null +++ b/modules/stake/keeper/delegation_test.go @@ -0,0 +1,826 @@ +package keeper + +import ( + "fmt" + "testing" + "time" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// tests GetDelegation, GetDelegatorDelegations, SetDelegation, RemoveDelegation, GetDelegatorDelegations +func TestDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(10, 0)) + pool := keeper.GetPool(ctx) + + //construct the validators + amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8), sdk.NewInt(7)} + var validators [3]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + } + + keeper.SetPool(ctx, pool) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) + + // first add a validators[0] to delegate too + + bond1to1 := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: sdk.NewDec(9), + } + + // check the empty keeper first + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found) + + // set and retrieve a record + keeper.SetDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, bond1to1.Equal(resBond)) + + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewDec(99) + keeper.SetDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, bond1to1.Equal(resBond)) + + // add some more records + bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewDec(9), 0} + bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewDec(9), 1} + bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewDec(9), 2} + bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewDec(9), 3} + bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewDec(9), 4} + keeper.SetDelegation(ctx, bond1to2) + keeper.SetDelegation(ctx, bond1to3) + keeper.SetDelegation(ctx, bond2to1) + keeper.SetDelegation(ctx, bond2to2) + keeper.SetDelegation(ctx, bond2to3) + + // test all bond retrieve capabilities + resBonds := keeper.GetDelegatorDelegations(ctx, addrDels[0], 5) + require.Equal(t, 3, len(resBonds)) + require.True(t, bond1to1.Equal(resBonds[0])) + require.True(t, bond1to2.Equal(resBonds[1])) + require.True(t, bond1to3.Equal(resBonds[2])) + resBonds = keeper.GetAllDelegatorDelegations(ctx, addrDels[0]) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[0], 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) + require.Equal(t, 3, len(resBonds)) + require.True(t, bond2to1.Equal(resBonds[0])) + require.True(t, bond2to2.Equal(resBonds[1])) + require.True(t, bond2to3.Equal(resBonds[2])) + allBonds := keeper.GetAllDelegations(ctx) + require.Equal(t, 6, len(allBonds)) + require.True(t, bond1to1.Equal(allBonds[0])) + require.True(t, bond1to2.Equal(allBonds[1])) + require.True(t, bond1to3.Equal(allBonds[2])) + require.True(t, bond2to1.Equal(allBonds[3])) + require.True(t, bond2to2.Equal(allBonds[4])) + require.True(t, bond2to3.Equal(allBonds[5])) + + resVals := keeper.GetDelegatorValidators(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resVals)) + resVals = keeper.GetDelegatorValidators(ctx, addrDels[1], 4) + require.Equal(t, 3, len(resVals)) + + for i := 0; i < 3; i++ { + + resVal, err := keeper.GetDelegatorValidator(ctx, addrDels[0], addrVals[i]) + require.Nil(t, err) + require.Equal(t, addrVals[i], resVal.GetOperator()) + + resVal, err = keeper.GetDelegatorValidator(ctx, addrDels[1], addrVals[i]) + require.Nil(t, err) + require.Equal(t, addrVals[i], resVal.GetOperator()) + + resDels := keeper.GetValidatorDelegations(ctx, addrVals[i]) + require.Len(t, resDels, 2) + } + + // delete a record + keeper.RemoveDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) + require.False(t, found) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) + require.Equal(t, 2, len(resBonds)) + require.True(t, bond2to1.Equal(resBonds[0])) + require.True(t, bond2to2.Equal(resBonds[1])) + + resBonds = keeper.GetAllDelegatorDelegations(ctx, addrDels[1]) + require.Equal(t, 2, len(resBonds)) + + // delete all the records from delegator 2 + keeper.RemoveDelegation(ctx, bond2to1) + keeper.RemoveDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) + require.False(t, found) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) + require.False(t, found) + resBonds = keeper.GetDelegatorDelegations(ctx, addrDels[1], 5) + require.Equal(t, 0, len(resBonds)) +} + +// tests Get/Set/Remove UnbondingDelegation +func TestUnbondingDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + MinTime: time.Unix(0, 0), + Balance: sdk.NewInt64Coin(types.StakeDenom, 5), + } + + // set and retrieve a record + keeper.SetUnbondingDelegation(ctx, ubd) + resUnbond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Equal(resUnbond)) + + // modify a records, save, and retrieve + ubd.Balance = sdk.NewInt64Coin(types.StakeDenom, 21) + keeper.SetUnbondingDelegation(ctx, ubd) + + resUnbonds := keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(resUnbonds)) + + resUnbonds = keeper.GetAllUnbondingDelegations(ctx, addrDels[0]) + require.Equal(t, 1, len(resUnbonds)) + + resUnbond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Equal(resUnbond)) + + // delete a record + keeper.RemoveUnbondingDelegation(ctx, ubd) + _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found) + + resUnbonds = keeper.GetUnbondingDelegations(ctx, addrDels[0], 5) + require.Equal(t, 0, len(resUnbonds)) + + resUnbonds = keeper.GetAllUnbondingDelegations(ctx, addrDels[0]) + require.Equal(t, 0, len(resUnbonds)) + +} + +func TestUnbondDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + + //create a validator and a delegator to that validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + + pool = keeper.GetPool(ctx) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), pool.BondedTokens) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.BondedTokens()) + + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + amount, err := keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + require.NoError(t, err) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), amount) // shares to be added to an unbonding delegation / redelegation + + delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + pool = keeper.GetPool(ctx) + + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(4, 18)), delegation.Shares) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(4, 18)), validator.BondedTokens()) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), pool.LooseTokens, "%v", pool) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(4, 18)), pool.BondedTokens) +} + +// test removing all self delegation from a validator which should +// shift it from the bonded to unbonded state +func TestUndelegateSelfDelegation(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.Tokens) + require.Equal(t, sdk.Unbonding, validator.Status) +} + +func TestUndelegateFromUnbondingValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + selfDelegation := types.Delegation{ + DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + //change the context + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + require.NoError(t, err) + + // retrieve the unbonding delegation + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + require.True(t, ubd.Balance.IsEqual(sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)))) + assert.Equal(t, blockHeight, ubd.CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) +} + +func TestUndelegateFromUnbondedValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) + + // unbond the all self-delegation to put validator in unbonding state + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + // unbond the validator + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // Make sure validator is still in state because there is still an outstanding delegation + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, validator.Status, sdk.Unbonded) + + // unbond some of the other delegation's shares + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + require.NoError(t, err) + + // no ubd should have been found, coins should have been returned direcly to account + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.False(t, found, "%v", ubd) + + // unbond rest of the other delegation's shares + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(4, 18))) + require.NoError(t, err) + + // now validator should now be deleted from state + validator, found = keeper.GetValidator(ctx, addrVals[0]) + fmt.Println(validator) + require.False(t, found) +} + +func TestUnbondingAllDelegationFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) + + // unbond the all self-delegation to put validator in unbonding state + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // unbond all the remaining delegation + _, err = keeper.BeginUnbonding(ctx, addrDels[0], addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // validator should still be in state and still be in unbonding state + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, validator.Status, sdk.Unbonding) + + // unbond the validator + ctx = ctx.WithBlockTime(validator.UnbondingMinTime) + keeper.UnbondAllMatureValidatorQueue(ctx) + + // validator should now be deleted from state + _, found = keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) +} + +// Make sure that that the retrieving the delegations doesn't affect the state +func TestGetRedelegationsFromValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: time.Unix(0, 0), + SharesSrc: sdk.NewDec(5), + SharesDst: sdk.NewDec(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, sdk.ZeroInt()) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: time.Unix(0, 0), + SharesSrc: sdk.NewDec(5), + SharesDst: sdk.NewDec(5), + } + + // test shouldn't have and redelegations + has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + require.False(t, has) + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resRed, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + redelegations := keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + // check if has the redelegation + has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + require.True(t, has) + + // modify a records, save, and retrieve + rd.SharesSrc = sdk.NewDec(21) + rd.SharesDst = sdk.NewDec(21) + keeper.SetRedelegation(ctx, rd) + + resRed, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.True(t, rd.Equal(resRed)) + + redelegations = keeper.GetRedelegationsFromValidator(ctx, addrVals[0]) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 1, len(redelegations)) + require.True(t, redelegations[0].Equal(resRed)) + + // delete a record + keeper.RemoveRedelegation(ctx, rd) + _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found) + + redelegations = keeper.GetRedelegations(ctx, addrDels[0], 5) + require.Equal(t, 0, len(redelegations)) + + redelegations = keeper.GetAllRedelegations(ctx, addrDels[0]) + require.Equal(t, 0, len(redelegations)) +} + +func TestRedelegateToSameValidator(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDec(30) + + // create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + require.Equal(t, int64(10), issuedShares.RoundInt64()) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[0], sdk.NewDec(5)) + require.Error(t, err) + +} + +func TestRedelegateSelfDelegation(t *testing.T) { + + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + keeper.SetPool(ctx, pool) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) + require.Equal(t, sdk.Bonded, validator2.Status) + + // create a second delegation to this validator + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + _, err := keeper.BeginRedelegation(ctx, val0AccAddr, addrVals[0], addrVals[1], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.Tokens) + require.Equal(t, sdk.Unbonding, validator.Status) +} + +func TestRedelegateFromUnbondingValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator.BondIntraTxCounter = 1 + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2.BondIntraTxCounter = 2 + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) + + header := ctx.BlockHeader() + blockHeight := int64(10) + header.Height = blockHeight + blockTime := time.Unix(333, 0) + header.Time = blockTime + ctx = ctx.WithBlockHeader(header) + + // unbond the all self-delegation to put validator in unbonding state + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, blockHeight, validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, blockTime.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + //change the context + header = ctx.BlockHeader() + blockHeight2 := int64(20) + header.Height = blockHeight2 + blockTime2 := time.Unix(444, 0) + header.Time = blockTime2 + ctx = ctx.WithBlockHeader(header) + + // unbond some of the other delegation's shares + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + require.NoError(t, err) + + // retrieve the unbonding delegation + ubd, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + require.True(t, ubd.Balance.IsEqual(sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)))) + assert.Equal(t, blockHeight, ubd.CreationHeight) + assert.True(t, blockTime.Add(params.UnbondingTime).Equal(ubd.MinTime)) +} + +func TestRedelegateFromUnbondedValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18)) + + //create a validator with a self-delegation + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + + validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) + selfDelegation := types.Delegation{ + DelegatorAddr: val0AccAddr, + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, selfDelegation) + + // create a second delegation to this validator + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + validator.BondIntraTxCounter = 1 + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + pool = keeper.GetPool(ctx) + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + // create a second validator + validator2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + validator2.BondIntraTxCounter = 2 + validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), issuedShares) + keeper.SetPool(ctx, pool) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) + require.Equal(t, sdk.Bonded, validator2.Status) + + ctx = ctx.WithBlockHeight(10) + ctx = ctx.WithBlockTime(time.Unix(333, 0)) + + // unbond the all self-delegation to put validator in unbonding state + _, err := keeper.BeginUnbonding(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18))) + require.NoError(t, err) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, ctx.BlockHeight(), validator.UnbondingHeight) + params := keeper.GetParams(ctx) + require.True(t, ctx.BlockHeader().Time.Add(params.UnbondingTime).Equal(validator.UnbondingMinTime)) + + // unbond the validator + keeper.unbondingToUnbonded(ctx, validator) + + // redelegate some of the delegation's shares + _, err = keeper.BeginRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1], sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + require.NoError(t, err) + + // no red should have been found + red, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.False(t, found, "%v", red) +} diff --git a/modules/stake/keeper/keeper.go b/modules/stake/keeper/keeper.go index be623a58b..840a0ad4b 100644 --- a/modules/stake/keeper/keeper.go +++ b/modules/stake/keeper/keeper.go @@ -112,6 +112,21 @@ func (k Keeper) SetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress, store.Set(GetLastValidatorPowerKey(operator), bz) } +// Iterate over last validator powers. +func (k Keeper) IterateLastValidatorPowers(ctx sdk.Context, handler func(operator sdk.ValAddress, power sdk.Int) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Key()[len(LastValidatorPowerKey):]) + var power sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &power) + if handler(addr, power) { + break + } + } +} + // Delete the last validator power. func (k Keeper) DeleteLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) { store := ctx.KVStore(k.storeKey) diff --git a/modules/stake/keeper/keeper_test.go b/modules/stake/keeper/keeper_test.go new file mode 100644 index 000000000..06e8f9930 --- /dev/null +++ b/modules/stake/keeper/keeper_test.go @@ -0,0 +1,40 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake/types" +) + +func TestParams(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + expParams := types.DefaultParams() + + //check that the empty keeper loads the default + resParams := keeper.GetParams(ctx) + require.True(t, expParams.Equal(resParams)) + + //modify a params, save, and retrieve + expParams.MaxValidators = 777 + keeper.SetParams(ctx, expParams) + resParams = keeper.GetParams(ctx) + require.True(t, expParams.Equal(resParams)) +} + +func TestPool(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + expPool := types.InitialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + require.True(t, expPool.Equal(resPool)) + + //modify a params, save, and retrieve + expPool.BondedTokens = sdk.NewDec(777) + keeper.SetPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + require.True(t, expPool.Equal(resPool)) +} diff --git a/modules/stake/keeper/key_test.go b/modules/stake/keeper/key_test.go new file mode 100644 index 000000000..a6070687c --- /dev/null +++ b/modules/stake/keeper/key_test.go @@ -0,0 +1,91 @@ +package keeper + +import ( + "encoding/hex" + "math/big" + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake/types" + "github.com/stretchr/testify/assert" + "github.com/tendermint/tendermint/crypto/ed25519" +) + +var ( + pk1 = ed25519.GenPrivKeyFromSecret([]byte{1}).PubKey() + pk2 = ed25519.GenPrivKeyFromSecret([]byte{2}).PubKey() + pk3 = ed25519.GenPrivKeyFromSecret([]byte{3}).PubKey() + addr1 = pk1.Address() + addr2 = pk2.Address() + addr3 = pk3.Address() +) + +func TestGetValidatorPowerRank(t *testing.T) { + valAddr1 := sdk.ValAddress(addr1) + emptyDesc := types.Description{} + val1 := types.NewValidator(valAddr1, pk1, emptyDesc) + val1.Tokens = sdk.ZeroDec() + val2, val3, val4 := val1, val1, val1 + val2.Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1, 18)) + val3.Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + x := new(big.Int).Exp(big.NewInt(2), big.NewInt(40), big.NewInt(0)) + x = new(big.Int).Mul(x, sdk.NewIntWithDecimal(1, 18).BigInt()) + val4.Tokens = sdk.NewDecFromBigInt(x) + + tests := []struct { + validator types.Validator + wantHex string + }{ + {val1, "230000000000000000ffffffffffffffffffff"}, + {val2, "230000000000000001ffffffffffffffffffff"}, + {val3, "23000000000000000affffffffffffffffffff"}, + {val4, "230000010000000000ffffffffffffffffffff"}, + } + for i, tt := range tests { + got := hex.EncodeToString(getValidatorPowerRank(tt.validator)) + + assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) + } +} + +func TestGetREDByValDstIndexKey(t *testing.T) { + tests := []struct { + delAddr sdk.AccAddress + valSrcAddr sdk.ValAddress + valDstAddr sdk.ValAddress + wantHex string + }{ + {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), + "3663d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), + "363ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, + {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), + "363ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, + } + for i, tt := range tests { + got := hex.EncodeToString(GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) + + assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) + } +} + +func TestGetREDByValSrcIndexKey(t *testing.T) { + tests := []struct { + delAddr sdk.AccAddress + valSrcAddr sdk.ValAddress + valDstAddr sdk.ValAddress + wantHex string + }{ + {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), + "3563d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), + "355ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, + {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), + "3563d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, + } + for i, tt := range tests { + got := hex.EncodeToString(GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) + + assert.Equal(t, tt.wantHex, got, "Keys did not match on test case %d", i) + } +} diff --git a/modules/stake/keeper/slash.go b/modules/stake/keeper/slash.go index afffa303e..f4535ec65 100644 --- a/modules/stake/keeper/slash.go +++ b/modules/stake/keeper/slash.go @@ -32,7 +32,7 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh slashAmount := sdk.NewDec(power).Mul(slashFactor) // ref https://github.com/irisnet/irishub/issues/1348 // ref https://github.com/irisnet/irishub/issues/1471 - //Multiply 1*10^18 to calculate equivalent iris-atto amount + //Multiply 1*10^18 to calculate equivalent stake denom amount tokenPrecision := sdk.NewIntWithDecimal(1, 18) slashAmount = slashAmount.MulInt(tokenPrecision) diff --git a/modules/stake/keeper/slash_test.go b/modules/stake/keeper/slash_test.go new file mode 100644 index 000000000..e77243b14 --- /dev/null +++ b/modules/stake/keeper/slash_test.go @@ -0,0 +1,544 @@ +package keeper + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/stake/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// TODO integrate with test_common.go helper (CreateTestInput) +// setup helper function - creates two validators +func setupHelper(t *testing.T, amt sdk.Int) (sdk.Context, Keeper, types.Params) { + + // setup + ctx, _, keeper := CreateTestInput(t, false, amt) + params := keeper.GetParams(ctx) + pool := keeper.GetPool(ctx) + numVals := 3 + pool.LooseTokens = sdk.NewDecFromInt(amt.Mul(sdk.NewInt(int64(numVals)))) + + // add numVals validators + for i := 0; i < numVals; i++ { + validator := types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, amt) + validator.BondIntraTxCounter = int16(i) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDecFromInt(amt)) + keeper.SetPool(ctx, pool) + validator = TestingUpdateValidator(keeper, ctx, validator) + keeper.SetValidatorByConsAddr(ctx, validator) + } + pool = keeper.GetPool(ctx) + + return ctx, keeper, params +} + +//_________________________________________________________________________________ + +// tests Jail, Unjail +func TestRevocation(t *testing.T) { + + // setup + ctx, keeper, _ := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + addr := addrVals[0] + consAddr := sdk.ConsAddress(PKs[0].Address()) + + // initial state + val, found := keeper.GetValidator(ctx, addr) + require.True(t, found) + require.False(t, val.GetJailed()) + + // test jail + keeper.Jail(ctx, consAddr) + val, found = keeper.GetValidator(ctx, addr) + require.True(t, found) + require.True(t, val.GetJailed()) + + // test unjail + keeper.Unjail(ctx, consAddr) + val, found = keeper.GetValidator(ctx, addr) + require.True(t, found) + require.False(t, val.GetJailed()) +} + +// tests slashUnbondingDelegation +func TestSlashUnbondingDelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + fraction := sdk.NewDecWithPrec(5, 1) + + // set an unbonding delegation + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: time.Unix(0, 0), + InitialBalance: sdk.NewInt64Coin(params.BondDenom, 10), + Balance: sdk.NewInt64Coin(params.BondDenom, 10), + } + keeper.SetUnbondingDelegation(ctx, ubd) + + // unbonding started prior to the infraction height, stake didn't contribute + slashAmount := keeper.slashUnbondingDelegation(ctx, ubd, 1, fraction) + require.Equal(t, int64(0), slashAmount.RoundInt64()) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(0), slashAmount.RoundInt64()) + + // test valid slash, before expiration timestamp and to which stake contributed + oldPool := keeper.GetPool(ctx) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) + keeper.SetUnbondingDelegation(ctx, ubd) + slashAmount = keeper.slashUnbondingDelegation(ctx, ubd, 0, fraction) + require.Equal(t, int64(5), slashAmount.RoundInt64()) + ubd, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + + // initialbalance unchanged + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 10), ubd.InitialBalance) + + // balance decreased + require.Equal(t, sdk.NewInt64Coin(params.BondDenom, 5), ubd.Balance) + newPool := keeper.GetPool(ctx) + require.Equal(t, int64(5), oldPool.LooseTokens.Sub(newPool.LooseTokens).RoundInt64()) +} + +// tests slashRedelegation +func TestSlashRedelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + fraction := sdk.NewDecWithPrec(5, 1) + + // set a redelegation + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + // expiration timestamp (beyond which the redelegation shouldn't be slashed) + MinTime: time.Unix(0, 0), + SharesSrc: sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), + SharesDst: sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), + InitialBalance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(10, 18)), + Balance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(10, 18)), + } + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), + } + keeper.SetDelegation(ctx, del) + + // started redelegating prior to the current height, stake didn't contribute to infraction + validator, found := keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount := keeper.slashRedelegation(ctx, validator, rd, 1, fraction) + require.Equal(t, sdk.ZeroDec(), slashAmount) + + // after the expiration time, no longer eligible for slashing + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(10, 0)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, sdk.ZeroDec(), slashAmount) + + // test valid slash, before expiration timestamp and to which stake contributed + oldPool := keeper.GetPool(ctx) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Unix(0, 0)}) + keeper.SetRedelegation(ctx, rd) + validator, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + slashAmount = keeper.slashRedelegation(ctx, validator, rd, 0, fraction) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), slashAmount) + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // initialbalance unchanged + require.Equal(t, sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(10, 18)), rd.InitialBalance) + + // balance decreased + require.Equal(t, sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(5, 18)), rd.Balance) + + // shares decreased + del, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[1]) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), del.Shares) + + // pool bonded tokens decreased + newPool := keeper.GetPool(ctx) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) +} + +// tests Slash at a future height (must panic) +func TestSlashAtFutureHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + require.Panics(t, func() { keeper.Slash(ctx, consAddr, 1, 10, fraction) }) +} + +// test slash at a negative height +// this just represents pre-genesis and should have the same effect as slashing at height 0 +func TestSlashAtNegativeHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, -2, 10, fraction) + + // read updated state + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + newPool := keeper.GetPool(ctx) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) + + validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) + // power decreased + require.Equal(t, sdk.NewDec(5), validator.GetPower()) + // pool bonded shares decreased + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) +} + +// tests Slash at the current height +func TestSlashValidatorAtCurrentHeight(t *testing.T) { + ctx, keeper, _ := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, ctx.BlockHeight(), 10, fraction) + + // read updated state + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + newPool := keeper.GetPool(ctx) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates), "cons addr: %v, updates: %v", []byte(consAddr), updates) + + validator = keeper.mustGetValidator(ctx, validator.OperatorAddr) + // power decreased + require.Equal(t, sdk.NewDec(5), validator.GetPower()) + // pool bonded shares decreased + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) +} + +// tests Slash at a previous height with an unbonding delegation +func TestSlashWithUnbondingDelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + // set an unbonding delegation + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 11, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: time.Unix(0, 0), + InitialBalance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(4, 18)), + Balance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(4, 18)), + } + keeper.SetUnbondingDelegation(ctx, ubd) + + // slash validator for the first time + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, 10, 10, fraction) + + // end block + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + + // read updating unbonding delegation + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewIntWithDecimal(2, 18), ubd.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(3, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 3 - 6 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, sdk.NewDec(7), validator.GetPower()) + + // slash validator again + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance decreased again + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // bonded tokens burned again + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 3 again + require.Equal(t, sdk.NewDec(4), validator.GetPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/irisnet/irishub/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // bonded tokens burned again + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(9, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 3 again + require.Equal(t, sdk.NewDec(1), validator.GetPower()) + + // slash validator again + // all originally bonded stake has been slashed, so this will have no effect + // on the unbonding delegation, but it will slash stake bonded since the infraction + // this may not be the desirable behaviour, ref https://github.com/irisnet/irishub/issues/1440 + ctx = ctx.WithBlockHeight(13) + keeper.Slash(ctx, consAddr, 9, 10, fraction) + ubd, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + require.True(t, found) + // balance unchanged + require.Equal(t, sdk.NewInt(0), ubd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // just 1 bonded token burned again since that's all the validator now has + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // read updated validator + // power decreased by 1 again, validator is out of stake + // validator should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} + +// tests Slash at a previous height with a redelegation +func TestSlashWithRedelegation(t *testing.T) { + ctx, keeper, params := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + consAddr := sdk.ConsAddress(PKs[0].Address()) + fraction := sdk.NewDecWithPrec(5, 1) + + // set a redelegation + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 11, + MinTime: time.Unix(0, 0), + SharesSrc: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + SharesDst: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + InitialBalance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)), + Balance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)), + } + keeper.SetRedelegation(ctx, rd) + + // set the associated delegation + del := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + } + keeper.SetDelegation(ctx, del) + + // update bonded tokens + pool := keeper.GetPool(ctx) + pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18))) + keeper.SetPool(ctx, pool) + + // slash validator + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, 10, 10, fraction) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewIntWithDecimal(3, 18), rd.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(5, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 2 - 4 stake originally bonded at the time of infraction + // was still bonded at the time of discovery and was slashed by half, 4 stake + // bonded at the time of discovery hadn't been bonded at the time of infraction + // and wasn't slashed + require.Equal(t, sdk.NewDec(8), validator.GetPower()) + + // slash the validator again + ctx = ctx.WithBlockHeight(12) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + require.NotPanics(t, func() { keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) }) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased, now zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // seven bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(12, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + // power decreased by 4 + require.Equal(t, sdk.NewDec(4), validator.GetPower()) + + // slash the validator again, by 100% + ctx = ctx.WithBlockHeight(12) + validator, found = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.True(t, found) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance still zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // four more bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(16, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // read updated validator + // validator decreased to zero power, should be in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) + + // slash the validator again, by 100% + // no stake remains to be slashed + ctx = ctx.WithBlockHeight(12) + // validator still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) + keeper.Slash(ctx, consAddr, 10, 10, sdk.OneDec()) + + // read updating redelegation + rd, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance still zero + require.Equal(t, sdk.NewInt(0), rd.Balance.Amount) + // read updated pool + newPool = keeper.GetPool(ctx) + // no more bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(16, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + // power still zero, still in unbonding period + validator, _ = keeper.GetValidatorByConsAddr(ctx, consAddr) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} + +// tests Slash at a previous height with both an unbonding delegation and a redelegation +func TestSlashBoth(t *testing.T) { + ctx, keeper, params := setupHelper(t, sdk.NewIntWithDecimal(10, 18)) + fraction := sdk.NewDecWithPrec(5, 1) + + // set a redelegation + rdA := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 11, + // expiration timestamp (beyond which the redelegation shouldn't be slashed) + MinTime: time.Unix(0, 0), + SharesSrc: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + SharesDst: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + InitialBalance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)), + Balance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(6, 18)), + } + keeper.SetRedelegation(ctx, rdA) + + // set the associated delegation + delA := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[1], + Shares: sdk.NewDecFromInt(sdk.NewIntWithDecimal(6, 18)), + } + keeper.SetDelegation(ctx, delA) + + // set an unbonding delegation + ubdA := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 11, + // expiration timestamp (beyond which the unbonding delegation shouldn't be slashed) + MinTime: time.Unix(0, 0), + InitialBalance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(4, 18)), + Balance: sdk.NewCoin(params.BondDenom, sdk.NewIntWithDecimal(4, 18)), + } + keeper.SetUnbondingDelegation(ctx, ubdA) + + // slash validator + ctx = ctx.WithBlockHeight(12) + oldPool := keeper.GetPool(ctx) + validator, found := keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 10, 10, fraction) + + // read updating redelegation + rdA, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + require.True(t, found) + // balance decreased + require.Equal(t, sdk.NewIntWithDecimal(3, 18), rdA.Balance.Amount) + // read updated pool + newPool := keeper.GetPool(ctx) + // loose tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(2, 18)), oldPool.LooseTokens.Sub(newPool.LooseTokens)) + // bonded tokens burned + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(3, 18)), oldPool.BondedTokens.Sub(newPool.BondedTokens)) + // read updated validator + validator, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + // power not decreased, all stake was bonded since + require.Equal(t, sdk.NewDec(10), validator.GetPower()) +} diff --git a/modules/stake/keeper/test_common.go b/modules/stake/keeper/test_common.go index b1e50b188..c4d49f273 100644 --- a/modules/stake/keeper/test_common.go +++ b/modules/stake/keeper/test_common.go @@ -75,7 +75,7 @@ func MakeTestCodec() *codec.Codec { } // hogpodge of all sorts of input required for testing -func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountKeeper, Keeper) { +func CreateTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Context, auth.AccountKeeper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") tkeyStake := sdk.NewTransientStoreKey("transient_stake") @@ -112,10 +112,10 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context for _, addr := range Addrs { pool := keeper.GetPool(ctx) _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.BondDenom(ctx), sdk.NewInt(initCoins)}, + {keeper.BondDenom(ctx), initCoins}, }) require.Nil(t, err) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDecFromInt(initCoins)) keeper.SetPool(ctx, pool) } diff --git a/modules/stake/keeper/validator_test.go b/modules/stake/keeper/validator_test.go new file mode 100644 index 000000000..4ac01479c --- /dev/null +++ b/modules/stake/keeper/validator_test.go @@ -0,0 +1,1066 @@ +package keeper + +import ( + "fmt" + "testing" + "time" + + "github.com/irisnet/irishub/modules/stake/types" + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +//_______________________________________________________ + +func TestSetValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(10, 18)) + pool := keeper.GetPool(ctx) + + valPubKey := PKs[0] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + // test how the validator is set from a purely unbonbed pool + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + require.Equal(t, sdk.Unbonded, validator.Status) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.DelegatorShares)) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) + + // ensure update + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, found := keeper.GetValidator(ctx, valAddr) + require.True(t, found) + require.Equal(t, 1, len(updates)) + require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) + + // after the save the validator should be bonded + require.Equal(t, sdk.Bonded, validator.Status) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)), validator.DelegatorShares)) + + // Check each store for being saved + resVal, found := keeper.GetValidator(ctx, valAddr) + assert.True(ValEq(t, validator, resVal)) + require.True(t, found) + + resVals := keeper.GetLastValidators(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, 1, len(resVals)) + require.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidators(ctx, 1) + require.Equal(t, 1, len(resVals)) + require.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidators(ctx, 10) + require.Equal(t, 1, len(resVals)) + require.True(ValEq(t, validator, resVals[0])) + + allVals := keeper.GetAllValidators(ctx) + require.Equal(t, 1, len(allVals)) +} + +func TestUpdateValidatorByPowerIndex(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + + // create a random pool + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10000, 18)) + pool.BondedTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1234, 18)) + keeper.SetPool(ctx, pool) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(100, 18)) + require.Equal(t, sdk.Unbonded, validator.Status) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)), validator.Tokens) + keeper.SetPool(ctx, pool) + TestingUpdateValidator(keeper, ctx, validator) + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)), validator.Tokens, "\nvalidator %v\npool %v", validator, pool) + + pool = keeper.GetPool(ctx) + power := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, validatorByPowerIndexExists(keeper, ctx, power)) + + // burn half the delegator shares + keeper.DeleteValidatorByPowerIndex(ctx, validator, pool) + validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewDec(2))) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(50, 18)), burned) + keeper.SetPool(ctx, pool) // update the pool + TestingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out + require.False(t, validatorByPowerIndexExists(keeper, ctx, power)) + + pool = keeper.GetPool(ctx) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + power = GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, validatorByPowerIndexExists(keeper, ctx, power)) +} + +func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { + numVals := 10 + maxVals := 5 + + // create context, keeper, and pool for tests + ctx, _, keeper := CreateTestInput(t, false, sdk.ZeroInt()) + pool := keeper.GetPool(ctx) + + // create keeper parameters + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(maxVals) + keeper.SetParams(ctx, params) + + // create a random pool + pool.LooseTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10000, 18)) + pool.BondedTokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(1234, 18)) + keeper.SetPool(ctx, pool) + + validators := make([]types.Validator, numVals) + for i := 0; i < len(validators); i++ { + moniker := fmt.Sprintf("val#%d", int64(i)) + val := types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{Moniker: moniker}) + val.BondHeight = int64(i) + val.BondIntraTxCounter = int16(i) + val, pool, _ = val.AddTokensFromDel(pool, sdk.NewIntWithDecimal(int64((i+1)*10), 18)) + + keeper.SetPool(ctx, pool) + val = TestingUpdateValidator(keeper, ctx, val) + validators[i] = val + } + + nextCliffVal := validators[numVals-maxVals+1] + + // remove enough tokens to kick out the validator below the current cliff + // validator and next in line cliff validator + keeper.DeleteValidatorByPowerIndex(ctx, nextCliffVal, pool) + nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, sdk.NewDecFromInt(sdk.NewIntWithDecimal(21, 18))) + keeper.SetPool(ctx, pool) + nextCliffVal = TestingUpdateValidator(keeper, ctx, nextCliffVal) + + expectedValStatus := map[int]sdk.BondStatus{ + 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, + 0: sdk.Unbonding, 1: sdk.Unbonding, 2: sdk.Unbonding, 3: sdk.Unbonding, 6: sdk.Unbonding, + } + + // require all the validators have their respective statuses + for valIdx, status := range expectedValStatus { + valAddr := validators[valIdx].OperatorAddr + val, _ := keeper.GetValidator(ctx, valAddr) + + assert.Equal( + t, status, val.GetStatus(), + fmt.Sprintf("expected validator at index %v to have status: %s", + valIdx, + sdk.BondStatusToString(status))) + } +} + +func TestSlashToZeroPowerRemoved(t *testing.T) { + // initialize setup + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(100, 18)) + pool := keeper.GetPool(ctx) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(100, 18)) + require.Equal(t, sdk.Unbonded, validator.Status) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)), validator.Tokens) + keeper.SetPool(ctx, pool) + keeper.SetValidatorByConsAddr(ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) + require.Equal(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(100, 18)), validator.Tokens, "\nvalidator %v\npool %v", validator, pool) + + // slash the validator by 100% + consAddr0 := sdk.ConsAddress(PKs[0].Address()) + keeper.Slash(ctx, consAddr0, 0, 100, sdk.OneDec()) + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + // validator should be unbonding + validator, _ = keeper.GetValidator(ctx, addrVals[0]) + require.Equal(t, validator.GetStatus(), sdk.Unbonding) +} + +// This function tests UpdateValidator, GetValidator, GetLastValidators, RemoveValidator +func TestValidatorBasics(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + pool := keeper.GetPool(ctx) + + //construct the validators + var validators [3]types.Validator + amts := []sdk.Int{sdk.NewIntWithDecimal(9, 18), sdk.NewIntWithDecimal(8, 18), sdk.NewIntWithDecimal(7, 18)} + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i].Status = sdk.Unbonded + validators[i].Tokens = sdk.ZeroDec() + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(9, 18)), validators[0].Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(8, 18)), validators[1].Tokens)) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(7, 18)), validators[2].Tokens)) + + // check the empty keeper first + _, found := keeper.GetValidator(ctx, addrVals[0]) + require.False(t, found) + resVals := keeper.GetLastValidators(ctx) + require.Zero(t, len(resVals)) + + resVals = keeper.GetValidators(ctx, 2) + require.Zero(t, len(resVals)) + + pool = keeper.GetPool(ctx) + assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) + + // set and retrieve a record + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + keeper.SetValidatorByConsAddr(ctx, validators[0]) + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + // retrieve from consensus + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.ConsAddress(PKs[0].Address())) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + resVal, found = keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(PKs[0])) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetLastValidators(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + assert.Equal(t, sdk.Bonded, validators[0].Status) + assert.True(sdk.DecEq(t, sdk.NewDecFromInt(sdk.NewIntWithDecimal(9, 18)), validators[0].BondedTokens())) + + pool = keeper.GetPool(ctx) + assert.True(sdk.DecEq(t, pool.BondedTokens, validators[0].BondedTokens())) + + // modify a records, save, and retrieve + validators[0].Status = sdk.Bonded + validators[0].Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + validators[0].DelegatorShares = sdk.NewDecFromInt(sdk.NewIntWithDecimal(10, 18)) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + resVal, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetLastValidators(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // add other validators + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) + resVal, found = keeper.GetValidator(ctx, addrVals[1]) + require.True(t, found) + assert.True(ValEq(t, validators[1], resVal)) + resVal, found = keeper.GetValidator(ctx, addrVals[2]) + require.True(t, found) + assert.True(ValEq(t, validators[2], resVal)) + + resVals = keeper.GetLastValidators(ctx) + require.Equal(t, 3, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here + assert.True(ValEq(t, validators[1], resVals[1])) + assert.True(ValEq(t, validators[2], resVals[2])) + + // remove a record + validators[1].Status = sdk.Unbonded // First must set to Unbonded. + keeper.SetValidator(ctx, validators[1]) // ... + keeper.RemoveValidator(ctx, validators[1].OperatorAddr) // Now it can be removed. + _, found = keeper.GetValidator(ctx, addrVals[1]) + require.False(t, found) +} + +// test how the validators are sorted, tests GetBondedValidatorsByPower +func GetValidatorSortingUnmixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i].Status = sdk.Bonded + validators[i].Tokens = sdk.NewDec(amt) + validators[i].DelegatorShares = sdk.NewDec(amt) + TestingUpdateValidator(keeper, ctx, validators[i]) + } + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetBondedValidatorsByPower(ctx) + assert.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(100), resValidators[2].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(1), resValidators[3].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(0), resValidators[4].BondedTokens(), "%v", resValidators) + assert.Equal(t, validators[3].OperatorAddr, resValidators[0].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[4].OperatorAddr, resValidators[1].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[1].OperatorAddr, resValidators[2].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[2].OperatorAddr, resValidators[3].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[0].OperatorAddr, resValidators[4].OperatorAddr, "%v", resValidators) + + // test a basic increase in voting power + validators[3].Tokens = sdk.NewDec(500) + TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + + // test a decrease in voting power + validators[3].Tokens = sdk.NewDec(300) + TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // test equal voting power, different age + validators[3].Tokens = sdk.NewDec(200) + ctx = ctx.WithBlockHeight(10) + TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + require.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) + require.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) + + // no change in voting power - no change in sort + ctx = ctx.WithBlockHeight(20) + TestingUpdateValidator(keeper, ctx, validators[4]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + + // change in voting power of both validators, both still in v-set, no age change + validators[3].Tokens = sdk.NewDec(300) + validators[4].Tokens = sdk.NewDec(300) + TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + ctx = ctx.WithBlockHeight(30) + TestingUpdateValidator(keeper, ctx, validators[4]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n, "%v", resValidators) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) +} + +func GetValidatorSortingMixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 1, 400, 200} + + n := len(amts) + var validators [5]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i].DelegatorShares = sdk.NewDec(amt) + } + + validators[0].Status = sdk.Bonded + validators[1].Status = sdk.Bonded + validators[2].Status = sdk.Bonded + validators[0].Tokens = sdk.NewDec(amts[0]) + validators[1].Tokens = sdk.NewDec(amts[1]) + validators[2].Tokens = sdk.NewDec(amts[2]) + + validators[3].Status = sdk.Bonded + validators[4].Status = sdk.Bonded + validators[3].Tokens = sdk.NewDec(amts[3]) + validators[4].Tokens = sdk.NewDec(amts[4]) + + for i := range amts { + TestingUpdateValidator(keeper, ctx, validators[i]) + } + val0, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[0])) + require.True(t, found) + val1, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[1])) + require.True(t, found) + val2, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[2])) + require.True(t, found) + val3, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[3])) + require.True(t, found) + val4, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[4])) + require.True(t, found) + require.Equal(t, sdk.Unbonded, val0.Status) + require.Equal(t, sdk.Unbonded, val1.Status) + require.Equal(t, sdk.Unbonded, val2.Status) + require.Equal(t, sdk.Bonded, val3.Status) + require.Equal(t, sdk.Bonded, val4.Status) + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetBondedValidatorsByPower(ctx) + assert.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewDec(400), resValidators[0].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(200), resValidators[1].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(100), resValidators[2].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(1), resValidators[3].BondedTokens(), "%v", resValidators) + assert.Equal(t, sdk.NewDec(0), resValidators[4].BondedTokens(), "%v", resValidators) + assert.Equal(t, validators[3].OperatorAddr, resValidators[0].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[4].OperatorAddr, resValidators[1].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[1].OperatorAddr, resValidators[2].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[2].OperatorAddr, resValidators[3].OperatorAddr, "%v", resValidators) + assert.Equal(t, validators[0].OperatorAddr, resValidators[4].OperatorAddr, "%v", resValidators) +} + +// TODO separate out into multiple tests +func TestGetValidatorsEdgeCases(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + var found bool + + // now 2 max resValidators + params := keeper.GetParams(ctx) + nMax := uint16(2) + params.MaxValidators = nMax + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []sdk.Int{sdk.ZeroInt(), sdk.NewIntWithDecimal(100, 18), sdk.NewIntWithDecimal(400, 18), sdk.NewIntWithDecimal(400, 18)} + var validators [4]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + moniker := fmt.Sprintf("val#%d", int64(i)) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{Moniker: moniker}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetPool(ctx, pool) + validators[i] = TestingUpdateValidator(keeper, ctx, validators[i]) + } + + for i := range amts { + validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) + require.True(t, found) + } + resValidators := keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[2], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + pool := keeper.GetPool(ctx) + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewIntWithDecimal(500, 18)) + keeper.SetPool(ctx, pool) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // A validator which leaves the gotValidator set due to a decrease in voting power, + // then increases to the original voting power, does not get its spot back in the + // case of a tie. + + // validator 3 enters bonded validator set + ctx = ctx.WithBlockHeight(40) + + validators[3], found = keeper.GetValidator(ctx, validators[3].OperatorAddr) + require.True(t, found) + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewIntWithDecimal(1, 18)) + keeper.SetPool(ctx, pool) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[3], resValidators[1])) + + // validator 3 kicked out temporarily + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) + validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewDecFromInt(sdk.NewIntWithDecimal(201, 18))) + keeper.SetPool(ctx, pool) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + + // validator 4 does not get spot back + keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewIntWithDecimal(200, 18)) + keeper.SetPool(ctx, pool) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, nMax, uint16(len(resValidators))) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) + validator, exists := keeper.GetValidator(ctx, validators[3].OperatorAddr) + require.Equal(t, exists, true) + require.Equal(t, int64(40), validator.BondHeight) +} + +func TestValidatorBondHeight(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + pool := keeper.GetPool(ctx) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + var validators [3]types.Validator + validators[0] = types.NewValidator(sdk.ValAddress(Addrs[0]), PKs[0], types.Description{}) + validators[1] = types.NewValidator(sdk.ValAddress(Addrs[1]), PKs[1], types.Description{}) + validators[2] = types.NewValidator(sdk.ValAddress(Addrs[2]), PKs[2], types.Description{}) + validators[0].BondIntraTxCounter = 0 + validators[1].BondIntraTxCounter = 1 + validators[2].BondIntraTxCounter = 2 + + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewIntWithDecimal(200, 18)) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewIntWithDecimal(100, 18)) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewIntWithDecimal(100, 18)) + keeper.SetPool(ctx, pool) + + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + + //////////////////////////////////////// + // If two validators both increase to the same voting power in the same block, + // the one with the first transaction should become bonded + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) + + pool = keeper.GetPool(ctx) + + resValidators := keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, uint16(len(resValidators)), params.MaxValidators) + + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[1], resValidators[1])) + keeper.DeleteValidatorByPowerIndex(ctx, validators[1], pool) + keeper.DeleteValidatorByPowerIndex(ctx, validators[2], pool) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewIntWithDecimal(50, 18)) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewIntWithDecimal(50, 18)) + keeper.SetPool(ctx, pool) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + require.Equal(t, params.MaxValidators, uint16(len(resValidators))) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +func TestFullValidatorSetPowerChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + params := keeper.GetParams(ctx) + max := 2 + params.MaxValidators = uint16(2) + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []sdk.Int{sdk.ZeroInt(), sdk.NewIntWithDecimal(100, 18), sdk.NewIntWithDecimal(400, 18), sdk.NewIntWithDecimal(400, 18), sdk.NewIntWithDecimal(200, 18)} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetPool(ctx, pool) + TestingUpdateValidator(keeper, ctx, validators[i]) + } + for i := range amts { + var found bool + validators[i], found = keeper.GetValidator(ctx, validators[i].OperatorAddr) + require.True(t, found) + } + assert.Equal(t, sdk.Unbonded, validators[0].Status) + assert.Equal(t, sdk.Unbonding, validators[1].Status) + assert.Equal(t, sdk.Bonded, validators[2].Status) + assert.Equal(t, sdk.Bonded, validators[3].Status) + assert.Equal(t, sdk.Unbonded, validators[4].Status) + resValidators := keeper.GetBondedValidatorsByPower(ctx) + assert.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs + assert.True(ValEq(t, validators[3], resValidators[1])) + + // test a swap in voting power + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewIntWithDecimal(600, 18)) + keeper.SetPool(ctx, pool) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + resValidators = keeper.GetBondedValidatorsByPower(ctx) + assert.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +func TestApplyAndReturnValidatorSetUpdatesAllNone(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18)} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + + // test from nothing to something + // tendermintUpdate set: {} -> {c1, c3} + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + pool := keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) + keeper.SetValidator(ctx, validators[1]) + keeper.SetValidatorByPowerIndex(ctx, validators[1], pool) + + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + assert.Equal(t, 2, len(updates)) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + assert.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) + assert.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) +} + +func TestApplyAndReturnValidatorSetUpdatesIdentical(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18)} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // test identical, + // tendermintUpdate set: {} -> {} + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) +} + +func TestApplyAndReturnValidatorSetUpdatesSingleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18)} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // test single value change + // tendermintUpdate set: {} -> {c1'} + validators[0].Status = sdk.Bonded + validators[0].Tokens = sdk.NewDecFromInt(sdk.NewIntWithDecimal(600, 18)) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) +} + +func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18)} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewIntWithDecimal(190, 18)) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewIntWithDecimal(80, 18)) + keeper.SetPool(ctx, pool) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) +} + +func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18), sdk.NewIntWithDecimal(5, 18), sdk.NewIntWithDecimal(15, 18), sdk.NewIntWithDecimal(25, 18)} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + pool := keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[2]) + keeper.SetValidatorByPowerIndex(ctx, validators[2], pool) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + pool = keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[3]) + keeper.SetValidatorByPowerIndex(ctx, validators[3], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[3], _ = keeper.GetValidator(ctx, validators[3].OperatorAddr) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[3].ABCIValidatorUpdate(), updates[0]) + + // test validtor added at the end + // tendermintUpdate set: {} -> {c0} + pool = keeper.GetPool(ctx) + keeper.SetValidator(ctx, validators[4]) + keeper.SetValidatorByPowerIndex(ctx, validators[4], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[4], _ = keeper.GetValidator(ctx, validators[4].OperatorAddr) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[4].ABCIValidatorUpdate(), updates[0]) +} + +func TestApplyAndReturnValidatorSetUpdatesWithCliffValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + params := types.DefaultParams() + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + amts := []sdk.Int{sdk.NewIntWithDecimal(10, 18), sdk.NewIntWithDecimal(20, 18), sdk.NewIntWithDecimal(5, 18)} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // test validator added at the end but not inserted in the valset + // tendermintUpdate set: {} -> {} + TestingUpdateValidator(keeper, ctx, validators[2]) + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 0, len(updates)) + + // test validator change its power and become a gotValidator (pushing out an existing) + // tendermintUpdate set: {} -> {c0, c4} + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + pool := keeper.GetPool(ctx) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewIntWithDecimal(10, 18)) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[2]) + keeper.SetValidatorByPowerIndex(ctx, validators[2], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + require.Equal(t, 2, len(updates), "%v", updates) + require.Equal(t, validators[0].ABCIValidatorUpdateZero(), updates[1]) + require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) +} + +func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + amts := []sdk.Int{sdk.NewIntWithDecimal(100, 18), sdk.NewIntWithDecimal(100, 18)} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(sdk.ValAddress(Addrs[i]), PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetPool(ctx, pool) + } + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // check initial power + require.Equal(t, sdk.NewDec(100), validators[0].GetPower()) + require.Equal(t, sdk.NewDec(100), validators[1].GetPower()) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18))) + validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewDecFromInt(sdk.NewIntWithDecimal(30, 18))) + keeper.SetPool(ctx, pool) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + + // power has changed + require.Equal(t, sdk.NewDec(80), validators[0].GetPower()) + require.Equal(t, sdk.NewDec(70), validators[1].GetPower()) + + // Tendermint updates should reflect power change + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) +} + +func TestApplyAndReturnValidatorSetUpdatesNewValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(3) + + keeper.SetParams(ctx, params) + + amts := []sdk.Int{sdk.NewIntWithDecimal(100, 18), sdk.NewIntWithDecimal(100, 18)} + var validators [2]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{}) + validators[i].BondIntraTxCounter = int16(i) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) + } + + // verify initial Tendermint updates are correct + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, len(validators), len(updates)) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[0]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) + + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // update initial validator set + for i, amt := range amts { + pool := keeper.GetPool(ctx) + keeper.DeleteValidatorByPowerIndex(ctx, validators[i], pool) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) + } + + // add a new validator that goes from zero power, to non-zero power, back to + // zero power + pool := keeper.GetPool(ctx) + valPubKey := PKs[len(validators)+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + amt := sdk.NewIntWithDecimal(100, 18) + + validator := types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, amt) + + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validator) + + validator, pool, _ = validator.RemoveDelShares(pool, sdk.NewDecFromInt(amt)) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) + + // add a new validator that increases in power + valPubKey = PKs[len(validators)+2] + valAddr = sdk.ValAddress(valPubKey.Address().Bytes()) + + validator = types.NewValidator(valAddr, valPubKey, types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewIntWithDecimal(500, 18)) + keeper.SetValidator(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, pool) + keeper.SetPool(ctx, pool) + + // verify initial Tendermint updates are correct + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + validator, _ = keeper.GetValidator(ctx, validator.OperatorAddr) + validators[0], _ = keeper.GetValidator(ctx, validators[0].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.Equal(t, len(validators)+1, len(updates)) + require.Equal(t, validator.ABCIValidatorUpdate(), updates[0]) + require.Equal(t, validators[0].ABCIValidatorUpdate(), updates[1]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[2]) +} + +func TestApplyAndReturnValidatorSetUpdatesBondTransition(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + params := keeper.GetParams(ctx) + params.MaxValidators = uint16(2) + + keeper.SetParams(ctx, params) + + amts := []sdk.Int{sdk.NewIntWithDecimal(100, 18), sdk.NewIntWithDecimal(200, 18), sdk.NewIntWithDecimal(300, 18)} + var validators [3]types.Validator + + // initialize some validators into the state + for i, amt := range amts { + pool := keeper.GetPool(ctx) + moniker := fmt.Sprintf("%d", i) + valPubKey := PKs[i+1] + valAddr := sdk.ValAddress(valPubKey.Address().Bytes()) + + validators[i] = types.NewValidator(valAddr, valPubKey, types.Description{Moniker: moniker}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) + } + + // verify initial Tendermint updates are correct + updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 2, len(updates)) + validators[2], _ = keeper.GetValidator(ctx, validators[2].OperatorAddr) + validators[1], _ = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.Equal(t, validators[2].ABCIValidatorUpdate(), updates[0]) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[1]) + + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // delegate to validator with lowest power but not enough to bond + ctx = ctx.WithBlockHeight(1) + pool := keeper.GetPool(ctx) + + var found bool + validators[0], found = keeper.GetValidator(ctx, validators[0].OperatorAddr) + require.True(t, found) + + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(1)) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) + + // verify initial Tendermint updates are correct + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) + + // create a series of events that will bond and unbond the validator with + // lowest power in a single block context (height) + ctx = ctx.WithBlockHeight(2) + pool = keeper.GetPool(ctx) + + validators[1], found = keeper.GetValidator(ctx, validators[1].OperatorAddr) + require.True(t, found) + + keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) + validators[0], pool, _ = validators[0].RemoveDelShares(pool, validators[0].DelegatorShares) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidatorByPowerIndex(ctx, validators[0], pool) + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 0, len(updates)) + + keeper.DeleteValidatorByPowerIndex(ctx, validators[1], pool) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewIntWithDecimal(250, 18)) + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[1]) + keeper.SetValidatorByPowerIndex(ctx, validators[1], pool) + + // verify initial Tendermint updates are correct + updates = keeper.ApplyAndReturnValidatorSetUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[1].ABCIValidatorUpdate(), updates[0]) + + require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) +} + +func TestUpdateValidatorCommission(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + ctx = ctx.WithBlockHeader(abci.Header{Time: time.Now().UTC()}) + + commission1 := types.NewCommissionWithTime( + sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), + sdk.NewDecWithPrec(1, 1), time.Now().UTC().Add(time.Duration(-1)*time.Hour), + ) + commission2 := types.NewCommission(sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(3, 1), sdk.NewDecWithPrec(1, 1)) + + val1 := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + val2 := types.NewValidator(addrVals[1], PKs[1], types.Description{}) + + val1, _ = val1.SetInitialCommission(commission1) + val2, _ = val2.SetInitialCommission(commission2) + + keeper.SetValidator(ctx, val1) + keeper.SetValidator(ctx, val2) + + testCases := []struct { + validator types.Validator + newRate sdk.Dec + expectedErr bool + }{ + {val1, sdk.ZeroDec(), true}, + {val2, sdk.NewDecWithPrec(-1, 1), true}, + {val2, sdk.NewDecWithPrec(4, 1), true}, + {val2, sdk.NewDecWithPrec(3, 1), true}, + {val2, sdk.NewDecWithPrec(2, 1), false}, + } + + for i, tc := range testCases { + commission, err := keeper.UpdateValidatorCommission(ctx, tc.validator, tc.newRate) + + if tc.expectedErr { + require.Error(t, err, "expected error for test case #%d with rate: %s", i, tc.newRate) + } else { + tc.validator.Commission = commission + keeper.SetValidator(ctx, tc.validator) + val, found := keeper.GetValidator(ctx, tc.validator.OperatorAddr) + + require.True(t, found, + "expected to find validator for test case #%d with rate: %s", i, tc.newRate, + ) + require.NoError(t, err, + "unexpected error for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, tc.newRate, val.Commission.Rate, + "expected new validator commission rate for test case #%d with rate: %s", i, tc.newRate, + ) + require.Equal(t, ctx.BlockHeader().Time, val.Commission.UpdateTime, + "expected new validator commission update time for test case #%d with rate: %s", i, tc.newRate, + ) + } + } +} diff --git a/modules/stake/querier/queryable.go b/modules/stake/querier/queryable.go index cfafed452..3e458c8d4 100644 --- a/modules/stake/querier/queryable.go +++ b/modules/stake/querier/queryable.go @@ -15,6 +15,7 @@ const ( QueryDelegatorDelegations = "delegatorDelegations" QueryDelegatorUnbondingDelegations = "delegatorUnbondingDelegations" QueryDelegatorRedelegations = "delegatorRedelegations" + QueryValidatorDelegations = "validatorDelegations" QueryValidatorUnbondingDelegations = "validatorUnbondingDelegations" QueryValidatorRedelegations = "validatorRedelegations" QueryDelegator = "delegator" @@ -34,6 +35,8 @@ func NewQuerier(k keep.Keeper, cdc *codec.Codec) sdk.Querier { return queryValidators(ctx, cdc, k) case QueryValidator: return queryValidator(ctx, cdc, req, k) + case QueryValidatorDelegations: + return queryValidatorDelegations(ctx, cdc, req, k) case QueryValidatorUnbondingDelegations: return queryValidatorUnbondingDelegations(ctx, cdc, req, k) case QueryValidatorRedelegations: @@ -73,6 +76,7 @@ type QueryDelegatorParams struct { // defines the params for the following queries: // - 'custom/stake/validator' +// - 'custom/stake/validatorDelegations' // - 'custom/stake/validatorUnbondingDelegations' // - 'custom/stake/validatorRedelegations' type QueryValidatorParams struct { @@ -88,6 +92,26 @@ type QueryBondsParams struct { ValidatorAddr sdk.ValAddress } +// creates a new QueryDelegatorParams +func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} +// creates a new QueryValidatorParams +func NewQueryValidatorParams(validatorAddr sdk.ValAddress) QueryValidatorParams { + return QueryValidatorParams{ + ValidatorAddr: validatorAddr, + } +} +// creates a new QueryBondsParams +func NewQueryBondsParams(delegatorAddr sdk.AccAddress, validatorAddr sdk.ValAddress) QueryBondsParams { + return QueryBondsParams{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + func queryValidators(ctx sdk.Context, cdc *codec.Codec, k keep.Keeper) (res []byte, err sdk.Error) { stakeParams := k.GetParams(ctx) validators := k.GetValidators(ctx, stakeParams.MaxValidators) @@ -119,6 +143,20 @@ func queryValidator(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k return res, nil } +func queryValidatorDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { + var params QueryValidatorParams + errRes := cdc.UnmarshalJSON(req.Data, ¶ms) + if errRes != nil { + return []byte{}, sdk.ErrUnknownAddress("") + } + delegations := k.GetValidatorDelegations(ctx, params.ValidatorAddr) + res, errRes = codec.MarshalJSONIndent(cdc, delegations) + if errRes != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", errRes.Error())) + } + return res, nil +} + func queryValidatorUnbondingDelegations(ctx sdk.Context, cdc *codec.Codec, req abci.RequestQuery, k keep.Keeper) (res []byte, err sdk.Error) { var params QueryValidatorParams diff --git a/modules/stake/querier/queryable_test.go b/modules/stake/querier/queryable_test.go new file mode 100644 index 000000000..c87769ef7 --- /dev/null +++ b/modules/stake/querier/queryable_test.go @@ -0,0 +1,398 @@ +package querier + +import ( + "testing" + + "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" + keep "github.com/irisnet/irishub/modules/stake/keeper" + "github.com/irisnet/irishub/modules/stake/types" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" +) + +var ( + addrAcc1, addrAcc2 = keep.Addrs[0], keep.Addrs[1] + addrVal1, addrVal2 = sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) + pk1, pk2 = keep.PKs[0], keep.PKs[1] +) + +func TestNewQuerier(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + pool := keeper.GetPool(ctx) + // Create Validators + amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8)} + var validators [2]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + validators[i].BondIntraTxCounter = int16(i) + keeper.SetValidator(ctx, validators[i]) + keeper.SetValidatorByPowerIndex(ctx, validators[i], pool) + } + keeper.SetPool(ctx, pool) + + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + querier := NewQuerier(keeper, cdc) + + bz, err := querier(ctx, []string{"other"}, query) + require.NotNil(t, err) + require.Nil(t, bz) + + _, err = querier(ctx, []string{"validators"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"pool"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"parameters"}, query) + require.Nil(t, err) + + queryValParams := NewQueryValidatorParams(addrVal1) + bz, errRes := cdc.MarshalJSON(queryValParams) + require.Nil(t, errRes) + + query.Path = "/custom/stake/validator" + query.Data = bz + + _, err = querier(ctx, []string{"validator"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"validatorUnbondingDelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"validatorRedelegations"}, query) + require.Nil(t, err) + + queryDelParams := NewQueryDelegatorParams(addrAcc2) + bz, errRes = cdc.MarshalJSON(queryDelParams) + require.Nil(t, errRes) + + query.Path = "/custom/stake/validator" + query.Data = bz + + _, err = querier(ctx, []string{"delegatorDelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorUnbondingDelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorRedelegations"}, query) + require.Nil(t, err) + + _, err = querier(ctx, []string{"delegatorValidators"}, query) + require.Nil(t, err) +} + +func TestQueryParametersPool(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(1000, 18)) + + res, err := queryParameters(ctx, cdc, keeper) + require.Nil(t, err) + + var params types.Params + errRes := cdc.UnmarshalJSON(res, ¶ms) + require.Nil(t, errRes) + require.Equal(t, keeper.GetParams(ctx), params) + + res, err = queryPool(ctx, cdc, keeper) + require.Nil(t, err) + + var pool types.Pool + errRes = cdc.UnmarshalJSON(res, &pool) + require.Nil(t, errRes) + require.Equal(t, keeper.GetPool(ctx), pool) +} + +func TestQueryValidators(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(10000, 18)) + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + + // Create Validators + amts := []sdk.Int{sdk.NewInt(9), sdk.NewInt(8)} + var validators [2]types.Validator + for i, amt := range amts { + validators[i] = types.NewValidator(sdk.ValAddress(keep.Addrs[i]), keep.PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + } + keeper.SetPool(ctx, pool) + keeper.SetValidator(ctx, validators[0]) + keeper.SetValidator(ctx, validators[1]) + + // Query Validators + queriedValidators := keeper.GetValidators(ctx, params.MaxValidators) + + res, err := queryValidators(ctx, cdc, keeper) + require.Nil(t, err) + + var validatorsResp []types.Validator + errRes := cdc.UnmarshalJSON(res, &validatorsResp) + require.Nil(t, errRes) + + require.Equal(t, len(queriedValidators), len(validatorsResp)) + require.ElementsMatch(t, queriedValidators, validatorsResp) + + // Query each validator + queryParams := NewQueryValidatorParams(addrVal1) + bz, errRes := cdc.MarshalJSON(queryParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/validator", + Data: bz, + } + res, err = queryValidator(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validator types.Validator + errRes = cdc.UnmarshalJSON(res, &validator) + require.Nil(t, errRes) + + require.Equal(t, queriedValidators[0], validator) +} + +func TestQueryDelegation(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(10000, 18)) + params := keeper.GetParams(ctx) + + // Create Validators and Delegation + val1 := types.NewValidator(addrVal1, pk1, types.Description{}) + keeper.SetValidator(ctx, val1) + pool := keeper.GetPool(ctx) + keeper.SetValidatorByPowerIndex(ctx, val1, pool) + + val2 := types.NewValidator(addrVal2, pk2, types.Description{}) + keeper.SetValidator(ctx, val2) + pool = keeper.GetPool(ctx) + keeper.SetValidatorByPowerIndex(ctx, val2, pool) + + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.StakeDenom, sdk.NewIntWithDecimal(20, 18)), val1, true) + + // apply TM updates + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + // Query Delegator bonded validators + queryParams := NewQueryDelegatorParams(addrAcc2) + bz, errRes := cdc.MarshalJSON(queryParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/delegatorValidators", + Data: bz, + } + + delValidators := keeper.GetDelegatorValidators(ctx, addrAcc2, params.MaxValidators) + + res, err := queryDelegatorValidators(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validatorsResp []types.Validator + errRes = cdc.UnmarshalJSON(res, &validatorsResp) + require.Nil(t, errRes) + + require.Equal(t, len(delValidators), len(validatorsResp)) + require.ElementsMatch(t, delValidators, validatorsResp) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidators(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query bonded validator + queryBondParams := NewQueryBondsParams(addrAcc2, addrVal1) + bz, errRes = cdc.MarshalJSON(queryBondParams) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorValidator", + Data: bz, + } + + res, err = queryDelegatorValidator(ctx, cdc, query, keeper) + require.Nil(t, err) + + var validator types.Validator + errRes = cdc.UnmarshalJSON(res, &validator) + require.Nil(t, errRes) + + require.Equal(t, delValidators[0], validator) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorValidator(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query delegation + + query = abci.RequestQuery{ + Path: "/custom/stake/delegation", + Data: bz, + } + + delegation, found := keeper.GetDelegation(ctx, addrAcc2, addrVal1) + require.True(t, found) + + res, err = queryDelegation(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegationRes types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegationRes) + require.Nil(t, errRes) + + require.Equal(t, delegation, delegationRes) + + // Query Delegator Delegations + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorDelegations", + Data: bz, + } + + res, err = queryDelegatorDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegatorDelegations []types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegatorDelegations) + require.Nil(t, errRes) + require.Len(t, delegatorDelegations, 1) + require.Equal(t, delegation, delegatorDelegations[0]) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query validator delegations + bz, errRes = cdc.MarshalJSON(NewQueryValidatorParams(addrVal1)) + require.Nil(t, errRes) + query = abci.RequestQuery{ + Path: "custom/stake/validatorDelegations", + Data: bz, + } + res, err = queryValidatorDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + var delegationsRes []types.Delegation + errRes = cdc.UnmarshalJSON(res, &delegationsRes) + require.Nil(t, errRes) + require.Equal(t, delegationsRes[0], delegation) + + // Query unbonging delegation + keeper.BeginUnbonding(ctx, addrAcc2, val1.OperatorAddr, sdk.NewDec(10)) + + queryBondParams = NewQueryBondsParams(addrAcc2, addrVal1) + bz, errRes = cdc.MarshalJSON(queryBondParams) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "/custom/stake/unbondingDelegation", + Data: bz, + } + + unbond, found := keeper.GetUnbondingDelegation(ctx, addrAcc2, addrVal1) + require.True(t, found) + + res, err = queryUnbondingDelegation(ctx, cdc, query, keeper) + require.Nil(t, err) + + var unbondRes types.UnbondingDelegation + errRes = cdc.UnmarshalJSON(res, &unbondRes) + require.Nil(t, errRes) + + require.Equal(t, unbond, unbondRes) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryUnbondingDelegation(ctx, cdc, query, keeper) + require.NotNil(t, err) + + // Query Delegator Delegations + + query = abci.RequestQuery{ + Path: "/custom/stake/delegatorUnbondingDelegations", + Data: bz, + } + + res, err = queryDelegatorUnbondingDelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var delegatorUbds []types.UnbondingDelegation + errRes = cdc.UnmarshalJSON(res, &delegatorUbds) + require.Nil(t, errRes) + require.Equal(t, unbond, delegatorUbds[0]) + + // error unknown request + query.Data = bz[:len(bz)-1] + + _, err = queryDelegatorUnbondingDelegations(ctx, cdc, query, keeper) + require.NotNil(t, err) +} + +func TestQueryRedelegations(t *testing.T) { + cdc := codec.New() + ctx, _, keeper := keep.CreateTestInput(t, false, sdk.NewIntWithDecimal(10000, 18)) + + // Create Validators and Delegation + val1 := types.NewValidator(addrVal1, pk1, types.Description{}) + val2 := types.NewValidator(addrVal2, pk2, types.Description{}) + keeper.SetValidator(ctx, val1) + keeper.SetValidator(ctx, val2) + + keeper.Delegate(ctx, addrAcc2, sdk.NewCoin(types.StakeDenom, sdk.NewIntWithDecimal(100, 18)), val1, true) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + keeper.BeginRedelegation(ctx, addrAcc2, val1.GetOperator(), val2.GetOperator(), sdk.NewDecFromInt(sdk.NewIntWithDecimal(20, 18))) + keeper.ApplyAndReturnValidatorSetUpdates(ctx) + + redelegation, found := keeper.GetRedelegation(ctx, addrAcc2, val1.OperatorAddr, val2.OperatorAddr) + require.True(t, found) + + // delegator redelegations + queryDelegatorParams := NewQueryDelegatorParams(addrAcc2) + bz, errRes := cdc.MarshalJSON(queryDelegatorParams) + require.Nil(t, errRes) + + query := abci.RequestQuery{ + Path: "/custom/stake/delegatorRedelegations", + Data: bz, + } + + res, err := queryDelegatorRedelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + var redsRes []types.Redelegation + errRes = cdc.UnmarshalJSON(res, &redsRes) + require.Nil(t, errRes) + + require.Equal(t, redelegation, redsRes[0]) + + // validator redelegations + queryValidatorParams := NewQueryValidatorParams(val1.GetOperator()) + bz, errRes = cdc.MarshalJSON(queryValidatorParams) + require.Nil(t, errRes) + + query = abci.RequestQuery{ + Path: "/custom/stake/validatorRedelegations", + Data: bz, + } + + res, err = queryValidatorRedelegations(ctx, cdc, query, keeper) + require.Nil(t, err) + + errRes = cdc.UnmarshalJSON(res, &redsRes) + require.Nil(t, errRes) + + require.Equal(t, redelegation, redsRes[0]) +} diff --git a/simulation/stake/invariants.go b/modules/stake/simulation/invariants.go similarity index 89% rename from simulation/stake/invariants.go rename to modules/stake/simulation/invariants.go index ffc6d779f..290cf0123 100644 --- a/simulation/stake/invariants.go +++ b/modules/stake/simulation/invariants.go @@ -10,9 +10,9 @@ import ( "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/stake/keeper" "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock/simulation" abci "github.com/tendermint/tendermint/abci/types" + "github.com/irisnet/irishub/modules/stake/types" ) // AllInvariants runs all invariants of the stake module. @@ -48,7 +48,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, loose := sdk.ZeroDec() bonded := sdk.ZeroDec() am.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(mock.MiniDenom))) + loose = loose.Add(sdk.NewDecFromInt(acc.GetCoins().AmountOf(types.StakeDenom))) return false }) k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool { @@ -70,19 +70,19 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, feePool := d.GetFeePool(ctx) // add outstanding fees - loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(mock.MiniDenom))) + loose = loose.Add(sdk.NewDecFromInt(f.GetCollectedFees(ctx).AmountOf(types.StakeDenom))) // add community pool - loose = loose.Add(feePool.CommunityPool.AmountOf(mock.MiniDenom)) + loose = loose.Add(feePool.CommunityPool.AmountOf(types.StakeDenom)) // add validator distribution pool - loose = loose.Add(feePool.ValPool.AmountOf(mock.MiniDenom)) + loose = loose.Add(feePool.ValPool.AmountOf(types.StakeDenom)) // add validator distribution commission and yet-to-be-withdrawn-by-delegators d.IterateValidatorDistInfos(ctx, func(_ int64, distInfo distribution.ValidatorDistInfo) (stop bool) { - loose = loose.Add(distInfo.DelPool.AmountOf(mock.MiniDenom)) - loose = loose.Add(distInfo.ValCommission.AmountOf(mock.MiniDenom)) + loose = loose.Add(distInfo.DelPool.AmountOf(types.StakeDenom)) + loose = loose.Add(distInfo.ValCommission.AmountOf(types.StakeDenom)) return false }, ) diff --git a/simulation/stake/msgs.go b/modules/stake/simulation/msgs.go similarity index 98% rename from simulation/stake/msgs.go rename to modules/stake/simulation/msgs.go index eb4b79edb..8c04489d3 100644 --- a/simulation/stake/msgs.go +++ b/modules/stake/simulation/msgs.go @@ -7,8 +7,8 @@ import ( "github.com/irisnet/irishub/modules/stake" "github.com/irisnet/irishub/modules/stake/keeper" "github.com/irisnet/irishub/baseapp" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" abci "github.com/tendermint/tendermint/abci/types" "math/rand" ) diff --git a/simulation/stake/sim_test.go b/modules/stake/simulation/sim_test.go similarity index 94% rename from simulation/stake/sim_test.go rename to modules/stake/simulation/sim_test.go index d53e7eab2..89bbd2259 100644 --- a/simulation/stake/sim_test.go +++ b/modules/stake/simulation/sim_test.go @@ -11,9 +11,10 @@ import ( "github.com/irisnet/irishub/modules/distribution" "github.com/irisnet/irishub/modules/params" "github.com/irisnet/irishub/modules/stake" - "github.com/irisnet/irishub/simulation/mock" - "github.com/irisnet/irishub/simulation/mock/simulation" + "github.com/irisnet/irishub/modules/mock" + "github.com/irisnet/irishub/modules/mock/simulation" abci "github.com/tendermint/tendermint/abci/types" + "github.com/irisnet/irishub/modules/stake/types" ) // TestStakeWithRandomMessages @@ -51,7 +52,7 @@ func TestStakeWithRandomMessages(t *testing.T) { } appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { - simulation.RandomSetGenesis(r, mapp, accs, []string{mock.DefaultStakeDenom}) + simulation.RandomSetGenesis(r, mapp, accs, []string{types.StakeDenom}) return json.RawMessage("{}") } diff --git a/modules/stake/stake.go b/modules/stake/stake.go index ec3b03991..5b4829ddb 100644 --- a/modules/stake/stake.go +++ b/modules/stake/stake.go @@ -84,7 +84,10 @@ var ( NewMsgBeginUnbonding = types.NewMsgBeginUnbonding NewMsgBeginRedelegate = types.NewMsgBeginRedelegate - NewQuerier = querier.NewQuerier + NewQuerier = querier.NewQuerier + NewQueryDelegatorParams = querier.NewQueryDelegatorParams + NewQueryValidatorParams = querier.NewQueryValidatorParams + NewQueryBondsParams = querier.NewQueryBondsParams ) const ( diff --git a/modules/stake/test_common.go b/modules/stake/test_common.go index ca3c0225c..39b7c1423 100644 --- a/modules/stake/test_common.go +++ b/modules/stake/test_common.go @@ -26,37 +26,37 @@ var ( commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) -func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator { return types.NewMsgCreateValidator( - address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commissionMsg, + address, pubKey, sdk.NewCoin(types.StakeDenom, amt), Description{}, commissionMsg, ) } func NewTestMsgCreateValidatorWithCommission(address sdk.ValAddress, pubKey crypto.PubKey, - amt int64, commissionRate sdk.Dec) MsgCreateValidator { + amt sdk.Int, commissionRate sdk.Dec) MsgCreateValidator { commission := NewCommissionMsg(commissionRate, sdk.OneDec(), sdk.ZeroDec()) return types.NewMsgCreateValidator( - address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commission, + address, pubKey, sdk.NewCoin(types.StakeDenom, amt), Description{}, commission, ) } -func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { +func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt sdk.Int) MsgDelegate { return MsgDelegate{ DelegatorAddr: delAddr, ValidatorAddr: valAddr, - Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), + Delegation: sdk.NewCoin(types.StakeDenom, amt), } } -func NewTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { +func NewTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator { return MsgCreateValidator{ Description: Description{}, Commission: commissionMsg, DelegatorAddr: delAddr, ValidatorAddr: valAddr, PubKey: valPubKey, - Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), + Delegation: sdk.NewCoin(types.StakeDenom, amt), } } diff --git a/modules/stake/types/delegation_test.go b/modules/stake/types/delegation_test.go new file mode 100644 index 000000000..5f84de4c1 --- /dev/null +++ b/modules/stake/types/delegation_test.go @@ -0,0 +1,117 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestDelegationEqual(t *testing.T) { + d1 := Delegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorAddr: addr2, + Shares: sdk.NewDec(100), + } + d2 := Delegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorAddr: addr2, + Shares: sdk.NewDec(100), + } + + ok := d1.Equal(d2) + require.True(t, ok) + + d2.ValidatorAddr = addr3 + d2.Shares = sdk.NewDec(200) + + ok = d1.Equal(d2) + require.False(t, ok) +} + +func TestDelegationHumanReadableString(t *testing.T) { + d := Delegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorAddr: addr2, + Shares: sdk.NewDec(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: sdk.AccAddress(addr1), + ValidatorAddr: addr2, + } + ud2 := UnbondingDelegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorAddr: addr2, + } + + ok := ud1.Equal(ud2) + require.True(t, ok) + + ud2.ValidatorAddr = addr3 + + ud2.MinTime = time.Unix(20*20*2, 0) + ok = ud1.Equal(ud2) + require.False(t, ok) +} + +func TestUnbondingDelegationHumanReadableString(t *testing.T) { + ud := UnbondingDelegation{ + DelegatorAddr: sdk.AccAddress(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: sdk.AccAddress(addr1), + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + r2 := Redelegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + } + + ok := r1.Equal(r2) + require.True(t, ok) + + r2.SharesDst = sdk.NewDec(10) + r2.SharesSrc = sdk.NewDec(20) + r2.MinTime = time.Unix(20*20*2, 0) + + ok = r1.Equal(r2) + require.False(t, ok) +} + +func TestRedelegationHumanReadableString(t *testing.T) { + r := Redelegation{ + DelegatorAddr: sdk.AccAddress(addr1), + ValidatorSrcAddr: addr2, + ValidatorDstAddr: addr3, + SharesDst: sdk.NewDec(10), + SharesSrc: sdk.NewDec(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/modules/stake/types/genesis.go b/modules/stake/types/genesis.go index 7d133022f..adb83aabe 100644 --- a/modules/stake/types/genesis.go +++ b/modules/stake/types/genesis.go @@ -10,10 +10,18 @@ type GenesisState struct { Params Params `json:"params"` IntraTxCounter int16 `json:"intra_tx_counter"` LastTotalPower sdk.Int `json:"last_total_power"` + LastValidatorPowers []LastValidatorPower `json:"last_validator_powers"` Validators []Validator `json:"validators"` Bonds []Delegation `json:"bonds"` UnbondingDelegations []UnbondingDelegation `json:"unbonding_delegations"` Redelegations []Redelegation `json:"redelegations"` + Exported bool `json:"exported"` +} + +// Last validator power, needed for validator set update logic +type LastValidatorPower struct { + Address sdk.ValAddress + Power sdk.Int } func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { diff --git a/modules/stake/types/msg_test.go b/modules/stake/types/msg_test.go new file mode 100644 index 000000000..b2bf8cdbb --- /dev/null +++ b/modules/stake/types/msg_test.go @@ -0,0 +1,204 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/irisnet/irishub/types" + "github.com/tendermint/tendermint/crypto" +) + +var ( + coinPos = sdk.NewInt64Coin(StakeDenom, 1000) + coinZero = sdk.NewInt64Coin(StakeDenom, 0) + coinNeg = sdk.NewInt64Coin(StakeDenom, -10000) +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgCreateValidator(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + + tests := []struct { + name, moniker, identity, website, details string + commissionMsg CommissionMsg + validatorAddr sdk.ValAddress + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", commission1, addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", commission1, addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", commission2, addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", commission2, emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission2, addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission1, addr1, pk1, coinNeg, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description, tc.commissionMsg) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgEditValidator +func TestMsgEditValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.ValAddress + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, true}, + {"partial description", "", "", "c", "", addr1, true}, + {"empty description", "", "", "", "", addr1, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + newRate := sdk.ZeroDec() + + msg := NewMsgEditValidator(tc.validatorAddr, description, &newRate) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic and GetSigners for MsgCreateValidatorOnBehalfOf +func TestMsgCreateValidatorOnBehalfOf(t *testing.T) { + commission1 := NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) + commission2 := NewCommissionMsg(sdk.NewDec(5), sdk.NewDec(5), sdk.NewDec(5)) + + tests := []struct { + name, moniker, identity, website, details string + commissionMsg CommissionMsg + delegatorAddr sdk.AccAddress + validatorAddr sdk.ValAddress + validatorPubKey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"partial description", "", "", "c", "", commission2, sdk.AccAddress(addr1), addr2, pk2, coinPos, true}, + {"empty description", "", "", "", "", commission1, sdk.AccAddress(addr1), addr2, pk2, coinPos, false}, + {"empty delegator address", "a", "b", "c", "d", commission1, sdk.AccAddress(emptyAddr), addr2, pk2, coinPos, false}, + {"empty validator address", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), emptyAddr, pk2, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinZero, false}, + {"negative bond", "a", "b", "c", "d", commission1, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", commission2, sdk.AccAddress(addr1), addr2, pk2, coinNeg, false}, + } + + for _, tc := range tests { + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) + msg := NewMsgCreateValidatorOnBehalfOf( + tc.delegatorAddr, tc.validatorAddr, tc.validatorPubKey, tc.bond, description, tc.commissionMsg, + ) + + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } + + msg := NewMsgCreateValidator(addr1, pk1, coinPos, Description{}, CommissionMsg{}) + addrs := msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr1)}, addrs, "Signers on default msg is wrong") + + msg = NewMsgCreateValidatorOnBehalfOf(sdk.AccAddress(addr2), addr1, pk1, coinPos, Description{}, CommissionMsg{}) + addrs = msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.AccAddress(addr2), sdk.AccAddress(addr1)}, addrs, "Signers for onbehalfof msg is wrong") +} + +// test ValidateBasic for MsgDelegate +func TestMsgDelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.ValAddress + bond sdk.Coin + expectPass bool + }{ + {"basic good", sdk.AccAddress(addr1), addr2, coinPos, true}, + {"self bond", sdk.AccAddress(addr1), addr1, coinPos, true}, + {"empty delegator", sdk.AccAddress(emptyAddr), addr1, coinPos, false}, + {"empty validator", sdk.AccAddress(addr1), emptyAddr, coinPos, false}, + {"empty bond", sdk.AccAddress(addr1), addr2, coinZero, false}, + {"negative bond", sdk.AccAddress(addr1), addr2, coinNeg, false}, + } + + for _, tc := range tests { + msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorSrcAddr sdk.ValAddress + validatorDstAddr sdk.ValAddress + sharesAmount sdk.Dec + expectPass bool + }{ + {"regular", sdk.AccAddress(addr1), addr2, addr3, sdk.NewDecWithPrec(1, 1), true}, + {"negative decimal", sdk.AccAddress(addr1), addr2, addr3, sdk.NewDecWithPrec(-1, 1), false}, + {"zero amount", sdk.AccAddress(addr1), addr2, addr3, sdk.ZeroDec(), false}, + {"empty delegator", sdk.AccAddress(emptyAddr), addr1, addr3, sdk.NewDecWithPrec(1, 1), false}, + {"empty source validator", sdk.AccAddress(addr1), emptyAddr, addr3, sdk.NewDecWithPrec(1, 1), false}, + {"empty destination validator", sdk.AccAddress(addr1), addr2, emptyAddr, sdk.NewDecWithPrec(1, 1), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.AccAddress + validatorAddr sdk.ValAddress + sharesAmount sdk.Dec + expectPass bool + }{ + {"regular", sdk.AccAddress(addr1), addr2, sdk.NewDecWithPrec(1, 1), true}, + {"negative decimal", sdk.AccAddress(addr1), addr2, sdk.NewDecWithPrec(-1, 1), false}, + {"zero amount", sdk.AccAddress(addr1), addr2, sdk.ZeroDec(), false}, + {"empty delegator", sdk.AccAddress(emptyAddr), addr1, sdk.NewDecWithPrec(1, 1), false}, + {"empty validator", sdk.AccAddress(addr1), emptyAddr, sdk.NewDecWithPrec(1, 1), false}, + } + + for _, tc := range tests { + msg := NewMsgBeginUnbonding(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} diff --git a/modules/stake/types/params.go b/modules/stake/types/params.go index 805a8460c..1ab21de16 100644 --- a/modules/stake/types/params.go +++ b/modules/stake/types/params.go @@ -19,6 +19,12 @@ const ( // if this is 1, the validator set at the end of a block will sign the block after the next. // Constant as this should not change without a hard fork. ValidatorUpdateDelay int64 = 1 + + // Stake coin denomination + StakeDenom = "iris-atto" + + // Stake coin denomination name + StakeDenomName = "iris" ) // nolint - Keys for parameter access @@ -59,7 +65,7 @@ func DefaultParams() Params { return Params{ UnbondingTime: defaultUnbondingTime, MaxValidators: 100, - BondDenom: "steak", + BondDenom: StakeDenom, } } diff --git a/modules/stake/types/params_test.go b/modules/stake/types/params_test.go new file mode 100644 index 000000000..c18700ef4 --- /dev/null +++ b/modules/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/modules/stake/types/pool_test.go b/modules/stake/types/pool_test.go new file mode 100644 index 000000000..cc5d01a7a --- /dev/null +++ b/modules/stake/types/pool_test.go @@ -0,0 +1,38 @@ +package types + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestPoolEqual(t *testing.T) { + p1 := InitialPool() + p2 := InitialPool() + require.True(t, p1.Equal(p2)) + p2.BondedTokens = sdk.NewDec(3) + require.False(t, p1.Equal(p2)) +} + +func TestAddBondedTokens(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + pool.BondedTokens = sdk.NewDec(10) + + pool = pool.looseTokensToBonded(sdk.NewDec(10)) + + require.True(sdk.DecEq(t, sdk.NewDec(20), pool.BondedTokens)) + require.True(sdk.DecEq(t, sdk.NewDec(0), pool.LooseTokens)) +} + +func TestRemoveBondedTokens(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + pool.BondedTokens = sdk.NewDec(10) + + pool = pool.bondedTokensToLoose(sdk.NewDec(5)) + + require.True(sdk.DecEq(t, sdk.NewDec(5), pool.BondedTokens)) + require.True(sdk.DecEq(t, sdk.NewDec(15), pool.LooseTokens)) +} diff --git a/modules/stake/types/validator_test.go b/modules/stake/types/validator_test.go new file mode 100644 index 000000000..1c323551d --- /dev/null +++ b/modules/stake/types/validator_test.go @@ -0,0 +1,305 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/irisnet/irishub/codec" + sdk "github.com/irisnet/irishub/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + 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{ + Website: "https://validator.cosmos", + Details: "Test validator", + } + + d2 := Description{ + Moniker: DoNotModifyDesc, + Identity: DoNotModifyDesc, + Website: DoNotModifyDesc, + Details: DoNotModifyDesc, + } + + d3 := Description{ + Moniker: "", + Identity: "", + Website: "", + Details: "", + } + + d, err := d1.UpdateDescription(d2) + require.Nil(t, err) + require.Equal(t, d, d1) + + d, err = d1.UpdateDescription(d3) + require.Nil(t, err) + require.Equal(t, d, d3) +} + +func TestABCIValidatorUpdate(t *testing.T) { + validator := NewValidator(addr1, pk1, Description{}) + + abciVal := validator.ABCIValidatorUpdate() + require.Equal(t, tmtypes.TM2PB.PubKey(validator.ConsPubKey), abciVal.PubKey) + require.Equal(t, validator.BondedTokens().RoundInt64(), abciVal.Power) +} + +func TestABCIValidatorUpdateZero(t *testing.T) { + validator := NewValidator(addr1, pk1, Description{}) + + abciVal := validator.ABCIValidatorUpdateZero() + require.Equal(t, tmtypes.TM2PB.PubKey(validator.ConsPubKey), abciVal.PubKey) + require.Equal(t, int64(0), abciVal.Power) +} + +func TestRemoveTokens(t *testing.T) { + + validator := Validator{ + OperatorAddr: addr1, + ConsPubKey: pk1, + Status: sdk.Bonded, + Tokens: sdk.NewDec(100), + DelegatorShares: sdk.NewDec(100), + } + + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + pool.BondedTokens = validator.BondedTokens() + + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + require.Equal(t, sdk.Bonded, validator.Status) + + // remove tokens and test check everything + validator, pool = validator.RemoveTokens(pool, sdk.NewDec(10)) + require.Equal(t, int64(90), validator.Tokens.RoundInt64()) + require.Equal(t, int64(90), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(20), pool.LooseTokens.RoundInt64()) + + // update validator to unbonded and remove some more tokens + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + require.Equal(t, sdk.Unbonded, validator.Status) + require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(110), pool.LooseTokens.RoundInt64()) + + validator, pool = validator.RemoveTokens(pool, sdk.NewDec(10)) + require.Equal(t, int64(80), validator.Tokens.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(110), pool.LooseTokens.RoundInt64()) +} + +func TestAddTokensValidatorBonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + validator := NewValidator(addr1, pk1, Description{}) + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + + require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) + + assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.BondedTokens())) +} + +func TestAddTokensValidatorUnbonding(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + validator := NewValidator(addr1, pk1, Description{}) + validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) + validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + + require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) + + assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) + assert.Equal(t, sdk.Unbonding, validator.Status) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) +} + +func TestAddTokensValidatorUnbonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(10) + validator := NewValidator(addr1, pk1, Description{}) + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + validator, pool, delShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) + + require.Equal(t, sdk.OneDec(), validator.DelegatorShareExRate()) + + assert.True(sdk.DecEq(t, sdk.NewDec(10), delShares)) + assert.Equal(t, sdk.Unbonded, validator.Status) + assert.True(sdk.DecEq(t, sdk.NewDec(10), validator.Tokens)) +} + +// TODO refactor to make simpler like the AddToken tests above +func TestRemoveDelShares(t *testing.T) { + valA := Validator{ + OperatorAddr: addr1, + ConsPubKey: pk1, + Status: sdk.Bonded, + Tokens: sdk.NewDec(100), + DelegatorShares: sdk.NewDec(100), + } + poolA := InitialPool() + poolA.LooseTokens = sdk.NewDec(10) + poolA.BondedTokens = valA.BondedTokens() + require.Equal(t, valA.DelegatorShareExRate(), sdk.OneDec()) + + // Remove delegator shares + valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewDec(10)) + assert.Equal(t, int64(10), coinsB.RoundInt64()) + assert.Equal(t, int64(90), valB.DelegatorShares.RoundInt64()) + assert.Equal(t, int64(90), valB.BondedTokens().RoundInt64()) + assert.Equal(t, int64(90), poolB.BondedTokens.RoundInt64()) + assert.Equal(t, int64(20), poolB.LooseTokens.RoundInt64()) + + // conservation of tokens + require.True(sdk.DecEq(t, + poolB.LooseTokens.Add(poolB.BondedTokens), + poolA.LooseTokens.Add(poolA.BondedTokens))) + + // specific case from random tests + poolTokens := sdk.NewDec(5102) + delShares := sdk.NewDec(115) + validator := Validator{ + OperatorAddr: addr1, + ConsPubKey: pk1, + Status: sdk.Bonded, + Tokens: poolTokens, + DelegatorShares: delShares, + } + pool := Pool{ + BondedTokens: sdk.NewDec(248305), + LooseTokens: sdk.NewDec(232147), + } + shares := sdk.NewDec(29) + _, newPool, tokens := validator.RemoveDelShares(pool, shares) + + exp, err := sdk.NewDecFromStr("1286.5913043477") + require.NoError(t, err) + + require.True(sdk.DecEq(t, exp, tokens)) + + require.True(sdk.DecEq(t, + newPool.LooseTokens.Add(newPool.BondedTokens), + pool.LooseTokens.Add(pool.BondedTokens))) +} + +func TestUpdateStatus(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = sdk.NewDec(100) + + validator := NewValidator(addr1, pk1, Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, sdk.NewInt(100)) + require.Equal(t, sdk.Unbonded, validator.Status) + require.Equal(t, int64(100), validator.Tokens.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(100), pool.LooseTokens.RoundInt64()) + + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + require.Equal(t, sdk.Bonded, validator.Status) + require.Equal(t, int64(100), validator.Tokens.RoundInt64()) + require.Equal(t, int64(100), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(0), pool.LooseTokens.RoundInt64()) + + validator, pool = validator.UpdateStatus(pool, sdk.Unbonding) + require.Equal(t, sdk.Unbonding, validator.Status) + require.Equal(t, int64(100), validator.Tokens.RoundInt64()) + require.Equal(t, int64(0), pool.BondedTokens.RoundInt64()) + require.Equal(t, int64(100), pool.LooseTokens.RoundInt64()) +} + +func TestPossibleOverflow(t *testing.T) { + poolTokens := sdk.NewDec(2159) + delShares := sdk.NewDec(391432570689183511).Quo(sdk.NewDec(40113011844664)) + validator := Validator{ + OperatorAddr: addr1, + ConsPubKey: pk1, + Status: sdk.Bonded, + Tokens: poolTokens, + DelegatorShares: delShares, + } + pool := Pool{ + LooseTokens: sdk.NewDec(100), + BondedTokens: poolTokens, + } + tokens := int64(71) + msg := fmt.Sprintf("validator %#v", validator) + newValidator, _, _ := validator.AddTokensFromDel(pool, sdk.NewInt(tokens)) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + require.False(t, newValidator.DelegatorShareExRate().LT(sdk.ZeroDec()), + "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", + msg, newValidator.DelegatorShareExRate()) +} + +func TestHumanReadableString(t *testing.T) { + validator := NewValidator(addr1, pk1, Description{}) + + // NOTE: Being that the validator's keypair is random, we cannot test the + // actual contents of the string. + valStr, err := validator.HumanReadableString() + require.Nil(t, err) + require.NotEmpty(t, valStr) +} + +func TestValidatorMarshalUnmarshalJSON(t *testing.T) { + validator := NewValidator(addr1, pk1, Description{}) + js, err := codec.Cdc.MarshalJSON(validator) + require.NoError(t, err) + require.NotEmpty(t, js) + require.Contains(t, string(js), "\"consensus_pubkey\":\"fcp") + got := &Validator{} + err = codec.Cdc.UnmarshalJSON(js, got) + assert.NoError(t, err) + assert.Equal(t, validator, *got) +} + +func TestValidatorSetInitialCommission(t *testing.T) { + val := NewValidator(addr1, pk1, Description{}) + testCases := []struct { + validator Validator + commission Commission + expectedErr bool + }{ + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), false}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDec(15000000000), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(-1, 1), sdk.ZeroDec(), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.NewDecWithPrec(2, 1), sdk.NewDecWithPrec(1, 1), sdk.ZeroDec()), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.NewDecWithPrec(-1, 1)), true}, + {val, NewCommission(sdk.ZeroDec(), sdk.NewDecWithPrec(1, 1), sdk.NewDecWithPrec(2, 1)), true}, + } + + for i, tc := range testCases { + val, err := tc.validator.SetInitialCommission(tc.commission) + + if tc.expectedErr { + require.Error(t, err, + "expected error for test case #%d with commission: %s", i, tc.commission, + ) + } else { + require.NoError(t, err, + "unexpected error for test case #%d with commission: %s", i, tc.commission, + ) + require.Equal(t, tc.commission, val.Commission, + "invalid validator commission for test case #%d with commission: %s", i, tc.commission, + ) + } + } +} diff --git a/server/config/config_test.go b/server/config/config_test.go new file mode 100644 index 000000000..ca3822c8e --- /dev/null +++ b/server/config/config_test.go @@ -0,0 +1,19 @@ +package config + +import ( + "testing" + + sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + cfg := DefaultConfig() + require.True(t, cfg.MinimumFees().IsZero()) +} + +func TestSetMinimumFees(t *testing.T) { + cfg := DefaultConfig() + cfg.SetMinimumFees(sdk.Coins{sdk.NewCoin("foo", sdk.NewInt(100))}) + require.Equal(t, "100foo", cfg.MinFees) +} diff --git a/server/mock/app.go b/server/mock/app.go index 6068916a9..c0f7a5d52 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -36,7 +36,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { baseApp.SetInitChainer(InitChainer(capKeyMainStore)) // Set a handler Route. - baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore)) + baseApp.Router().AddRoute("kvstore", []*sdk.KVStoreKey{capKeyMainStore}, KVStoreHandler(capKeyMainStore)) // Load latest version. if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil { diff --git a/server/mock/app_test.go b/server/mock/app_test.go new file mode 100644 index 000000000..6e3ae8679 --- /dev/null +++ b/server/mock/app_test.go @@ -0,0 +1,77 @@ +package mock + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/types" +) + +// TestInitApp makes sure we can initialize this thing without an error +func TestInitApp(t *testing.T) { + // set up an app + app, closer, err := SetupApp() + + // closer may need to be run, even when error in later stage + if closer != nil { + defer closer() + } + require.NoError(t, err) + + // initialize it future-way + appState, err := AppGenState(nil, types.GenesisDoc{}, nil) + require.NoError(t, err) + + //TODO test validators in the init chain? + req := abci.RequestInitChain{ + AppStateBytes: appState, + } + app.InitChain(req) + app.Commit() + + // make sure we can query these values + query := abci.RequestQuery{ + Path: "/store/main/key", + Data: []byte("foo"), + } + qres := app.Query(query) + require.Equal(t, uint32(0), qres.Code, qres.Log) + require.Equal(t, []byte("bar"), qres.Value) +} + +// TextDeliverTx ensures we can write a tx +func TestDeliverTx(t *testing.T) { + // set up an app + app, closer, err := SetupApp() + // closer may need to be run, even when error in later stage + if closer != nil { + defer closer() + } + require.NoError(t, err) + + key := "my-special-key" + value := "top-secret-data!!" + tx := NewTx(key, value) + txBytes := tx.GetSignBytes() + + header := abci.Header{ + AppHash: []byte("apphash"), + Height: 1, + } + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + dres := app.DeliverTx(txBytes) + require.Equal(t, uint32(0), dres.Code, dres.Log) + app.EndBlock(abci.RequestEndBlock{}) + cres := app.Commit() + require.NotEmpty(t, cres.Data) + + // make sure we can query these values + query := abci.RequestQuery{ + Path: "/store/main/key", + Data: []byte(key), + } + qres := app.Query(query) + require.Equal(t, uint32(0), qres.Code, qres.Log) + require.Equal(t, []byte(value), qres.Value) +} diff --git a/server/mock/store_test.go b/server/mock/store_test.go new file mode 100644 index 000000000..c81e3d7bc --- /dev/null +++ b/server/mock/store_test.go @@ -0,0 +1,33 @@ +package mock + +import ( + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + + sdk "github.com/irisnet/irishub/types" +) + +func TestStore(t *testing.T) { + db := dbm.NewMemDB() + cms := NewCommitMultiStore() + + key := sdk.NewKVStoreKey("test") + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + err := cms.LoadLatestVersion() + require.Nil(t, err) + + store := cms.GetKVStore(key) + require.NotNil(t, store) + + k := []byte("hello") + v := []byte("world") + require.False(t, store.Has(k)) + store.Set(k, v) + require.True(t, store.Has(k)) + require.Equal(t, v, store.Get(k)) + store.Delete(k) + require.False(t, store.Has(k)) +} diff --git a/server/mock/tx.go b/server/mock/tx.go index 2a3644297..2feaf5a65 100644 --- a/server/mock/tx.go +++ b/server/mock/tx.go @@ -6,7 +6,7 @@ import ( "fmt" sdk "github.com/irisnet/irishub/types" - "github.com/irisnet/irishub/x/auth" + "github.com/irisnet/irishub/modules/auth" ) // An sdk.Tx which is its own sdk.Msg. diff --git a/server/tm_cmds.go b/server/tm_cmds.go index 60a7c7fca..a0537d286 100644 --- a/server/tm_cmds.go +++ b/server/tm_cmds.go @@ -67,13 +67,13 @@ func ShowAddressCmd(ctx *Context) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cfg := ctx.Config privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorFile()) - valAddr := (sdk.ValAddress)(privValidator.Address) + valConsAddr := (sdk.ConsAddress)(privValidator.Address) if viper.GetBool(client.FlagJson) { - return printlnJSON(valAddr) + return printlnJSON(valConsAddr) } - fmt.Println(valAddr.String()) + fmt.Println(valConsAddr.String()) return nil }, } diff --git a/server/util_test.go b/server/util_test.go new file mode 100644 index 000000000..5f15f8568 --- /dev/null +++ b/server/util_test.go @@ -0,0 +1,40 @@ +package server + +import ( + "encoding/json" + "testing" + + "github.com/irisnet/irishub/codec" + "github.com/stretchr/testify/require" +) + +func TestInsertKeyJSON(t *testing.T) { + cdc := codec.New() + + foo := map[string]string{"foo": "foofoo"} + bar := map[string]string{"barInner": "barbar"} + + // create raw messages + bz, err := cdc.MarshalJSON(foo) + require.NoError(t, err) + fooRaw := json.RawMessage(bz) + + bz, err = cdc.MarshalJSON(bar) + require.NoError(t, err) + barRaw := json.RawMessage(bz) + + // make the append + appBz, err := InsertKeyJSON(cdc, fooRaw, "barOuter", barRaw) + require.NoError(t, err) + + // test the append + var appended map[string]json.RawMessage + err = cdc.UnmarshalJSON(appBz, &appended) + require.NoError(t, err) + + var resBar map[string]string + err = cdc.UnmarshalJSON(appended["barOuter"], &resBar) + require.NoError(t, err) + + require.Equal(t, bar, resBar, "appended: %v", appended) +} diff --git a/types/address.go b/types/address.go index 36eb5ac32..6ed2905eb 100644 --- a/types/address.go +++ b/types/address.go @@ -18,17 +18,17 @@ const ( AddrLen = 20 // Bech32PrefixAccAddr defines the Bech32 prefix of an account's address - Bech32PrefixAccAddr = "cosmos" + Bech32PrefixAccAddr = "faa" // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key - Bech32PrefixAccPub = "cosmospub" + Bech32PrefixAccPub = "fap" // Bech32PrefixValAddr defines the Bech32 prefix of a validator's operator address - Bech32PrefixValAddr = "cosmosvaloper" + Bech32PrefixValAddr = "fva" // Bech32PrefixValPub defines the Bech32 prefix of a validator's operator public key - Bech32PrefixValPub = "cosmosvaloperpub" + Bech32PrefixValPub = "fvp" // Bech32PrefixConsAddr defines the Bech32 prefix of a consensus node address - Bech32PrefixConsAddr = "cosmosvalcons" + Bech32PrefixConsAddr = "fca" // Bech32PrefixConsPub defines the Bech32 prefix of a consensus node public key - Bech32PrefixConsPub = "cosmosvalconspub" + Bech32PrefixConsPub = "fcp" ) // ---------------------------------------------------------------------------- diff --git a/types/address_test.go b/types/address_test.go new file mode 100644 index 000000000..3ee09aee7 --- /dev/null +++ b/types/address_test.go @@ -0,0 +1,223 @@ +package types_test + +import ( + "encoding/hex" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/tendermint/tendermint/crypto/ed25519" + + "strings" + + "github.com/irisnet/irishub/types" +) + +var invalidStrs = []string{ + "", + "hello, world!", + "0xAA", + "AAA", + types.Bech32PrefixAccAddr + "AB0C", + types.Bech32PrefixAccPub + "1234", + types.Bech32PrefixValAddr + "5678", + types.Bech32PrefixValPub + "BBAB", + types.Bech32PrefixConsAddr + "FF04", + types.Bech32PrefixConsPub + "6789", +} + +func testMarshal(t *testing.T, original interface{}, res interface{}, marshal func() ([]byte, error), unmarshal func([]byte) error) { + bz, err := marshal() + require.Nil(t, err) + err = unmarshal(bz) + require.Nil(t, err) + require.Equal(t, original, res) +} + +func TestRandBech32PubkeyConsistency(t *testing.T) { + var pub ed25519.PubKeyEd25519 + + for i := 0; i < 1000; i++ { + rand.Read(pub[:]) + + mustBech32AccPub := types.MustBech32ifyAccPub(pub) + bech32AccPub, err := types.Bech32ifyAccPub(pub) + require.Nil(t, err) + require.Equal(t, bech32AccPub, mustBech32AccPub) + + mustBech32ValPub := types.MustBech32ifyValPub(pub) + bech32ValPub, err := types.Bech32ifyValPub(pub) + require.Nil(t, err) + require.Equal(t, bech32ValPub, mustBech32ValPub) + + mustBech32ConsPub := types.MustBech32ifyConsPub(pub) + bech32ConsPub, err := types.Bech32ifyConsPub(pub) + require.Nil(t, err) + require.Equal(t, bech32ConsPub, mustBech32ConsPub) + + mustAccPub := types.MustGetAccPubKeyBech32(bech32AccPub) + accPub, err := types.GetAccPubKeyBech32(bech32AccPub) + require.Nil(t, err) + require.Equal(t, accPub, mustAccPub) + + mustValPub := types.MustGetValPubKeyBech32(bech32ValPub) + valPub, err := types.GetValPubKeyBech32(bech32ValPub) + require.Nil(t, err) + require.Equal(t, valPub, mustValPub) + + mustConsPub := types.MustGetConsPubKeyBech32(bech32ConsPub) + consPub, err := types.GetConsPubKeyBech32(bech32ConsPub) + require.Nil(t, err) + require.Equal(t, consPub, mustConsPub) + + require.Equal(t, valPub, accPub) + require.Equal(t, valPub, consPub) + } +} + +func TestRandBech32AccAddrConsistency(t *testing.T) { + var pub ed25519.PubKeyEd25519 + + for i := 0; i < 1000; i++ { + rand.Read(pub[:]) + + acc := types.AccAddress(pub.Address()) + res := types.AccAddress{} + + testMarshal(t, &acc, &res, acc.MarshalJSON, (&res).UnmarshalJSON) + testMarshal(t, &acc, &res, acc.Marshal, (&res).Unmarshal) + + str := acc.String() + res, err := types.AccAddressFromBech32(str) + require.Nil(t, err) + require.Equal(t, acc, res) + + str = hex.EncodeToString(acc) + res, err = types.AccAddressFromHex(str) + require.Nil(t, err) + require.Equal(t, acc, res) + } + + for _, str := range invalidStrs { + _, err := types.AccAddressFromHex(str) + require.NotNil(t, err) + + _, err = types.AccAddressFromBech32(str) + require.NotNil(t, err) + + err = (*types.AccAddress)(nil).UnmarshalJSON([]byte("\"" + str + "\"")) + require.NotNil(t, err) + } +} + +func TestValAddr(t *testing.T) { + var pub ed25519.PubKeyEd25519 + + for i := 0; i < 20; i++ { + rand.Read(pub[:]) + + acc := types.ValAddress(pub.Address()) + res := types.ValAddress{} + + testMarshal(t, &acc, &res, acc.MarshalJSON, (&res).UnmarshalJSON) + testMarshal(t, &acc, &res, acc.Marshal, (&res).Unmarshal) + + str := acc.String() + res, err := types.ValAddressFromBech32(str) + require.Nil(t, err) + require.Equal(t, acc, res) + + str = hex.EncodeToString(acc) + res, err = types.ValAddressFromHex(str) + require.Nil(t, err) + require.Equal(t, acc, res) + } + + for _, str := range invalidStrs { + _, err := types.ValAddressFromHex(str) + require.NotNil(t, err) + + _, err = types.ValAddressFromBech32(str) + require.NotNil(t, err) + + err = (*types.ValAddress)(nil).UnmarshalJSON([]byte("\"" + str + "\"")) + require.NotNil(t, err) + } +} + +func TestConsAddress(t *testing.T) { + var pub ed25519.PubKeyEd25519 + + for i := 0; i < 20; i++ { + rand.Read(pub[:]) + + acc := types.ConsAddress(pub.Address()) + res := types.ConsAddress{} + + testMarshal(t, &acc, &res, acc.MarshalJSON, (&res).UnmarshalJSON) + testMarshal(t, &acc, &res, acc.Marshal, (&res).Unmarshal) + + str := acc.String() + res, err := types.ConsAddressFromBech32(str) + require.Nil(t, err) + require.Equal(t, acc, res) + + str = hex.EncodeToString(acc) + res, err = types.ConsAddressFromHex(str) + require.Nil(t, err) + require.Equal(t, acc, res) + } + + for _, str := range invalidStrs { + _, err := types.ConsAddressFromHex(str) + require.NotNil(t, err) + + _, err = types.ConsAddressFromBech32(str) + require.NotNil(t, err) + + err = (*types.ConsAddress)(nil).UnmarshalJSON([]byte("\"" + str + "\"")) + require.NotNil(t, err) + } +} + +const letterBytes = "abcdefghijklmnopqrstuvwxyz" + +func RandString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} + +func TestConfiguredPrefix(t *testing.T) { + var pub ed25519.PubKeyEd25519 + for length := 1; length < 10; length++ { + for times := 1; times < 20; times++ { + rand.Read(pub[:]) + // Test if randomly generated prefix of a given length works + prefix := RandString(length) + // Assuming that GetConfig is not sealed. + config := types.GetConfig() + config.SetBech32PrefixForAccount(prefix+"acc", prefix+"pub") + acc := types.AccAddress(pub.Address()) + require.True(t, strings.HasPrefix(acc.String(), prefix+"acc")) + bech32Pub := types.MustBech32ifyAccPub(pub) + require.True(t, strings.HasPrefix(bech32Pub, prefix+"pub")) + + config.SetBech32PrefixForValidator(prefix+"valaddr", prefix+"valpub") + val := types.ValAddress(pub.Address()) + require.True(t, strings.HasPrefix(val.String(), prefix+"valaddr")) + bech32ValPub := types.MustBech32ifyValPub(pub) + require.True(t, strings.HasPrefix(bech32ValPub, prefix+"valpub")) + + config.SetBech32PrefixForConsensusNode(prefix+"consaddr", prefix+"conspub") + cons := types.ConsAddress(pub.Address()) + require.True(t, strings.HasPrefix(cons.String(), prefix+"consaddr")) + bech32ConsPub := types.MustBech32ifyConsPub(pub) + require.True(t, strings.HasPrefix(bech32ConsPub, prefix+"conspub")) + } + + } +} diff --git a/types/codespacer_test.go b/types/codespacer_test.go new file mode 100644 index 000000000..7052aa92a --- /dev/null +++ b/types/codespacer_test.go @@ -0,0 +1,47 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRegisterNext(t *testing.T) { + codespacer := NewCodespacer() + // unregistered, allow + code1 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code1, CodespaceType(2)) + // registered, pick next + code2 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code2, CodespaceType(3)) + // pick next + code3 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code3, CodespaceType(4)) + // skip 1 + code4 := codespacer.RegisterNext(CodespaceType(6)) + require.Equal(t, code4, CodespaceType(6)) + code5 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code5, CodespaceType(5)) + code6 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code6, CodespaceType(7)) + // panic on maximum + defer func() { + r := recover() + require.NotNil(t, r, "Did not panic on maximum codespace") + }() + codespacer.RegisterNext(MaximumCodespace - 1) + codespacer.RegisterNext(MaximumCodespace - 1) +} + +func TestRegisterOrPanic(t *testing.T) { + codespacer := NewCodespacer() + // unregistered, allow + code1 := codespacer.RegisterNext(CodespaceType(2)) + require.Equal(t, code1, CodespaceType(2)) + // panic on duplicate + defer func() { + r := recover() + require.NotNil(t, r, "Did not panic on duplicate codespace") + }() + codespacer.RegisterOrPanic(CodespaceType(2)) +} diff --git a/types/coin_test.go b/types/coin_test.go new file mode 100644 index 000000000..6dcaf0b13 --- /dev/null +++ b/types/coin_test.go @@ -0,0 +1,535 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsPositiveCoin(t *testing.T) { + cases := []struct { + inputOne Coin + expected bool + }{ + {NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", 0), false}, + {NewInt64Coin("a", -1), false}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsPositive() + require.Equal(t, tc.expected, res, "%s positivity is incorrect, tc #%d", tc.inputOne.String(), tcIndex) + } +} + +func TestIsNotNegativeCoin(t *testing.T) { + cases := []struct { + inputOne Coin + expected bool + }{ + {NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", 0), true}, + {NewInt64Coin("a", -1), false}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsNotNegative() + require.Equal(t, tc.expected, res, "%s not-negativity is incorrect, tc #%d", tc.inputOne.String(), tcIndex) + } +} + +func TestSameDenomAsCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected bool + }{ + {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + {NewInt64Coin("iris-atto", 1), NewInt64Coin("iris-atto", 10), true}, + {NewInt64Coin("iris-atto", -11), NewInt64Coin("iris-atto", 10), true}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.SameDenomAs(tc.inputTwo) + require.Equal(t, tc.expected, res, "coin denominations didn't match, tc #%d", tcIndex) + } +} + +func TestIsGTECoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected bool + }{ + {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", 2), NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", -1), NewInt64Coin("A", 5), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsGTE(tc.inputTwo) + require.Equal(t, tc.expected, res, "coin GTE relation is incorrect, tc #%d", tcIndex) + } +} + +func TestIsLTCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected bool + }{ + {NewInt64Coin("A", 1), NewInt64Coin("A", 1), false}, + {NewInt64Coin("A", 2), NewInt64Coin("A", 1), false}, + {NewInt64Coin("A", -1), NewInt64Coin("A", 5), true}, + {NewInt64Coin("a", 0), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("a", 2), true}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsLT(tc.inputTwo) + require.Equal(t, tc.expected, res, "coin LT relation is incorrect, tc #%d", tcIndex) + } +} + +func TestIsEqualCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected bool + }{ + {NewInt64Coin("A", 1), NewInt64Coin("A", 1), true}, + {NewInt64Coin("A", 1), NewInt64Coin("a", 1), false}, + {NewInt64Coin("a", 1), NewInt64Coin("b", 1), false}, + {NewInt64Coin("iris-atto", 1), NewInt64Coin("iris-atto", 10), false}, + {NewInt64Coin("iris-atto", -11), NewInt64Coin("iris-atto", 10), false}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.IsEqual(tc.inputTwo) + require.Equal(t, tc.expected, res, "coin equality relation is incorrect, tc #%d", tcIndex) + } +} + +func TestPlusCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected Coin + }{ + {NewInt64Coin("A", 1), NewInt64Coin("A", 1), NewInt64Coin("A", 2)}, + {NewInt64Coin("A", 1), NewInt64Coin("B", 1), NewInt64Coin("A", 1)}, + {NewInt64Coin("asdf", -4), NewInt64Coin("asdf", 5), NewInt64Coin("asdf", 1)}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Plus(tc.inputTwo) + require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) + } + + tc := struct { + inputOne Coin + inputTwo Coin + expected int64 + }{NewInt64Coin("asdf", -1), NewInt64Coin("asdf", 1), 0} + res := tc.inputOne.Plus(tc.inputTwo) + require.Equal(t, tc.expected, res.Amount.Int64()) +} + +func TestMinusCoin(t *testing.T) { + cases := []struct { + inputOne Coin + inputTwo Coin + expected Coin + }{ + + {NewInt64Coin("A", 1), NewInt64Coin("B", 1), NewInt64Coin("A", 1)}, + {NewInt64Coin("asdf", -4), NewInt64Coin("asdf", 5), NewInt64Coin("asdf", -9)}, + {NewInt64Coin("asdf", 10), NewInt64Coin("asdf", 1), NewInt64Coin("asdf", 9)}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Minus(tc.inputTwo) + require.Equal(t, tc.expected, res, "difference of coins is incorrect, tc #%d", tcIndex) + } + + tc := struct { + inputOne Coin + inputTwo Coin + expected int64 + }{NewInt64Coin("A", 1), NewInt64Coin("A", 1), 0} + res := tc.inputOne.Minus(tc.inputTwo) + require.Equal(t, tc.expected, res.Amount.Int64()) + +} + +func TestIsZeroCoins(t *testing.T) { + cases := []struct { + inputOne Coins + expected bool + }{ + {Coins{}, true}, + {Coins{NewInt64Coin("A", 0)}, true}, + {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 0)}, true}, + {Coins{NewInt64Coin("A", 1)}, false}, + {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, false}, + } + + for _, tc := range cases { + res := tc.inputOne.IsZero() + require.Equal(t, tc.expected, res) + } +} + +func TestEqualCoins(t *testing.T) { + cases := []struct { + inputOne Coins + inputTwo Coins + expected bool + }{ + {Coins{}, Coins{}, true}, + {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 0)}, true}, + {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, true}, + {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("B", 0)}, false}, + {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 1)}, false}, + {Coins{NewInt64Coin("A", 0)}, Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, false}, + // TODO: is it expected behaviour? shouldn't we sort the coins before comparing them? + {Coins{NewInt64Coin("A", 0), NewInt64Coin("B", 1)}, Coins{NewInt64Coin("B", 1), NewInt64Coin("A", 0)}, false}, + } + + for tcnum, tc := range cases { + res := tc.inputOne.IsEqual(tc.inputTwo) + require.Equal(t, tc.expected, res, "Equality is differed from expected. tc #%d, expected %b, actual %b.", tcnum, tc.expected, res) + } +} + +func TestCoins(t *testing.T) { + + //Define the coins to be used in tests + good := Coins{ + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, + {"TREE", NewInt(1)}, + } + neg := good.Negative() + sum := good.Plus(neg) + empty := Coins{ + {"GOLD", NewInt(0)}, + } + null := Coins{} + badSort1 := Coins{ + {"TREE", NewInt(1)}, + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, + } + // both are after the first one, but the second and third are in the wrong order + badSort2 := Coins{ + {"GAS", NewInt(1)}, + {"TREE", NewInt(1)}, + {"MINERAL", NewInt(1)}, + } + badAmt := Coins{ + {"GAS", NewInt(1)}, + {"TREE", NewInt(0)}, + {"MINERAL", NewInt(1)}, + } + dup := Coins{ + {"GAS", NewInt(1)}, + {"GAS", NewInt(1)}, + {"MINERAL", NewInt(1)}, + } + + assert.True(t, good.IsValid(), "Coins are valid") + assert.True(t, good.IsPositive(), "Expected coins to be positive: %v", good) + assert.False(t, null.IsPositive(), "Expected coins to not be positive: %v", null) + assert.True(t, good.IsAllGTE(empty), "Expected %v to be >= %v", good, empty) + assert.False(t, good.IsAllLT(empty), "Expected %v to be < %v", good, empty) + assert.True(t, empty.IsAllLT(good), "Expected %v to be < %v", empty, good) + assert.False(t, neg.IsPositive(), "Expected neg coins to not be positive: %v", neg) + assert.Zero(t, len(sum), "Expected 0 coins") + assert.False(t, badSort1.IsValid(), "Coins are not sorted") + assert.False(t, badSort2.IsValid(), "Coins are not sorted") + assert.False(t, badAmt.IsValid(), "Coins cannot include 0 amounts") + assert.False(t, dup.IsValid(), "Duplicate coin") + +} + +func TestCoinsGT(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.False(t, Coins{}.IsAllGT(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllGT(Coins{{"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGT(Coins{{"B", two}})) +} + +func TestCoinsGTE(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.True(t, Coins{}.IsAllGTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllGTE(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllGTE(Coins{{"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllGTE(Coins{{"B", two}})) +} + +func TestCoinsLT(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.False(t, Coins{}.IsAllLT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllLT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"B", two}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLT(Coins{{"A", one}, {"B", two}})) + assert.True(t, Coins{}.IsAllLT(Coins{{"A", one}})) +} + +func TestCoinsLTE(t *testing.T) { + one := NewInt(1) + two := NewInt(2) + + assert.True(t, Coins{}.IsAllLTE(Coins{})) + assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{})) + assert.True(t, Coins{{"A", one}}.IsAllLTE(Coins{{"A", one}})) + assert.False(t, Coins{{"A", one}}.IsAllLTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", one}})) + assert.False(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"B", two}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", one}})) + assert.True(t, Coins{{"A", one}, {"B", one}}.IsAllLTE(Coins{{"A", one}, {"B", two}})) + assert.True(t, Coins{}.IsAllLTE(Coins{{"A", one}})) +} + +func TestPlusCoins(t *testing.T) { + one := NewInt(1) + zero := NewInt(0) + negone := NewInt(-1) + two := NewInt(2) + + cases := []struct { + inputOne Coins + inputTwo Coins + expected Coins + }{ + {Coins{{"A", one}, {"B", one}}, Coins{{"A", one}, {"B", one}}, Coins{{"A", two}, {"B", two}}}, + {Coins{{"A", zero}, {"B", one}}, Coins{{"A", zero}, {"B", zero}}, Coins{{"B", one}}}, + {Coins{{"A", zero}, {"B", zero}}, Coins{{"A", zero}, {"B", zero}}, Coins(nil)}, + {Coins{{"A", one}, {"B", zero}}, Coins{{"A", negone}, {"B", zero}}, Coins(nil)}, + {Coins{{"A", negone}, {"B", zero}}, Coins{{"A", zero}, {"B", zero}}, Coins{{"A", negone}}}, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Plus(tc.inputTwo) + assert.True(t, res.IsValid()) + require.Equal(t, tc.expected, res, "sum of coins is incorrect, tc #%d", tcIndex) + } +} + +//Test the parsing of Coin and Coins +func TestParse(t *testing.T) { + one := NewInt(1) + + cases := []struct { + input string + valid bool // if false, we expect an error on parse + expected Coins // if valid is true, make sure this is returned + }{ + {"", true, nil}, + {"1foo", true, Coins{{"foo", one}}}, + {"10bar", true, Coins{{"bar", NewInt(10)}}}, + {"99bar,1foo", true, Coins{{"bar", NewInt(99)}, {"foo", one}}}, + {"98 bar , 1 foo ", true, Coins{{"bar", NewInt(98)}, {"foo", one}}}, + {" 55\t \t bling\n", true, Coins{{"bling", NewInt(55)}}}, + {"2foo, 97 bar", true, Coins{{"bar", NewInt(97)}, {"foo", NewInt(2)}}}, + {"5 mycoin,", false, nil}, // no empty coins in a list + {"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name + {"11me coin, 12you coin", false, nil}, // no spaces in coin names + {"1.2btc", false, nil}, // amount must be integer + {"5foo-bar", false, nil}, // once more, only letters in coin name + } + + for tcIndex, tc := range cases { + res, err := ParseCoins(tc.input) + if !tc.valid { + require.NotNil(t, err, "%s: %#v. tc #%d", tc.input, res, tcIndex) + } else if assert.Nil(t, err, "%s: %+v", tc.input, err) { + require.Equal(t, tc.expected, res, "coin parsing was incorrect, tc #%d", tcIndex) + } + } + +} + +func TestSortCoins(t *testing.T) { + + good := Coins{ + NewInt64Coin("GAS", 1), + NewInt64Coin("MINERAL", 1), + NewInt64Coin("TREE", 1), + } + empty := Coins{ + NewInt64Coin("GOLD", 0), + } + badSort1 := Coins{ + NewInt64Coin("TREE", 1), + NewInt64Coin("GAS", 1), + NewInt64Coin("MINERAL", 1), + } + badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order + NewInt64Coin("GAS", 1), + NewInt64Coin("TREE", 1), + NewInt64Coin("MINERAL", 1), + } + badAmt := Coins{ + NewInt64Coin("GAS", 1), + NewInt64Coin("TREE", 0), + NewInt64Coin("MINERAL", 1), + } + dup := Coins{ + NewInt64Coin("GAS", 1), + NewInt64Coin("GAS", 1), + NewInt64Coin("MINERAL", 1), + } + + cases := []struct { + coins Coins + before, after bool // valid before/after sort + }{ + {good, true, true}, + {empty, false, false}, + {badSort1, false, true}, + {badSort2, false, true}, + {badAmt, false, false}, + {dup, false, false}, + } + + for tcIndex, tc := range cases { + require.Equal(t, tc.before, tc.coins.IsValid(), "coin validity is incorrect before sorting, tc #%d", tcIndex) + tc.coins.Sort() + require.Equal(t, tc.after, tc.coins.IsValid(), "coin validity is incorrect after sorting, tc #%d", tcIndex) + } +} + +func TestAmountOf(t *testing.T) { + + case0 := Coins{} + case1 := Coins{ + NewInt64Coin("", 0), + } + case2 := Coins{ + NewInt64Coin(" ", 0), + } + case3 := Coins{ + NewInt64Coin("GOLD", 0), + } + case4 := Coins{ + NewInt64Coin("GAS", 1), + NewInt64Coin("MINERAL", 1), + NewInt64Coin("TREE", 1), + } + case5 := Coins{ + NewInt64Coin("MINERAL", 1), + NewInt64Coin("TREE", 1), + } + case6 := Coins{ + NewInt64Coin("", 6), + } + case7 := Coins{ + NewInt64Coin(" ", 7), + } + case8 := Coins{ + NewInt64Coin("GAS", 8), + } + + cases := []struct { + coins Coins + amountOf int64 + amountOfSpace int64 + amountOfGAS int64 + amountOfMINERAL int64 + amountOfTREE int64 + }{ + {case0, 0, 0, 0, 0, 0}, + {case1, 0, 0, 0, 0, 0}, + {case2, 0, 0, 0, 0, 0}, + {case3, 0, 0, 0, 0, 0}, + {case4, 0, 0, 1, 1, 1}, + {case5, 0, 0, 0, 1, 1}, + {case6, 6, 0, 0, 0, 0}, + {case7, 0, 7, 0, 0, 0}, + {case8, 0, 0, 8, 0, 0}, + } + + for _, tc := range cases { + assert.Equal(t, NewInt(tc.amountOf), tc.coins.AmountOf("")) + assert.Equal(t, NewInt(tc.amountOfSpace), tc.coins.AmountOf(" ")) + assert.Equal(t, NewInt(tc.amountOfGAS), tc.coins.AmountOf("GAS")) + assert.Equal(t, NewInt(tc.amountOfMINERAL), tc.coins.AmountOf("MINERAL")) + assert.Equal(t, NewInt(tc.amountOfTREE), tc.coins.AmountOf("TREE")) + } +} + +func BenchmarkCoinsAdditionIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{{1, 1}, {5, 5}, {5, 20}, {1, 1000}, {2, 1000}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} + +func BenchmarkCoinsAdditionNoIntersect(b *testing.B) { + benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) { + return func(b *testing.B) { + coinsA := Coins(make([]Coin, numCoinsA)) + coinsB := Coins(make([]Coin, numCoinsB)) + for i := 0; i < numCoinsA; i++ { + coinsA[i] = NewCoin("COINZ_"+string(numCoinsB+i), NewInt(int64(i))) + } + for i := 0; i < numCoinsB; i++ { + coinsB[i] = NewCoin("COINZ_"+string(i), NewInt(int64(i))) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + coinsA.Plus(coinsB) + } + } + } + + benchmarkSizes := [][]int{{1, 1}, {5, 5}, {5, 20}, {1, 1000}, {2, 1000}, {1000, 2}} + for i := 0; i < len(benchmarkSizes); i++ { + sizeA := benchmarkSizes[i][0] + sizeB := benchmarkSizes[i][1] + b.Run(fmt.Sprintf("sizes: A_%d, B_%d", sizeA, sizeB), benchmarkingFunc(sizeA, sizeB)) + } +} diff --git a/types/context_test.go b/types/context_test.go new file mode 100644 index 000000000..ea2d43a73 --- /dev/null +++ b/types/context_test.go @@ -0,0 +1,185 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/irisnet/irishub/store" + "github.com/irisnet/irishub/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +type MockLogger struct { + logs *[]string +} + +func NewMockLogger() MockLogger { + logs := make([]string, 0) + return MockLogger{ + &logs, + } +} + +func (l MockLogger) Debug(msg string, kvs ...interface{}) { + *l.logs = append(*l.logs, msg) +} + +func (l MockLogger) Info(msg string, kvs ...interface{}) { + *l.logs = append(*l.logs, msg) +} + +func (l MockLogger) Error(msg string, kvs ...interface{}) { + *l.logs = append(*l.logs, msg) +} + +func (l MockLogger) With(kvs ...interface{}) log.Logger { + panic("not implemented") +} + +func TestContextGetOpShouldNeverPanic(t *testing.T) { + var ms types.MultiStore + ctx := types.NewContext(ms, abci.Header{}, false, log.NewNopLogger()) + indices := []int64{ + -10, 1, 0, 10, 20, + } + + for _, index := range indices { + _, _ = ctx.GetOp(index) + } +} + +func defaultContext(key types.StoreKey) types.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, types.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := types.NewContext(cms, abci.Header{}, false, log.NewNopLogger()) + return ctx +} + +func TestCacheContext(t *testing.T) { + key := types.NewKVStoreKey(t.Name()) + k1 := []byte("hello") + v1 := []byte("world") + k2 := []byte("key") + v2 := []byte("value") + + ctx := defaultContext(key) + store := ctx.KVStore(key) + store.Set(k1, v1) + require.Equal(t, v1, store.Get(k1)) + require.Nil(t, store.Get(k2)) + + cctx, write := ctx.CacheContext() + cstore := cctx.KVStore(key) + require.Equal(t, v1, cstore.Get(k1)) + require.Nil(t, cstore.Get(k2)) + + cstore.Set(k2, v2) + require.Equal(t, v2, cstore.Get(k2)) + require.Nil(t, store.Get(k2)) + + write() + + require.Equal(t, v2, store.Get(k2)) +} + +func TestLogContext(t *testing.T) { + key := types.NewKVStoreKey(t.Name()) + ctx := defaultContext(key) + logger := NewMockLogger() + ctx = ctx.WithLogger(logger) + ctx.Logger().Debug("debug") + ctx.Logger().Info("info") + ctx.Logger().Error("error") + require.Equal(t, *logger.logs, []string{"debug", "info", "error"}) +} + +type dummy int64 + +func (d dummy) Clone() interface{} { + return d +} + +// Testing saving/loading primitive values to/from the context +func TestContextWithPrimitive(t *testing.T) { + ctx := types.NewContext(nil, abci.Header{}, false, log.NewNopLogger()) + + clonerkey := "cloner" + stringkey := "string" + int32key := "int32" + uint32key := "uint32" + uint64key := "uint64" + + keys := []string{clonerkey, stringkey, int32key, uint32key, uint64key} + + for _, key := range keys { + require.Nil(t, ctx.Value(key)) + } + + clonerval := dummy(1) + stringval := "string" + int32val := int32(1) + uint32val := uint32(2) + uint64val := uint64(3) + + ctx = ctx. + WithCloner(clonerkey, clonerval). + WithString(stringkey, stringval). + WithInt32(int32key, int32val). + WithUint32(uint32key, uint32val). + WithUint64(uint64key, uint64val) + + require.Equal(t, clonerval, ctx.Value(clonerkey)) + require.Equal(t, stringval, ctx.Value(stringkey)) + require.Equal(t, int32val, ctx.Value(int32key)) + require.Equal(t, uint32val, ctx.Value(uint32key)) + require.Equal(t, uint64val, ctx.Value(uint64key)) +} + +// Testing saving/loading sdk type values to/from the context +func TestContextWithCustom(t *testing.T) { + var ctx types.Context + require.True(t, ctx.IsZero()) + + require.Panics(t, func() { ctx.BlockHeader() }) + require.Panics(t, func() { ctx.BlockHeight() }) + require.Panics(t, func() { ctx.ChainID() }) + require.Panics(t, func() { ctx.TxBytes() }) + require.Panics(t, func() { ctx.Logger() }) + require.Panics(t, func() { ctx.VoteInfos() }) + require.Panics(t, func() { ctx.GasMeter() }) + + header := abci.Header{} + height := int64(1) + chainid := "chainid" + ischeck := true + txbytes := []byte("txbytes") + logger := NewMockLogger() + voteinfos := []abci.VoteInfo{{}} + meter := types.NewGasMeter(10000) + minFees := types.Coins{types.NewInt64Coin("feeCoin", 1)} + + ctx = types.NewContext(nil, header, ischeck, logger) + require.Equal(t, header, ctx.BlockHeader()) + + ctx = ctx. + WithBlockHeight(height). + WithChainID(chainid). + WithTxBytes(txbytes). + WithVoteInfos(voteinfos). + WithGasMeter(meter). + WithMinimumFees(minFees) + require.Equal(t, height, ctx.BlockHeight()) + require.Equal(t, chainid, ctx.ChainID()) + require.Equal(t, ischeck, ctx.IsCheckTx()) + require.Equal(t, txbytes, ctx.TxBytes()) + require.Equal(t, logger, ctx.Logger()) + require.Equal(t, voteinfos, ctx.VoteInfos()) + require.Equal(t, meter, ctx.GasMeter()) + require.Equal(t, minFees, types.Coins{types.NewInt64Coin("feeCoin", 1)}) +} diff --git a/types/decimal_test.go b/types/decimal_test.go new file mode 100644 index 000000000..34fe1c9b1 --- /dev/null +++ b/types/decimal_test.go @@ -0,0 +1,366 @@ +package types + +import ( + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/irisnet/irishub/codec" + "github.com/stretchr/testify/require" +) + +// create a decimal from a decimal string (ex. "1234.5678") +func mustNewDecFromStr(t *testing.T, str string) (d Dec) { + d, err := NewDecFromStr(str) + require.NoError(t, err) + return d +} + +//_______________________________________ + +func TestPrecisionMultiplier(t *testing.T) { + res := precisionMultiplier(5) + exp := big.NewInt(100000) + require.Equal(t, 0, res.Cmp(exp), "equality was incorrect, res %v, exp %v", res, exp) +} + +func TestNewDecFromStr(t *testing.T) { + largeBigInt, success := new(big.Int).SetString("3144605511029693144278234343371835", 10) + require.True(t, success) + tests := []struct { + decimalStr string + expErr bool + exp Dec + }{ + {"", true, Dec{}}, + {"0.-75", true, Dec{}}, + {"0", false, NewDec(0)}, + {"1", false, NewDec(1)}, + {"1.1", false, NewDecWithPrec(11, 1)}, + {"0.75", false, NewDecWithPrec(75, 2)}, + {"0.8", false, NewDecWithPrec(8, 1)}, + {"0.11111", false, NewDecWithPrec(11111, 5)}, + {"314460551102969.3144278234343371835", true, NewDec(3141203149163817869)}, + {"314460551102969314427823434337.1835718092488231350", + true, NewDecFromBigIntWithPrec(largeBigInt, 4)}, + {"314460551102969314427823434337.1835", + false, NewDecFromBigIntWithPrec(largeBigInt, 4)}, + {".", true, Dec{}}, + {".0", true, NewDec(0)}, + {"1.", true, NewDec(1)}, + {"foobar", true, Dec{}}, + {"0.foobar", true, Dec{}}, + {"0.foobar.", true, Dec{}}, + } + + for tcIndex, tc := range tests { + res, err := NewDecFromStr(tc.decimalStr) + if tc.expErr { + require.NotNil(t, err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + require.Nil(t, err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + require.True(t, res.Equal(tc.exp), "equality was incorrect, res %v, exp %v, tc %v", res, tc.exp, tcIndex) + } + + // negative tc + res, err = NewDecFromStr("-" + tc.decimalStr) + if tc.expErr { + require.NotNil(t, err, "error expected, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + } else { + require.Nil(t, err, "unexpected error, decimalStr %v, tc %v", tc.decimalStr, tcIndex) + exp := tc.exp.Mul(NewDec(-1)) + require.True(t, res.Equal(exp), "equality was incorrect, res %v, exp %v, tc %v", res, exp, tcIndex) + } + } +} + +func TestEqualities(t *testing.T) { + tests := []struct { + d1, d2 Dec + gt, lt, eq bool + }{ + {NewDec(0), NewDec(0), false, false, true}, + {NewDecWithPrec(0, 2), NewDecWithPrec(0, 4), false, false, true}, + {NewDecWithPrec(100, 0), NewDecWithPrec(100, 0), false, false, true}, + {NewDecWithPrec(-100, 0), NewDecWithPrec(-100, 0), false, false, true}, + {NewDecWithPrec(-1, 1), NewDecWithPrec(-1, 1), false, false, true}, + {NewDecWithPrec(3333, 3), NewDecWithPrec(3333, 3), false, false, true}, + + {NewDecWithPrec(0, 0), NewDecWithPrec(3333, 3), false, true, false}, + {NewDecWithPrec(0, 0), NewDecWithPrec(100, 0), false, true, false}, + {NewDecWithPrec(-1, 0), NewDecWithPrec(3333, 3), false, true, false}, + {NewDecWithPrec(-1, 0), NewDecWithPrec(100, 0), false, true, false}, + {NewDecWithPrec(1111, 3), NewDecWithPrec(100, 0), false, true, false}, + {NewDecWithPrec(1111, 3), NewDecWithPrec(3333, 3), false, true, false}, + {NewDecWithPrec(-3333, 3), NewDecWithPrec(-1111, 3), false, true, false}, + + {NewDecWithPrec(3333, 3), NewDecWithPrec(0, 0), true, false, false}, + {NewDecWithPrec(100, 0), NewDecWithPrec(0, 0), true, false, false}, + {NewDecWithPrec(3333, 3), NewDecWithPrec(-1, 0), true, false, false}, + {NewDecWithPrec(100, 0), NewDecWithPrec(-1, 0), true, false, false}, + {NewDecWithPrec(100, 0), NewDecWithPrec(1111, 3), true, false, false}, + {NewDecWithPrec(3333, 3), NewDecWithPrec(1111, 3), true, false, false}, + {NewDecWithPrec(-1111, 3), NewDecWithPrec(-3333, 3), true, false, false}, + } + + for tcIndex, tc := range tests { + require.Equal(t, tc.gt, tc.d1.GT(tc.d2), "GT result is incorrect, tc %d", tcIndex) + require.Equal(t, tc.lt, tc.d1.LT(tc.d2), "LT result is incorrect, tc %d", tcIndex) + require.Equal(t, tc.eq, tc.d1.Equal(tc.d2), "equality result is incorrect, tc %d", tcIndex) + } + +} + +func TestDecsEqual(t *testing.T) { + tests := []struct { + d1s, d2s []Dec + eq bool + }{ + {[]Dec{NewDec(0)}, []Dec{NewDec(0)}, true}, + {[]Dec{NewDec(0)}, []Dec{NewDec(1)}, false}, + {[]Dec{NewDec(0)}, []Dec{}, false}, + {[]Dec{NewDec(0), NewDec(1)}, []Dec{NewDec(0), NewDec(1)}, true}, + {[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(1), NewDec(0)}, true}, + {[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(0), NewDec(1)}, false}, + {[]Dec{NewDec(1), NewDec(0)}, []Dec{NewDec(1)}, false}, + {[]Dec{NewDec(1), NewDec(2)}, []Dec{NewDec(2), NewDec(4)}, false}, + {[]Dec{NewDec(3), NewDec(18)}, []Dec{NewDec(1), NewDec(6)}, false}, + } + + for tcIndex, tc := range tests { + require.Equal(t, tc.eq, DecsEqual(tc.d1s, tc.d2s), "equality of decional arrays is incorrect, tc %d", tcIndex) + require.Equal(t, tc.eq, DecsEqual(tc.d2s, tc.d1s), "equality of decional arrays is incorrect (converse), tc %d", tcIndex) + } +} + +func TestArithmetic(t *testing.T) { + tests := []struct { + d1, d2 Dec + expMul, expDiv, expAdd, expSub Dec + }{ + // d1 d2 MUL DIV ADD SUB + {NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0), NewDec(0)}, + {NewDec(1), NewDec(0), NewDec(0), NewDec(0), NewDec(1), NewDec(1)}, + {NewDec(0), NewDec(1), NewDec(0), NewDec(0), NewDec(1), NewDec(-1)}, + {NewDec(0), NewDec(-1), NewDec(0), NewDec(0), NewDec(-1), NewDec(1)}, + {NewDec(-1), NewDec(0), NewDec(0), NewDec(0), NewDec(-1), NewDec(-1)}, + + {NewDec(1), NewDec(1), NewDec(1), NewDec(1), NewDec(2), NewDec(0)}, + {NewDec(-1), NewDec(-1), NewDec(1), NewDec(1), NewDec(-2), NewDec(0)}, + {NewDec(1), NewDec(-1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(2)}, + {NewDec(-1), NewDec(1), NewDec(-1), NewDec(-1), NewDec(0), NewDec(-2)}, + + {NewDec(3), NewDec(7), NewDec(21), NewDecWithPrec(4285714286, 10), NewDec(10), NewDec(-4)}, + {NewDec(2), NewDec(4), NewDec(8), NewDecWithPrec(5, 1), NewDec(6), NewDec(-2)}, + {NewDec(100), NewDec(100), NewDec(10000), NewDec(1), NewDec(200), NewDec(0)}, + + {NewDecWithPrec(15, 1), NewDecWithPrec(15, 1), NewDecWithPrec(225, 2), + NewDec(1), NewDec(3), NewDec(0)}, + {NewDecWithPrec(3333, 4), NewDecWithPrec(333, 4), NewDecWithPrec(1109889, 8), + NewDecWithPrec(10009009009, 9), NewDecWithPrec(3666, 4), NewDecWithPrec(3, 1)}, + } + + for tcIndex, tc := range tests { + resAdd := tc.d1.Add(tc.d2) + resSub := tc.d1.Sub(tc.d2) + resMul := tc.d1.Mul(tc.d2) + require.True(t, tc.expAdd.Equal(resAdd), "exp %v, res %v, tc %d", tc.expAdd, resAdd, tcIndex) + require.True(t, tc.expSub.Equal(resSub), "exp %v, res %v, tc %d", tc.expSub, resSub, tcIndex) + require.True(t, tc.expMul.Equal(resMul), "exp %v, res %v, tc %d", tc.expMul, resMul, tcIndex) + + if tc.d2.IsZero() { // panic for divide by zero + require.Panics(t, func() { tc.d1.Quo(tc.d2) }) + } else { + resDiv := tc.d1.Quo(tc.d2) + require.True(t, tc.expDiv.Equal(resDiv), "exp %v, res %v, tc %d", tc.expDiv.String(), resDiv.String(), tcIndex) + } + } +} + +func TestBankerRoundChop(t *testing.T) { + tests := []struct { + d1 Dec + exp int64 + }{ + {mustNewDecFromStr(t, "0.25"), 0}, + {mustNewDecFromStr(t, "0"), 0}, + {mustNewDecFromStr(t, "1"), 1}, + {mustNewDecFromStr(t, "0.75"), 1}, + {mustNewDecFromStr(t, "0.5"), 0}, + {mustNewDecFromStr(t, "7.5"), 8}, + {mustNewDecFromStr(t, "1.5"), 2}, + {mustNewDecFromStr(t, "2.5"), 2}, + {mustNewDecFromStr(t, "0.545"), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even + {mustNewDecFromStr(t, "1.545"), 2}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().RoundInt64() + require.Equal(t, -1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.RoundInt64() + require.Equal(t, tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +func TestTruncate(t *testing.T) { + tests := []struct { + d1 Dec + exp int64 + }{ + {mustNewDecFromStr(t, "0"), 0}, + {mustNewDecFromStr(t, "0.25"), 0}, + {mustNewDecFromStr(t, "0.75"), 0}, + {mustNewDecFromStr(t, "1"), 1}, + {mustNewDecFromStr(t, "1.5"), 1}, + {mustNewDecFromStr(t, "7.5"), 7}, + {mustNewDecFromStr(t, "7.6"), 7}, + {mustNewDecFromStr(t, "7.4"), 7}, + {mustNewDecFromStr(t, "100.1"), 100}, + {mustNewDecFromStr(t, "1000.1"), 1000}, + } + + for tcIndex, tc := range tests { + resNeg := tc.d1.Neg().TruncateInt64() + require.Equal(t, -1*tc.exp, resNeg, "negative tc %d", tcIndex) + + resPos := tc.d1.TruncateInt64() + require.Equal(t, tc.exp, resPos, "positive tc %d", tcIndex) + } +} + +var cdc = codec.New() + +func TestDecMarshalJSON(t *testing.T) { + decimal := func(i int64) Dec { + d := NewDec(0) + d.Int = new(big.Int).SetInt64(i) + return d + } + tests := []struct { + name string + d Dec + want string + wantErr bool // if wantErr = false, will also attempt unmarshaling + }{ + {"zero", decimal(0), "\"0.0000000000\"", false}, + {"one", decimal(1), "\"0.0000000001\"", false}, + {"ten", decimal(10), "\"0.0000000010\"", false}, + {"12340", decimal(12340), "\"0.0000012340\"", false}, + {"zeroInt", NewDec(0), "\"0.0000000000\"", false}, + {"oneInt", NewDec(1), "\"1.0000000000\"", false}, + {"tenInt", NewDec(10), "\"10.0000000000\"", false}, + {"12340Int", NewDec(12340), "\"12340.0000000000\"", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.d.MarshalJSON() + if (err != nil) != tt.wantErr { + t.Errorf("Dec.MarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + assert.Equal(t, tt.want, string(got), "incorrect marshalled value") + unmarshalledDec := NewDec(0) + unmarshalledDec.UnmarshalJSON(got) + assert.Equal(t, tt.d, unmarshalledDec, "incorrect unmarshalled value") + } + }) + } +} + +func TestZeroDeserializationJSON(t *testing.T) { + d := Dec{new(big.Int)} + err := cdc.UnmarshalJSON([]byte(`"0"`), &d) + require.Nil(t, err) + err = cdc.UnmarshalJSON([]byte(`"{}"`), &d) + require.NotNil(t, err) +} + +func TestSerializationText(t *testing.T) { + d := mustNewDecFromStr(t, "0.333") + + bz, err := d.MarshalText() + require.NoError(t, err) + + d2 := Dec{new(big.Int)} + err = d2.UnmarshalText(bz) + require.NoError(t, err) + require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) +} + +func TestSerializationGocodecJSON(t *testing.T) { + d := mustNewDecFromStr(t, "0.333") + + bz, err := cdc.MarshalJSON(d) + require.NoError(t, err) + + d2 := Dec{new(big.Int)} + err = cdc.UnmarshalJSON(bz, &d2) + require.NoError(t, err) + require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) +} + +func TestSerializationGocodecBinary(t *testing.T) { + d := mustNewDecFromStr(t, "0.333") + + bz, err := cdc.MarshalBinaryLengthPrefixed(d) + require.NoError(t, err) + + var d2 Dec + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &d2) + require.NoError(t, err) + require.True(t, d.Equal(d2), "original: %v, unmarshalled: %v", d, d2) +} + +type testDEmbedStruct struct { + Field1 string `json:"f1"` + Field2 int `json:"f2"` + Field3 Dec `json:"f3"` +} + +// TODO make work for UnmarshalJSON +func TestEmbeddedStructSerializationGocodec(t *testing.T) { + obj := testDEmbedStruct{"foo", 10, NewDecWithPrec(1, 3)} + bz, err := cdc.MarshalBinaryLengthPrefixed(obj) + require.Nil(t, err) + + var obj2 testDEmbedStruct + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &obj2) + require.Nil(t, err) + + require.Equal(t, obj.Field1, obj2.Field1) + require.Equal(t, obj.Field2, obj2.Field2) + require.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2) +} + +func TestStringOverflow(t *testing.T) { + // two random 64 bit primes + dec1, err := NewDecFromStr("51643150036226787134389711697696177267") + require.NoError(t, err) + dec2, err := NewDecFromStr("-31798496660535729618459429845579852627") + require.NoError(t, err) + dec3 := dec1.Add(dec2) + require.Equal(t, + "19844653375691057515930281852116324640.0000000000", + dec3.String(), + ) +} + +func TestDecMulInt(t *testing.T) { + tests := []struct { + sdkDec Dec + sdkInt Int + want Dec + }{ + {NewDec(10), NewInt(2), NewDec(20)}, + {NewDec(1000000), NewInt(100), NewDec(100000000)}, + {NewDecWithPrec(1, 1), NewInt(10), NewDec(1)}, + {NewDecWithPrec(1, 5), NewInt(20), NewDecWithPrec(2, 4)}, + } + for i, tc := range tests { + got := tc.sdkDec.MulInt(tc.sdkInt) + require.Equal(t, tc.want, got, "Incorrect result on test case %d", i) + } +} diff --git a/types/errors_test.go b/types/errors_test.go new file mode 100644 index 000000000..1d63e0990 --- /dev/null +++ b/types/errors_test.go @@ -0,0 +1,93 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +var codeTypes = []CodeType{ + CodeInternal, + CodeTxDecode, + CodeInvalidSequence, + CodeUnauthorized, + CodeInsufficientFunds, + CodeUnknownRequest, + CodeInvalidAddress, + CodeInvalidPubKey, + CodeUnknownAddress, + CodeInsufficientCoins, + CodeInvalidCoins, + CodeOutOfGas, + CodeMemoTooLarge, +} + +type errFn func(msg string) Error + +var errFns = []errFn{ + ErrInternal, + ErrTxDecode, + ErrInvalidSequence, + ErrUnauthorized, + ErrInsufficientFunds, + ErrUnknownRequest, + ErrInvalidAddress, + ErrInvalidPubKey, + ErrUnknownAddress, + ErrInsufficientCoins, + ErrInvalidCoins, + ErrOutOfGas, + ErrMemoTooLarge, +} + +func TestCodeType(t *testing.T) { + require.True(t, ABCICodeOK.IsOK()) + + for tcnum, c := range codeTypes { + msg := CodeToDefaultMsg(c) + require.NotEqual(t, unknownCodeMsg(c), msg, "Code expected to be known. tc #%d, code %d, msg %s", tcnum, c, msg) + } + + msg := CodeToDefaultMsg(CodeOK) + require.Equal(t, unknownCodeMsg(CodeOK), msg) +} + +func TestErrFn(t *testing.T) { + for i, errFn := range errFns { + err := errFn("") + codeType := codeTypes[i] + require.Equal(t, err.Code(), codeType, "Err function expected to return proper code. tc #%d", i) + require.Equal(t, err.Codespace(), CodespaceRoot, "Err function expected to return proper codespace. tc #%d", i) + require.Equal(t, err.Result().Code, ToABCICode(CodespaceRoot, codeType), "Err function expected to return proper ABCICode. tc #%d") + require.Equal(t, err.QueryResult().Code, uint32(err.ABCICode()), "Err function expected to return proper ABCICode from QueryResult. tc #%d") + require.Equal(t, err.QueryResult().Log, err.ABCILog(), "Err function expected to return proper ABCILog from QueryResult. tc #%d") + } + + require.Equal(t, ABCICodeOK, ToABCICode(CodespaceRoot, CodeOK)) +} + +func TestAppendMsgToErr(t *testing.T) { + for i, errFn := range errFns { + err := errFn("") + errMsg := err.Stacktrace().Error() + abciLog := err.ABCILog() + + // plain msg error + msg := AppendMsgToErr("something unexpected happened", errMsg) + require.Equal(t, fmt.Sprintf("something unexpected happened; %s", + errMsg), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + + // ABCI Log msg error + msg = AppendMsgToErr("something unexpected happened", abciLog) + msgIdx := mustGetMsgIndex(abciLog) + require.Equal(t, fmt.Sprintf("%s%s; %s}", + abciLog[:msgIdx], + "something unexpected happened", + abciLog[msgIdx:len(abciLog)-1]), + msg, + fmt.Sprintf("Should have formatted the error message of ABCI Log. tc #%d", i)) + } +} diff --git a/types/gas_test.go b/types/gas_test.go new file mode 100644 index 000000000..cd2384d12 --- /dev/null +++ b/types/gas_test.go @@ -0,0 +1,36 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGasMeter(t *testing.T) { + cases := []struct { + limit Gas + usage []Gas + }{ + {10, []Gas{1, 2, 3, 4}}, + {1000, []Gas{40, 30, 20, 10, 900}}, + {100000, []Gas{99999, 1}}, + {100000000, []Gas{50000000, 40000000, 10000000}}, + {65535, []Gas{32768, 32767}}, + {65536, []Gas{32768, 32767, 1}}, + } + + for tcnum, tc := range cases { + meter := NewGasMeter(tc.limit) + used := int64(0) + + for unum, usage := range tc.usage { + used += usage + require.NotPanics(t, func() { meter.ConsumeGas(usage, "") }, "Not exceeded limit but panicked. tc #%d, usage #%d", tcnum, unum) + require.Equal(t, used, meter.GasConsumed(), "Gas consumption not match. tc #%d, usage #%d", tcnum, unum) + } + + require.Panics(t, func() { meter.ConsumeGas(1, "") }, "Exceeded but not panicked. tc #%d", tcnum) + break + + } +} diff --git a/types/int_test.go b/types/int_test.go new file mode 100644 index 000000000..cd357c4f7 --- /dev/null +++ b/types/int_test.go @@ -0,0 +1,592 @@ +package types + +import ( + "math" + "math/big" + "math/rand" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFromInt64(t *testing.T) { + for n := 0; n < 20; n++ { + r := rand.Int63() + require.Equal(t, r, NewInt(r).Int64()) + } +} + +func TestIntPanic(t *testing.T) { + // Max Int = 2^255-1 = 5.789e+76 + // Min Int = -(2^255-1) = -5.789e+76 + require.NotPanics(t, func() { NewIntWithDecimal(1, 76) }) + i1 := NewIntWithDecimal(1, 76) + require.NotPanics(t, func() { NewIntWithDecimal(2, 76) }) + i2 := NewIntWithDecimal(2, 76) + require.NotPanics(t, func() { NewIntWithDecimal(3, 76) }) + i3 := NewIntWithDecimal(3, 76) + + require.Panics(t, func() { NewIntWithDecimal(6, 76) }) + require.Panics(t, func() { NewIntWithDecimal(9, 80) }) + + // Overflow check + require.NotPanics(t, func() { i1.Add(i1) }) + require.NotPanics(t, func() { i2.Add(i2) }) + require.Panics(t, func() { i3.Add(i3) }) + + require.NotPanics(t, func() { i1.Sub(i1.Neg()) }) + require.NotPanics(t, func() { i2.Sub(i2.Neg()) }) + require.Panics(t, func() { i3.Sub(i3.Neg()) }) + + require.Panics(t, func() { i1.Mul(i1) }) + require.Panics(t, func() { i2.Mul(i2) }) + require.Panics(t, func() { i3.Mul(i3) }) + + require.Panics(t, func() { i1.Neg().Mul(i1.Neg()) }) + require.Panics(t, func() { i2.Neg().Mul(i2.Neg()) }) + require.Panics(t, func() { i3.Neg().Mul(i3.Neg()) }) + + // Underflow check + i3n := i3.Neg() + require.NotPanics(t, func() { i3n.Sub(i1) }) + require.NotPanics(t, func() { i3n.Sub(i2) }) + require.Panics(t, func() { i3n.Sub(i3) }) + + require.NotPanics(t, func() { i3n.Add(i1.Neg()) }) + require.NotPanics(t, func() { i3n.Add(i2.Neg()) }) + require.Panics(t, func() { i3n.Add(i3.Neg()) }) + + require.Panics(t, func() { i1.Mul(i1.Neg()) }) + require.Panics(t, func() { i2.Mul(i2.Neg()) }) + require.Panics(t, func() { i3.Mul(i3.Neg()) }) + + // Bound check + intmax := NewIntFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(255), nil), big.NewInt(1))) + intmin := intmax.Neg() + require.NotPanics(t, func() { intmax.Add(ZeroInt()) }) + require.NotPanics(t, func() { intmin.Sub(ZeroInt()) }) + require.Panics(t, func() { intmax.Add(OneInt()) }) + require.Panics(t, func() { intmin.Sub(OneInt()) }) + + // Division-by-zero check + require.Panics(t, func() { i1.Div(NewInt(0)) }) +} + +func TestUintPanic(t *testing.T) { + // Max Uint = 1.15e+77 + // Min Uint = 0 + require.NotPanics(t, func() { NewUintWithDecimal(5, 76) }) + i1 := NewUintWithDecimal(5, 76) + require.NotPanics(t, func() { NewUintWithDecimal(10, 76) }) + i2 := NewUintWithDecimal(10, 76) + require.NotPanics(t, func() { NewUintWithDecimal(11, 76) }) + i3 := NewUintWithDecimal(11, 76) + + require.Panics(t, func() { NewUintWithDecimal(12, 76) }) + require.Panics(t, func() { NewUintWithDecimal(1, 80) }) + + // Overflow check + require.NotPanics(t, func() { i1.Add(i1) }) + require.Panics(t, func() { i2.Add(i2) }) + require.Panics(t, func() { i3.Add(i3) }) + + require.Panics(t, func() { i1.Mul(i1) }) + require.Panics(t, func() { i2.Mul(i2) }) + require.Panics(t, func() { i3.Mul(i3) }) + + // Underflow check + require.NotPanics(t, func() { i2.Sub(i1) }) + require.NotPanics(t, func() { i2.Sub(i2) }) + require.Panics(t, func() { i2.Sub(i3) }) + + // Bound check + uintmax := NewUintFromBigInt(new(big.Int).Sub(new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1))) + uintmin := NewUint(0) + require.NotPanics(t, func() { uintmax.Add(ZeroUint()) }) + require.NotPanics(t, func() { uintmin.Sub(ZeroUint()) }) + require.Panics(t, func() { uintmax.Add(OneUint()) }) + require.Panics(t, func() { uintmin.Sub(OneUint()) }) + + // Division-by-zero check + require.Panics(t, func() { i1.Div(uintmin) }) +} + +// Tests below uses randomness +// Since we are using *big.Int as underlying value +// and (U/)Int is immutable value(see TestImmutability(U/)Int) +// it is safe to use randomness in the tests +func TestIdentInt(t *testing.T) { + for d := 0; d < 1000; d++ { + n := rand.Int63() + i := NewInt(n) + + ifromstr, ok := NewIntFromString(strconv.FormatInt(n, 10)) + require.True(t, ok) + + cases := []int64{ + i.Int64(), + i.BigInt().Int64(), + ifromstr.Int64(), + NewIntFromBigInt(big.NewInt(n)).Int64(), + NewIntWithDecimal(n, 0).Int64(), + } + + for tcnum, tc := range cases { + require.Equal(t, n, tc, "Int is modified during conversion. tc #%d", tcnum) + } + } +} + +func minint(i1, i2 int64) int64 { + if i1 < i2 { + return i1 + } + return i2 +} + +func TestArithInt(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := int64(rand.Int31()) + i1 := NewInt(n1) + n2 := int64(rand.Int31()) + i2 := NewInt(n2) + + cases := []struct { + ires Int + nres int64 + }{ + {i1.Add(i2), n1 + n2}, + {i1.Sub(i2), n1 - n2}, + {i1.Mul(i2), n1 * n2}, + {i1.Div(i2), n1 / n2}, + {i1.AddRaw(n2), n1 + n2}, + {i1.SubRaw(n2), n1 - n2}, + {i1.MulRaw(n2), n1 * n2}, + {i1.DivRaw(n2), n1 / n2}, + {MinInt(i1, i2), minint(n1, n2)}, + {i1.Neg(), -n1}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ires.Int64(), "Int arithmetic operation does not match with int64 operation. tc #%d", tcnum) + } + } + +} + +func TestCompInt(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := int64(rand.Int31()) + i1 := NewInt(n1) + n2 := int64(rand.Int31()) + i2 := NewInt(n2) + + cases := []struct { + ires bool + nres bool + }{ + {i1.Equal(i2), n1 == n2}, + {i1.GT(i2), n1 > n2}, + {i1.LT(i2), n1 < n2}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ires, "Int comparison operation does not match with int64 operation. tc #%d", tcnum) + } + } +} + +func TestIdentUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n := rand.Uint64() + i := NewUint(n) + + ifromstr, ok := NewUintFromString(strconv.FormatUint(n, 10)) + require.True(t, ok) + + cases := []uint64{ + i.Uint64(), + i.BigInt().Uint64(), + ifromstr.Uint64(), + NewUintFromBigInt(new(big.Int).SetUint64(n)).Uint64(), + NewUintWithDecimal(n, 0).Uint64(), + } + + for tcnum, tc := range cases { + require.Equal(t, n, tc, "Uint is modified during conversion. tc #%d", tcnum) + } + } +} + +func minuint(i1, i2 uint64) uint64 { + if i1 < i2 { + return i1 + } + return i2 +} + +func TestArithUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := uint64(rand.Uint32()) + i1 := NewUint(n1) + n2 := uint64(rand.Uint32()) + i2 := NewUint(n2) + + cases := []struct { + ires Uint + nres uint64 + }{ + {i1.Add(i2), n1 + n2}, + {i1.Mul(i2), n1 * n2}, + {i1.Div(i2), n1 / n2}, + {i1.AddRaw(n2), n1 + n2}, + {i1.MulRaw(n2), n1 * n2}, + {i1.DivRaw(n2), n1 / n2}, + {MinUint(i1, i2), minuint(n1, n2)}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ires.Uint64(), "Uint arithmetic operation does not match with uint64 operation. tc #%d", tcnum) + } + + if n2 > n1 { + continue + } + + subs := []struct { + ires Uint + nres uint64 + }{ + {i1.Sub(i2), n1 - n2}, + {i1.SubRaw(n2), n1 - n2}, + } + + for tcnum, tc := range subs { + require.Equal(t, tc.nres, tc.ires.Uint64(), "Uint subtraction does not match with uint64 operation. tc #%d", tcnum) + } + } +} + +func TestCompUint(t *testing.T) { + for d := 0; d < 1000; d++ { + n1 := rand.Uint64() + i1 := NewUint(n1) + n2 := rand.Uint64() + i2 := NewUint(n2) + + cases := []struct { + ires bool + nres bool + }{ + {i1.Equal(i2), n1 == n2}, + {i1.GT(i2), n1 > n2}, + {i1.LT(i2), n1 < n2}, + } + + for tcnum, tc := range cases { + require.Equal(t, tc.nres, tc.ires, "Uint comparison operation does not match with uint64 operation. tc #%d", tcnum) + } + } +} + +func randint() Int { + return NewInt(rand.Int63()) +} + +func TestImmutabilityAllInt(t *testing.T) { + ops := []func(*Int){ + func(i *Int) { _ = i.Add(randint()) }, + func(i *Int) { _ = i.Sub(randint()) }, + func(i *Int) { _ = i.Mul(randint()) }, + func(i *Int) { _ = i.Div(randint()) }, + func(i *Int) { _ = i.AddRaw(rand.Int63()) }, + func(i *Int) { _ = i.SubRaw(rand.Int63()) }, + func(i *Int) { _ = i.MulRaw(rand.Int63()) }, + func(i *Int) { _ = i.DivRaw(rand.Int63()) }, + func(i *Int) { _ = i.Neg() }, + func(i *Int) { _ = i.IsZero() }, + func(i *Int) { _ = i.Sign() }, + func(i *Int) { _ = i.Equal(randint()) }, + func(i *Int) { _ = i.GT(randint()) }, + func(i *Int) { _ = i.LT(randint()) }, + func(i *Int) { _ = i.String() }, + } + + for i := 0; i < 1000; i++ { + n := rand.Int63() + ni := NewInt(n) + + for opnum, op := range ops { + op(&ni) + + require.Equal(t, n, ni.Int64(), "Int is modified by operation. tc #%d", opnum) + require.Equal(t, NewInt(n), ni, "Int is modified by operation. tc #%d", opnum) + } + } +} + +type intop func(Int, *big.Int) (Int, *big.Int) + +func intarith(uifn func(Int, Int) Int, bifn func(*big.Int, *big.Int, *big.Int) *big.Int) intop { + return func(ui Int, bi *big.Int) (Int, *big.Int) { + r := rand.Int63() + br := new(big.Int).SetInt64(r) + return uifn(ui, NewInt(r)), bifn(new(big.Int), bi, br) + } +} + +func intarithraw(uifn func(Int, int64) Int, bifn func(*big.Int, *big.Int, *big.Int) *big.Int) intop { + return func(ui Int, bi *big.Int) (Int, *big.Int) { + r := rand.Int63() + br := new(big.Int).SetInt64(r) + return uifn(ui, r), bifn(new(big.Int), bi, br) + } +} + +func TestImmutabilityArithInt(t *testing.T) { + size := 500 + + ops := []intop{ + intarith(Int.Add, (*big.Int).Add), + intarith(Int.Sub, (*big.Int).Sub), + intarith(Int.Mul, (*big.Int).Mul), + intarith(Int.Div, (*big.Int).Div), + intarithraw(Int.AddRaw, (*big.Int).Add), + intarithraw(Int.SubRaw, (*big.Int).Sub), + intarithraw(Int.MulRaw, (*big.Int).Mul), + intarithraw(Int.DivRaw, (*big.Int).Div), + } + + for i := 0; i < 100; i++ { + uis := make([]Int, size) + bis := make([]*big.Int, size) + + n := rand.Int63() + ui := NewInt(n) + bi := new(big.Int).SetInt64(n) + + for j := 0; j < size; j++ { + op := ops[rand.Intn(len(ops))] + uis[j], bis[j] = op(ui, bi) + } + + for j := 0; j < size; j++ { + require.Equal(t, 0, bis[j].Cmp(uis[j].BigInt()), "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + require.Equal(t, NewIntFromBigInt(bis[j]), uis[j], "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + require.True(t, uis[j].i != bis[j], "Pointer addresses are equal. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + } + } +} +func TestImmutabilityAllUint(t *testing.T) { + ops := []func(*Uint){ + func(i *Uint) { _ = i.Add(NewUint(rand.Uint64())) }, + func(i *Uint) { _ = i.Sub(NewUint(rand.Uint64() % i.Uint64())) }, + func(i *Uint) { _ = i.Mul(randuint()) }, + func(i *Uint) { _ = i.Div(randuint()) }, + func(i *Uint) { _ = i.AddRaw(rand.Uint64()) }, + func(i *Uint) { _ = i.SubRaw(rand.Uint64() % i.Uint64()) }, + func(i *Uint) { _ = i.MulRaw(rand.Uint64()) }, + func(i *Uint) { _ = i.DivRaw(rand.Uint64()) }, + func(i *Uint) { _ = i.IsZero() }, + func(i *Uint) { _ = i.Sign() }, + func(i *Uint) { _ = i.Equal(randuint()) }, + func(i *Uint) { _ = i.GT(randuint()) }, + func(i *Uint) { _ = i.LT(randuint()) }, + func(i *Uint) { _ = i.String() }, + } + + for i := 0; i < 1000; i++ { + n := rand.Uint64() + ni := NewUint(n) + + for opnum, op := range ops { + op(&ni) + + require.Equal(t, n, ni.Uint64(), "Uint is modified by operation. #%d", opnum) + require.Equal(t, NewUint(n), ni, "Uint is modified by operation. #%d", opnum) + } + } +} + +type uintop func(Uint, *big.Int) (Uint, *big.Int) + +func uintarith(uifn func(Uint, Uint) Uint, bifn func(*big.Int, *big.Int, *big.Int) *big.Int, sub bool) uintop { + return func(ui Uint, bi *big.Int) (Uint, *big.Int) { + r := rand.Uint64() + if sub && ui.IsUint64() { + if ui.IsZero() { + return ui, bi + } + r = r % ui.Uint64() + } + ur := NewUint(r) + br := new(big.Int).SetUint64(r) + return uifn(ui, ur), bifn(new(big.Int), bi, br) + } +} + +func uintarithraw(uifn func(Uint, uint64) Uint, bifn func(*big.Int, *big.Int, *big.Int) *big.Int, sub bool) uintop { + return func(ui Uint, bi *big.Int) (Uint, *big.Int) { + r := rand.Uint64() + if sub && ui.IsUint64() { + if ui.IsZero() { + return ui, bi + } + r = r % ui.Uint64() + } + br := new(big.Int).SetUint64(r) + mui := ui.ModRaw(math.MaxUint64) + mbi := new(big.Int).Mod(bi, new(big.Int).SetUint64(math.MaxUint64)) + return uifn(mui, r), bifn(new(big.Int), mbi, br) + } +} + +func TestImmutabilityArithUint(t *testing.T) { + size := 500 + + ops := []uintop{ + uintarith(Uint.Add, (*big.Int).Add, false), + uintarith(Uint.Sub, (*big.Int).Sub, true), + uintarith(Uint.Mul, (*big.Int).Mul, false), + uintarith(Uint.Div, (*big.Int).Div, false), + uintarithraw(Uint.AddRaw, (*big.Int).Add, false), + uintarithraw(Uint.SubRaw, (*big.Int).Sub, true), + uintarithraw(Uint.MulRaw, (*big.Int).Mul, false), + uintarithraw(Uint.DivRaw, (*big.Int).Div, false), + } + + for i := 0; i < 100; i++ { + uis := make([]Uint, size) + bis := make([]*big.Int, size) + + n := rand.Uint64() + ui := NewUint(n) + bi := new(big.Int).SetUint64(n) + + for j := 0; j < size; j++ { + op := ops[rand.Intn(len(ops))] + uis[j], bis[j] = op(ui, bi) + } + + for j := 0; j < size; j++ { + require.Equal(t, 0, bis[j].Cmp(uis[j].BigInt()), "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + require.Equal(t, NewUintFromBigInt(bis[j]), uis[j], "Int is different from *big.Int. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + require.True(t, uis[j].i != bis[j], "Pointer addresses are equal. tc #%d, Int %s, *big.Int %s", j, uis[j].String(), bis[j].String()) + } + } +} + +func randuint() Uint { + return NewUint(rand.Uint64()) +} + +func TestEncodingRandom(t *testing.T) { + for i := 0; i < 1000; i++ { + n := rand.Int63() + ni := NewInt(n) + var ri Int + + str, err := ni.MarshalAmino() + require.Nil(t, err) + err = (&ri).UnmarshalAmino(str) + require.Nil(t, err) + + require.Equal(t, ni, ri, "MarshalAmino * UnmarshalAmino is not identity. tc #%d, Expected %s, Actual %s", i, ni.String(), ri.String()) + require.True(t, ni.i != ri.i, "Pointer addresses are equal. tc #%d", i) + + bz, err := ni.MarshalJSON() + require.Nil(t, err) + err = (&ri).UnmarshalJSON(bz) + require.Nil(t, err) + + require.Equal(t, ni, ri, "MarshalJSON * UnmarshalJSON is not identity. tc #%d, Expected %s, Actual %s", i, ni.String(), ri.String()) + require.True(t, ni.i != ri.i, "Pointer addresses are equal. tc #%d", i) + } + + for i := 0; i < 1000; i++ { + n := rand.Uint64() + ni := NewUint(n) + var ri Uint + + str, err := ni.MarshalAmino() + require.Nil(t, err) + err = (&ri).UnmarshalAmino(str) + require.Nil(t, err) + + require.Equal(t, ni, ri, "MarshalAmino * UnmarshalAmino is not identity. tc #%d, Expected %s, Actual %s", i, ni.String(), ri.String()) + require.True(t, ni.i != ri.i, "Pointer addresses are equal. tc #%d", i) + + bz, err := ni.MarshalJSON() + require.Nil(t, err) + err = (&ri).UnmarshalJSON(bz) + require.Nil(t, err) + + require.Equal(t, ni, ri, "MarshalJSON * UnmarshalJSON is not identity. tc #%d, Expected %s, Actual %s", i, ni.String(), ri.String()) + require.True(t, ni.i != ri.i, "Pointer addresses are equal. tc #%d", i) + } +} + +func TestEncodingTableInt(t *testing.T) { + var i Int + + cases := []struct { + i Int + bz []byte + str string + }{ + {NewInt(0), []byte("\"0\""), "0"}, + {NewInt(100), []byte("\"100\""), "100"}, + {NewInt(51842), []byte("\"51842\""), "51842"}, + {NewInt(19513368), []byte("\"19513368\""), "19513368"}, + {NewInt(999999999999), []byte("\"999999999999\""), "999999999999"}, + } + + for tcnum, tc := range cases { + bz, err := tc.i.MarshalJSON() + require.Nil(t, err, "Error marshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.bz, bz, "Marshaled value is different from expected. tc #%d", tcnum) + err = (&i).UnmarshalJSON(bz) + require.Nil(t, err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.i, i, "Unmarshaled value is different from expected. tc #%d", tcnum) + + str, err := tc.i.MarshalAmino() + require.Nil(t, err, "Error marshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.str, str, "Marshaled value is different from expected. tc #%d", tcnum) + err = (&i).UnmarshalAmino(str) + require.Nil(t, err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.i, i, "Unmarshaled value is different from expected. tc #%d", tcnum) + } +} + +func TestEncodingTableUint(t *testing.T) { + var i Uint + + cases := []struct { + i Uint + bz []byte + str string + }{ + {NewUint(0), []byte("\"0\""), "0"}, + {NewUint(100), []byte("\"100\""), "100"}, + {NewUint(51842), []byte("\"51842\""), "51842"}, + {NewUint(19513368), []byte("\"19513368\""), "19513368"}, + {NewUint(999999999999), []byte("\"999999999999\""), "999999999999"}, + } + + for tcnum, tc := range cases { + bz, err := tc.i.MarshalJSON() + require.Nil(t, err, "Error marshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.bz, bz, "Marshaled value is different from expected. tc #%d", tcnum) + err = (&i).UnmarshalJSON(bz) + require.Nil(t, err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.i, i, "Unmarshaled value is different from expected. tc #%d", tcnum) + + str, err := tc.i.MarshalAmino() + require.Nil(t, err, "Error marshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.str, str, "Marshaled value is different from expected. tc #%d", tcnum) + err = (&i).UnmarshalAmino(str) + require.Nil(t, err, "Error unmarshaling Int. tc #%d, err %s", tcnum, err) + require.Equal(t, tc.i, i, "Unmarshaled value is different from expected. tc #%d", tcnum) + } +} diff --git a/types/rational_test.go b/types/rational_test.go index ce20013a1..489dfc273 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -5,7 +5,6 @@ import ( "math/rand" "testing" - codec "github.com/irisnet/irishub/codec" "github.com/stretchr/testify/require" ) @@ -69,7 +68,7 @@ func TestNewFromDecimal(t *testing.T) { } } -func TestEqualities(t *testing.T) { +func TestEqualitiesRational(t *testing.T) { tests := []struct { r1, r2 Rat gt, lt, eq bool @@ -108,7 +107,7 @@ func TestEqualities(t *testing.T) { } -func TestArithmetic(t *testing.T) { +func TestArithmeticRational(t *testing.T) { tests := []struct { r1, r2 Rat resMul, resDiv, resAdd, resSub Rat @@ -217,8 +216,6 @@ func TestToLeftPadded(t *testing.T) { } } -var cdc = codec.New() //var jsonCdc JSONCodec // TODO codec.Codec - func TestZeroSerializationJSON(t *testing.T) { r := NewRat(0, 1) err := cdc.UnmarshalJSON([]byte(`"0/1"`), &r) @@ -231,7 +228,7 @@ func TestZeroSerializationJSON(t *testing.T) { require.NotNil(t, err) } -func TestSerializationText(t *testing.T) { +func TestSerializationTextRational(t *testing.T) { r := NewRat(1, 3) bz, err := r.MarshalText() @@ -260,7 +257,7 @@ func TestSerializationGoWireBinary(t *testing.T) { require.NoError(t, err) var r2 Rat - err = cdc.UnMarshalBinaryLengthPrefixed(bz, &r2) + err = cdc.UnmarshalBinaryLengthPrefixed(bz, &r2) require.NoError(t, err) require.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } @@ -308,7 +305,7 @@ func TestRatsEqual(t *testing.T) { } -func TestStringOverflow(t *testing.T) { +func TestStringOverflowRational(t *testing.T) { // two random 64 bit primes rat1 := NewRat(5164315003622678713, 4389711697696177267) rat2 := NewRat(-3179849666053572961, 8459429845579852627) diff --git a/types/result_test.go b/types/result_test.go new file mode 100644 index 000000000..e0305932c --- /dev/null +++ b/types/result_test.go @@ -0,0 +1,18 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestResult(t *testing.T) { + var res Result + require.True(t, res.IsOK()) + + res.Data = []byte("data") + require.True(t, res.IsOK()) + + res.Code = ABCICodeType(1) + require.False(t, res.IsOK()) +} diff --git a/types/store.go b/types/store.go index f4b774959..1f7cabe09 100644 --- a/types/store.go +++ b/types/store.go @@ -197,15 +197,15 @@ func DiffKVStores(a KVStore, b KVStore, prefixesToSkip [][]byte) (kvA cmn.KVPair kvB = cmn.KVPair{Key: iterB.Key(), Value: iterB.Value()} iterB.Next() } + if !bytes.Equal(kvA.Key, kvB.Key) { + return kvA, kvB, count, false + } compareValue := true for _, prefix := range prefixesToSkip { if bytes.Equal(kvA.Key[:len(prefix)], prefix) { compareValue = false } } - if !bytes.Equal(kvA.Key, kvB.Key) { - return kvA, kvB, count, false - } if compareValue && !bytes.Equal(kvA.Value, kvB.Value) { return kvA, kvB, count, false } diff --git a/types/store_test.go b/types/store_test.go new file mode 100644 index 000000000..b5e36c487 --- /dev/null +++ b/types/store_test.go @@ -0,0 +1,39 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPrefixEndBytes(t *testing.T) { + var testCases = []struct { + prefix []byte + expected []byte + }{ + {[]byte{byte(55), byte(255), byte(255), byte(0)}, []byte{byte(55), byte(255), byte(255), byte(1)}}, + {[]byte{byte(55), byte(255), byte(255), byte(15)}, []byte{byte(55), byte(255), byte(255), byte(16)}}, + {[]byte{byte(55), byte(200), byte(255)}, []byte{byte(55), byte(201)}}, + {[]byte{byte(55), byte(255), byte(255)}, []byte{byte(56)}}, + {[]byte{byte(255), byte(255), byte(255)}, nil}, + {[]byte{byte(255)}, nil}, + {nil, nil}, + } + + for _, test := range testCases { + end := PrefixEndBytes(test.prefix) + require.Equal(t, test.expected, end) + } +} + +func TestCommitID(t *testing.T) { + var empty CommitID + require.True(t, empty.IsZero()) + + var nonempty CommitID + nonempty = CommitID{ + Version: 1, + Hash: []byte("testhash"), + } + require.False(t, nonempty.IsZero()) +} diff --git a/types/tags_test.go b/types/tags_test.go new file mode 100644 index 000000000..77bb4041c --- /dev/null +++ b/types/tags_test.go @@ -0,0 +1,35 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendTags(t *testing.T) { + a := NewTags("a", []byte("1")) + b := NewTags("b", []byte("2")) + c := a.AppendTags(b) + require.Equal(t, c, Tags{MakeTag("a", []byte("1")), MakeTag("b", []byte("2"))}) + require.Equal(t, c, Tags{MakeTag("a", []byte("1"))}.AppendTag("b", []byte("2"))) +} + +func TestEmptyTags(t *testing.T) { + a := EmptyTags() + require.Equal(t, a, Tags{}) +} + +func TestNewTags(t *testing.T) { + b := NewTags("a", []byte("1")) + require.Equal(t, b, Tags{MakeTag("a", []byte("1"))}) + + require.Panics(t, func() { NewTags("a", []byte("1"), "b") }) + require.Panics(t, func() { NewTags("a", 1) }) + require.Panics(t, func() { NewTags(1, 1) }) + require.Panics(t, func() { NewTags(true, false) }) +} + +func TestKVPairTags(t *testing.T) { + a := NewTags("a", []byte("1")) + require.Equal(t, a, Tags(a.ToKVPairs())) +} diff --git a/types/utils_test.go b/types/utils_test.go new file mode 100644 index 000000000..dbdd08c0a --- /dev/null +++ b/types/utils_test.go @@ -0,0 +1,66 @@ +package types + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSortJSON(t *testing.T) { + cases := []struct { + unsortedJSON string + want string + wantErr bool + }{ + // simple case + {unsortedJSON: `{"cosmos":"foo", "atom":"bar", "tendermint":"foobar"}`, + want: `{"atom":"bar","cosmos":"foo","tendermint":"foobar"}`, wantErr: false}, + // failing case (invalid JSON): + {unsortedJSON: `"cosmos":"foo",,,, "atom":"bar", "tendermint":"foobar"}`, + want: "", + wantErr: true}, + // genesis.json + {unsortedJSON: `{"consensus_params":{"block_size_params":{"max_bytes":22020096,"max_txs":100000,"max_gas":-1},"tx_size_params":{"max_bytes":10240,"max_gas":-1},"block_gossip_params":{"block_part_size_bytes":65536},"evidence_params":{"max_age":100000}},"validators":[{"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="},"power":100,"name":""}],"app_hash":"","genesis_time":"2018-05-11T15:52:25.424795506Z","chain_id":"test-chain-Q6VeoW","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"denom":"Token","amount":1000},{"denom":"steak","amount":50}]}],"stake":{"pool":{"total_supply":50,"bonded_shares":"0","unbonded_shares":"0","bonded_pool":0,"unbonded_pool":0,"inflation_last_time":0,"inflation":"7/100"},"params":{"inflation_rate_change":"13/100","inflation_max":"1/5","inflation_min":"7/100","goal_bonded":"67/100","max_validators":100,"bond_denom":"steak"},"candidates":null,"bonds":null}}}`, + want: `{"app_hash":"","app_state":{"accounts":[{"address":"718C9C23F98C9642569742ADDD9F9AB9743FBD5D","coins":[{"amount":1000,"denom":"Token"},{"amount":50,"denom":"steak"}]}],"stake":{"bonds":null,"candidates":null,"params":{"bond_denom":"steak","goal_bonded":"67/100","inflation_max":"1/5","inflation_min":"7/100","inflation_rate_change":"13/100","max_validators":100},"pool":{"bonded_pool":0,"bonded_shares":"0","inflation":"7/100","inflation_last_time":0,"total_supply":50,"unbonded_pool":0,"unbonded_shares":"0"}}},"chain_id":"test-chain-Q6VeoW","consensus_params":{"block_gossip_params":{"block_part_size_bytes":65536},"block_size_params":{"max_bytes":22020096,"max_gas":-1,"max_txs":100000},"evidence_params":{"max_age":100000},"tx_size_params":{"max_bytes":10240,"max_gas":-1}},"genesis_time":"2018-05-11T15:52:25.424795506Z","validators":[{"name":"","power":100,"pub_key":{"type":"AC26791624DE60","value":"c7UMMAbjFuc5GhGPy0E5q5tefy12p9Tq0imXqdrKXwo="}}]}`, + wantErr: false}, + // from the TXSpec: + {unsortedJSON: `{"chain_id":"test-chain-1","sequence":1,"fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"alt_bytes":null}`, + want: `{"alt_bytes":null,"chain_id":"test-chain-1","fee_bytes":{"amount":[{"amount":5,"denom":"photon"}],"gas":10000},"msg_bytes":{"inputs":[{"address":"696E707574","coins":[{"amount":10,"denom":"atom"}]}],"outputs":[{"address":"6F7574707574","coins":[{"amount":10,"denom":"atom"}]}]},"sequence":1}`, + wantErr: false}, + } + + for tcIndex, tc := range cases { + got, err := SortJSON([]byte(tc.unsortedJSON)) + if tc.wantErr { + require.NotNil(t, err, "tc #%d", tcIndex) + require.Panics(t, func() { MustSortJSON([]byte(tc.unsortedJSON)) }) + } else { + require.Nil(t, err, "tc #%d, err=%s", tcIndex, err) + require.NotPanics(t, func() { MustSortJSON([]byte(tc.unsortedJSON)) }) + require.Equal(t, got, MustSortJSON([]byte(tc.unsortedJSON))) + } + + require.Equal(t, string(got), tc.want) + } +} + +func TestTimeFormatAndParse(t *testing.T) { + cases := []struct { + RFC3339NanoStr string + SDKSortableTimeStr string + Equal bool + }{ + {"2009-11-10T23:00:00Z", "2009-11-10T23:00:00.000000000", true}, + {"2011-01-10T23:10:05.758230235Z", "2011-01-10T23:10:05.758230235", true}, + } + for _, tc := range cases { + timeFromRFC, err := time.Parse(time.RFC3339Nano, tc.RFC3339NanoStr) + require.Nil(t, err) + timeFromSDKFormat, err := time.Parse(SortableTimeFormat, tc.SDKSortableTimeStr) + require.Nil(t, err) + + require.True(t, timeFromRFC.Equal(timeFromSDKFormat)) + require.Equal(t, timeFromRFC.Format(SortableTimeFormat), tc.SDKSortableTimeStr) + } +}