diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b63fe13bb31..a60d162ee24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,14 @@ BREAKING CHANGES * Removed MsgChangePubKey from auth * Removed setPubKey from account mapper * Removed GetMemo from Tx (it is still on StdTx) +* [cli] rearranged commands under subcommands +* [stake] remove Tick and add EndBlocker +* [stake] introduce concept of unbonding for delegations and validators + * `gaiacli stake unbond` replaced with `gaiacli stake begin-unbonding` + * introduced: + * `gaiacli stake complete-unbonding` + * `gaiacli stake begin-redelegation` + * `gaiacli stake complete-redelegation` FEATURES * [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag @@ -29,7 +37,11 @@ FEATURES * [types] Switches internal representation of Int/Uint/Rat to use pointers * [gaiad] unsafe_reset_all now resets addrbook.json -FIXES +FIXES +* [gaia] Added self delegation for validators in the genesis creation +* [lcd] tests now don't depend on raw json text +* [stake] error strings lower case +* [stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator * \#1259 - fix bug where certain tests that could have a nil pointer in defer * \#1052 - Make all now works * Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile @@ -39,6 +51,16 @@ FIXES * \#1353 - CLI: Show pool shares fractions in human-readable format * \#1258 - printing big.rat's can no longer overflow int64 +IMPROVEMENTS +* bank module uses go-wire codec instead of 'encoding/json' +* auth module uses go-wire codec instead of 'encoding/json' +* revised use of endblock and beginblock +* [stake] module reorganized to include `types` and `keeper` package +* [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency) +* [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value) +* [types] added common tag constants +* [stake] offload more generic functionality from the handler into the keeper + ## 0.19.0 *June 13, 2018* diff --git a/Makefile b/Makefile index a4d7e29c31a7..41883cfaf778 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ test_cover: @bash tests/test_cover.sh test_lint: - gometalinter.v2 --disable-all --enable='golint' --vendor ./... + gometalinter.v2 --disable-all --enable='golint' --enable='misspell' --vendor ./... benchmark: @go test -bench=. $(PACKAGES_NOCLITEST) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 3150e84a5ed1..b1c7e54605c3 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -598,6 +598,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) { // Write the Deliver state and commit the MultiStore app.deliverState.ms.Write() commitID := app.cms.Commit() + // TODO: this is missing a module identifier and dumps byte array app.Logger.Debug("Commit synced", "commit", commitID, ) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b56c27b95c52..40bc77b7eea3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -32,7 +32,7 @@ import ( func TestKeys(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() // get seed @@ -218,7 +218,7 @@ func TestValidators(t *testing.T) { func TestCoinSend(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6") @@ -260,7 +260,7 @@ func TestCoinSend(t *testing.T) { func TestIBCTransfer(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() acc := getAccount(t, port, addr) @@ -289,7 +289,7 @@ func TestIBCTransfer(t *testing.T) { func TestTxs(t *testing.T) { name, password := "test", "1234567890" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() // query wrong @@ -378,13 +378,13 @@ func TestValidatorsQuery(t *testing.T) { func TestBonding(t *testing.T) { name, password, denom := "test", "1234567890", "steak" addr, seed := CreateAddr(t, "test", password, GetKB(t)) - cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{addr}) + cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.Address{addr}) defer cleanup() validator1Owner := pks[0].Address() // create bond TX - resultTx := doBond(t, port, seed, name, password, addr, validator1Owner) + resultTx := doDelegate(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // check if tx was committed @@ -405,7 +405,7 @@ func TestBonding(t *testing.T) { // testing unbonding // create unbond TX - resultTx = doUnbond(t, port, seed, name, password, addr, validator1Owner) + resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Owner) tests.WaitForHeight(resultTx.Height+1, port) // query validator @@ -416,12 +416,13 @@ func TestBonding(t *testing.T) { assert.Equal(t, uint32(0), resultTx.CheckTx.Code) assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) - // TODO fix shares fn in staking + // should the sender should have not received any coins as the unbonding has only just begun // query sender - //acc := getAccount(t, sendAddr) - //coins := acc.GetCoins() - //assert.Equal(t, int64(98), coins.AmountOf(coinDenom)) + acc = getAccount(t, port, addr) + coins = acc.GetCoins() + assert.Equal(t, int64(40), coins.AmountOf("steak").Int64()) + // TODO add redelegation, need more complex capabilities such to mock context and } func TestSubmitProposal(t *testing.T) { @@ -572,6 +573,8 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add receiveAddr := receiveInfo.PubKey.Address() receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr) + chainID := viper.GetString(client.FlagChainID) + // get the account to get the sequence acc := getAccount(t, port, addr) accnum := acc.GetAccountNumber() @@ -584,13 +587,14 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add "account_number":%d, "sequence": %d, "gas": 100000, + "chain_id": "%s", "amount":[ { "denom": "%s", "amount": 1 } ] - }`, name, password, accnum, sequence, "steak")) + }`, name, password, accnum, sequence, chainID, "steak")) res, body := Request(t, port, "POST", "/ibc/testchain/"+receiveAddrBech+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -606,7 +610,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) // get the account to get the sequence - res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/bonding_status/"+validatorAddrBech, nil) + res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/delegation/"+validatorAddrBech, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var bond stake.Delegation err := cdc.UnmarshalJSON([]byte(body), &bond) @@ -614,7 +618,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A return bond } -func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() @@ -623,6 +627,8 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", @@ -630,15 +636,19 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali "account_number": %d, "sequence": %d, "gas": 10000, - "delegate": [ + "chain_id": "%s", + "delegations": [ { "delegator_addr": "%s", "validator_addr": "%s", "bond": { "denom": "%s", "amount": 60 } } ], - "unbond": [] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech, "steak")) + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech, "steak")) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -649,7 +659,9 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali return results[0] } -func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { +func doBeginUnbonding(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence acc := getAccount(t, port, delegatorAddr) accnum := acc.GetAccountNumber() @@ -658,6 +670,8 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr) + chainID := viper.GetString(client.FlagChainID) + // send jsonStr := []byte(fmt.Sprintf(`{ "name": "%s", @@ -665,15 +679,64 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va "account_number": %d, "sequence": %d, "gas": 10000, - "delegate": [], - "unbond": [ + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [ { "delegator_addr": "%s", "validator_addr": "%s", "shares": "30" } - ] - }`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech)) + ], + "complete_unbondings": [], + "begin_redelegates": [], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech)) + res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results []ctypes.ResultBroadcastTxCommit + err := cdc.UnmarshalJSON([]byte(body), &results) + require.Nil(t, err) + + return results[0] +} + +func doBeginRedelegation(t *testing.T, port, seed, name, password string, + delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) { + + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + + delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr) + validatorSrcAddrBech := sdk.MustBech32ifyVal(validatorSrcAddr) + validatorDstAddrBech := sdk.MustBech32ifyVal(validatorDstAddr) + + chainID := viper.GetString(client.FlagChainID) + + // send + jsonStr := []byte(fmt.Sprintf(`{ + "name": "%s", + "password": "%s", + "account_number": %d, + "sequence": %d, + "gas": 10000, + "chain_id": "%s", + "delegations": [], + "begin_unbondings": [], + "complete_unbondings": [], + "begin_redelegates": [ + { + "delegator_addr": "%s", + "validator_src_addr": "%s", + "validator_dst_addr": "%s", + "shares": "30" + } + ], + "complete_redelegates": [] + }`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorSrcAddrBech, validatorDstAddrBech)) res, body := Request(t, port, "POST", "/stake/delegations", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 9a22073b712b..93a14585f669 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -100,13 +100,13 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( config.TxIndex.IndexAllTags = true logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger = log.NewFilter(logger, log.AllowError()) + logger = log.NewFilter(logger, log.AllowDebug()) privValidatorFile := config.PrivValidatorFile() privVal := pvm.LoadOrGenFilePV(privValidatorFile) privVal.Reset() db := dbm.NewMemDB() app := gapp.NewGaiaApp(logger, db) - cdc = gapp.MakeCodec() // XXX + cdc = gapp.MakeCodec() genesisFile := config.GenesisFile() genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) @@ -146,6 +146,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) ( accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)} acc := gapp.NewGenesisAccount(&accAuth) genesisState.Accounts = append(genesisState.Accounts, acc) + genesisState.StakeData.Pool.LooseTokens += 100 } appState, err := wire.MarshalJSONIndent(cdc, genesisState) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 87eef40ed68e..c5b40b927f22 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -3,6 +3,7 @@ package app import ( "encoding/json" "errors" + "github.com/spf13/pflag" crypto "github.com/tendermint/go-crypto" tmtypes "github.com/tendermint/tendermint/types" @@ -17,8 +18,8 @@ import ( var ( // bonded tokens given to genesis validators/accounts - freeFermionVal = sdk.NewInt(100) - freeFermionsAcc = sdk.NewInt(50) + freeFermionVal = int64(100) + freeFermionsAcc = int64(50) ) // State to Unmarshal @@ -124,7 +125,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name st validator = tmtypes.GenesisValidator{ PubKey: pk, - Power: freeFermionVal.Int64(), + Power: freeFermionVal, } return } @@ -155,22 +156,33 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState accAuth := auth.NewBaseAccountWithAddress(genTx.Address) accAuth.Coins = sdk.Coins{ {genTx.Name + "Token", sdk.NewInt(1000)}, - {"steak", freeFermionsAcc}, + {"steak", sdk.NewInt(freeFermionsAcc)}, } acc := NewGenesisAccount(&accAuth) genaccs[i] = acc - stakeData.Pool.LooseUnbondedTokens = stakeData.Pool.LooseUnbondedTokens.Add(freeFermionsAcc) // increase the supply + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply // add the validator if len(genTx.Name) > 0 { desc := stake.NewDescription(genTx.Name, "", "", "") validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc) - validator.PoolShares = stake.NewBondedShares(sdk.NewRatFromInt(freeFermionVal)) + + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply + + // add some new shares to the validator + var issuedDelShares sdk.Rat + validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, freeFermionVal) stakeData.Validators = append(stakeData.Validators, validator) - // pool logic - stakeData.Pool.BondedTokens = stakeData.Pool.BondedTokens.Add(freeFermionVal) - stakeData.Pool.BondedShares = sdk.NewRatFromInt(stakeData.Pool.BondedTokens) + // create the self-delegation from the issuedDelShares + delegation := stake.Delegation{ + DelegatorAddr: validator.Owner, + ValidatorAddr: validator.Owner, + Shares: issuedDelShares, + Height: 0, + } + + stakeData.Bonds = append(stakeData.Bonds, delegation) } } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index ad7fbf985765..8c6e971e72ec 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -95,7 +95,8 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), + stakecmd.GetCmdRedelegate("stake", cdc), slashingcmd.GetCmdUnrevoke(cdc), )...) rootCmd.AddCommand( diff --git a/docs/spec/staking/end_block.md b/docs/spec/staking/end_block.md index 28e3891d1022..61643f52665f 100644 --- a/docs/spec/staking/end_block.md +++ b/docs/spec/staking/end_block.md @@ -37,8 +37,8 @@ processProvisions(): provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr) - pool.LooseUnbondedTokens += provisions - feePool += LooseUnbondedTokens + pool.LooseTokens += provisions + feePool += LooseTokens setPool(pool) diff --git a/docs/spec/staking/state.md b/docs/spec/staking/state.md index 2593f4754c76..f337f4f71c49 100644 --- a/docs/spec/staking/state.md +++ b/docs/spec/staking/state.md @@ -12,7 +12,7 @@ information, etc. ```golang type Pool struct { - LooseUnbondedTokens int64 // tokens not associated with any validator + LooseTokens int64 // tokens not associated with any validator UnbondedTokens int64 // reserve of unbonded tokens held with validators UnbondingTokens int64 // tokens moving from bonded to unbonded pool BondedTokens int64 // reserve of bonded tokens diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index 06ccd4a0c4c9..dea4f26a2843 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -172,7 +172,8 @@ func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage, app.accountMapper.IterateAccounts(ctx, appendAccount) genState := types.GenesisState{ - Accounts: accounts, + Accounts: accounts, + StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), } appState, err = wire.MarshalJSONIndent(app.cdc, genState) if err != nil { diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 6540af38aa27..7aae47d46a88 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -51,6 +51,10 @@ func main() { // add query/post commands (custom to binary) rootCmd.AddCommand( client.GetCommands( + stakecmd.GetCmdQueryValidator("stake", cdc), + stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryDelegation("stake", cdc), + stakecmd.GetCmdQueryDelegations("stake", cdc), authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)), )...) @@ -62,7 +66,7 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - stakecmd.GetCmdUnbond(cdc), + stakecmd.GetCmdUnbond("stake", cdc), )...) // add proxy, version and key info diff --git a/types/rational.go b/types/rational.go index 1d00e36cc0cb..a192aa316f58 100644 --- a/types/rational.go +++ b/types/rational.go @@ -110,7 +110,9 @@ func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - r func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 } func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than +func (r Rat) GTE(r2 Rat) bool { return !r.LT(r2) } // greater than or equal func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than +func (r Rat) LTE(r2 Rat) bool { return !r.GT(r2) } // less than or equal func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition diff --git a/types/rational_test.go b/types/rational_test.go index 71b823045d6d..43c9ddd57511 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) { bz, err := r.MarshalText() require.NoError(t, err) - var r2 Rat = Rat{new(big.Rat)} + var r2 = Rat{new(big.Rat)} err = r2.UnmarshalText(bz) require.NoError(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) diff --git a/types/stake.go b/types/stake.go index d49fe387fb1e..d4e3b79ca94a 100644 --- a/types/stake.go +++ b/types/stake.go @@ -59,8 +59,9 @@ type ValidatorSet interface { IterateValidatorsBonded(Context, func(index int64, validator Validator) (stop bool)) - Validator(Context, Address) Validator // get a particular validator by owner address - TotalPower(Context) Rat // total power of the validator set + Validator(Context, Address) Validator // get a particular validator by owner address + TotalPower(Context) Rat // total power of the validator set + Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction Revoke(Context, crypto.PubKey) // revoke a validator Unrevoke(Context, crypto.PubKey) // unrevoke a validator diff --git a/types/tags.go b/types/tags.go index 5a8eb1f473fc..6bd721a5fcc3 100644 --- a/types/tags.go +++ b/types/tags.go @@ -21,8 +21,8 @@ func (t Tags) AppendTag(k string, v []byte) Tags { } // Append two lists of tags -func (t Tags) AppendTags(a Tags) Tags { - return append(t, a...) +func (t Tags) AppendTags(tags Tags) Tags { + return append(t, tags...) } // Turn tags into KVPair list @@ -51,3 +51,13 @@ func NewTags(tags ...interface{}) Tags { func MakeTag(k string, v []byte) Tag { return Tag{Key: []byte(k), Value: v} } + +//__________________________________________________ + +// common tags +var ( + TagAction = "action" + TagSrcValidator = "source-validator" + TagDstValidator = "destination-validator" + TagDelegator = "delegator" +) diff --git a/wire/wire.go b/wire/wire.go index 0ee01939d0f8..d8420e8fa976 100644 --- a/wire/wire.go +++ b/wire/wire.go @@ -35,3 +35,15 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) { } return out.Bytes(), nil } + +//__________________________________________________________________ + +// generic sealed codec to be used throughout sdk +var Cdc *Codec + +func init() { + cdc := NewCodec() + RegisterCrypto(cdc) + Cdc = cdc + //Cdc = cdc.Seal() // TODO uncomment once amino upgraded to 0.9.10 +} diff --git a/x/gov/test_common.go b/x/gov/test_common.go index bce8e4b30cc4..d602aad15ec0 100644 --- a/x/gov/test_common.go +++ b/x/gov/test_common.go @@ -57,7 +57,11 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) + + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + + stake.InitGenesis(ctx, stakeKeeper, stakeGenesis) InitGenesis(ctx, keeper, DefaultGenesisState()) return abci.ResponseInitChain{} } diff --git a/x/slashing/app_test.go b/x/slashing/app_test.go index 0e4bb014b3ba..8d98cefe3ea4 100644 --- a/x/slashing/app_test.go +++ b/x/slashing/app_test.go @@ -55,7 +55,9 @@ func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - stake.InitGenesis(ctx, keeper, stake.DefaultGenesisState()) + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + stake.InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } } diff --git a/x/slashing/errors.go b/x/slashing/errors.go index b9b152c7b483..9f5d9c4eb0b4 100644 --- a/x/slashing/errors.go +++ b/x/slashing/errors.go @@ -12,41 +12,16 @@ const ( // Default slashing codespace DefaultCodespace sdk.CodespaceType = 10 - // Invalid validator - CodeInvalidValidator CodeType = 201 - // Validator jailed - CodeValidatorJailed CodeType = 202 + CodeInvalidValidator CodeType = 101 + CodeValidatorJailed CodeType = 102 ) func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") + return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator") } func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator does not exist for that address") + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") } func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked") -} - -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidValidator: - return "invalid Validator" - case CodeValidatorJailed: - return "validator Jailed" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -func msgOrDefaultMsg(msg string, code CodeType) string { - if msg != "" { - return msg - } - return codeToDefaultMsg(code) -} - -func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { - msg = msgOrDefaultMsg(msg, code) - return sdk.NewError(codespace, code, msg) + return sdk.NewError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked") } diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index d0a87e149edb..fc0d3bc16a6f 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -80,7 +80,7 @@ func TestHandleAbsentValidator(t *testing.T) { validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.BondedTokens) // 51st block missed ctx = ctx.WithBlockHeight(height) @@ -109,7 +109,7 @@ func TestHandleAbsentValidator(t *testing.T) { // validator should have been slashed pool = sk.GetPool(ctx) - require.Equal(t, int64(99), pool.BondedTokens.Int64()) + require.Equal(t, int64(99), pool.BondedTokens) // validator start height should have been changed info, found = keeper.getValidatorSigningInfo(ctx, val.Address()) @@ -167,5 +167,5 @@ func TestHandleNewValidator(t *testing.T) { validator, _ := sk.GetValidatorByPubKey(ctx, val) require.Equal(t, sdk.Bonded, validator.GetStatus()) pool := sk.GetPool(ctx) - require.Equal(t, int64(100), pool.BondedTokens.Int64()) + require.Equal(t, int64(100), pool.BondedTokens) } diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index 525705dc4ffd..795483efdfc9 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -20,6 +20,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) +// TODO remove dependencies on staking (should only refer to validator set type from sdk) + var ( addrs = []sdk.Address{ testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), @@ -61,7 +63,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep ck := bank.NewKeeper(accountMapper) sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace) genesis := stake.DefaultGenesisState() - genesis.Pool.LooseUnbondedTokens = initCoins.MulRaw(int64(len(addrs))) + genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64() stake.InitGenesis(ctx, sk, genesis) for _, addr := range addrs { ck.AddCoins(ctx, addr, sdk.Coins{ @@ -89,9 +91,9 @@ func testAddr(addr string) sdk.Address { func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { return stake.MsgCreateValidator{ - Description: stake.Description{}, - ValidatorAddr: address, - PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, + Description: stake.Description{}, + ValidatorAddr: address, + PubKey: pubKey, + SelfDelegation: sdk.Coin{"steak", amt}, } } diff --git a/x/stake/app_test.go b/x/stake/app_test.go index c0098fed30c0..41195f31b31e 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -22,9 +22,9 @@ var ( addr3 = crypto.GenPrivKeyEd25519().PubKey().Address() priv4 = crypto.GenPrivKeyEd25519() addr4 = priv4.PubKey().Address() - coins = sdk.Coins{sdk.NewCoin("foocoin", 10)} + coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}} fee = auth.StdFee{ - sdk.Coins{sdk.NewCoin("foocoin", 0)}, + sdk.Coins{{"foocoin", sdk.NewInt(0)}}, 100000, } ) @@ -60,7 +60,9 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker { func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer { return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { mapp.InitChainer(ctx, req) - InitGenesis(ctx, keeper, DefaultGenesisState()) + stakeGenesis := DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = 100000 + InitGenesis(ctx, keeper, stakeGenesis) return abci.ResponseInitChain{} } @@ -93,8 +95,8 @@ func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr, func TestStakeMsgs(t *testing.T) { mapp, keeper := getMockApp(t) - genCoin := sdk.NewCoin("steak", 42) - bondCoin := sdk.NewCoin("steak", 10) + genCoin := sdk.Coin{"steak", sdk.NewInt(42)} + bondCoin := sdk.Coin{"steak", sdk.NewInt(10)} acc1 := &auth.BaseAccount{ Address: addr1, @@ -148,10 +150,14 @@ func TestStakeMsgs(t *testing.T) { checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10)) //////////////////// - // Unbond + // Begin Unbonding - unbondMsg := NewMsgUnbond(addr2, addr1, "MAX") - mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unbondMsg}, []int64{1}, []int64{1}, true, priv2) - mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin}) + beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10)) + mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2) + + // delegation should exist anymore checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{}) + + // balance should be the same because bonding not yet complete + mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)}) } diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index eea7e2031937..bb8923b58c33 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -6,11 +6,14 @@ import ( // nolint const ( - FlagAddressDelegator = "address-delegator" - FlagAddressValidator = "address-validator" - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" + FlagAddressDelegator = "address-delegator" + FlagAddressValidator = "address-validator" + FlagAddressValidatorSrc = "addr-validator-source" + FlagAddressValidatorDst = "addr-validator-dest" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagSharesAmount = "shares-amount" + FlagSharesPercent = "shares-percent" FlagMoniker = "moniker" FlagIdentity = "keybase-sig" @@ -20,22 +23,26 @@ const ( // common flagsets to add to various functions var ( - fsPk = flag.NewFlagSet("", flag.ContinueOnError) - fsAmount = flag.NewFlagSet("", flag.ContinueOnError) - fsShares = flag.NewFlagSet("", flag.ContinueOnError) - fsDescription = flag.NewFlagSet("", flag.ContinueOnError) - fsValidator = flag.NewFlagSet("", flag.ContinueOnError) - fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsPk = flag.NewFlagSet("", flag.ContinueOnError) + fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsDescription = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) + fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) + fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator. For Ed25519 the go-amino prepend hex is 1624de6220") fsAmount.String(FlagAmount, "1steak", "Amount of coins to bond") - fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") - fsDescription.String(FlagMoniker, "", "validator name") - fsDescription.String(FlagIdentity, "", "optional keybase signature") - fsDescription.String(FlagWebsite, "", "optional website") - fsDescription.String(FlagDetails, "", "optional details") + fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal") + fsShares.String(FlagSharesPercent, "", "Percent of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1") + fsDescription.String(FlagMoniker, "[do-not-modify]", "validator name") + fsDescription.String(FlagIdentity, "[do-not-modify]", "optional keybase signature") + fsDescription.String(FlagWebsite, "[do-not-modify]", "optional website") + fsDescription.String(FlagDetails, "[do-not-modify]", "optional details") fsValidator.String(FlagAddressValidator, "", "hex address of the validator") fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") + fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator") + fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator") } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 727cddcde20f..c162717efd8c 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" // XXX fix + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -105,14 +105,14 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query a single delegation bond +// get the command to query a single delegation func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegation", - Short: "Query a delegations bond based on address and validator address", + Short: "Query a delegation based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -122,26 +122,26 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return err } - key := stake.GetDelegationKey(delAddr, addr, cdc) + key := stake.GetDelegationKey(delAddr, valAddr, cdc) ctx := context.NewCoreContextFromViper() res, err := ctx.QueryStore(key, storeName) if err != nil { return err } - // parse out the bond - bond := new(stake.Delegation) + // parse out the delegation + delegation := new(stake.Delegation) switch viper.Get(cli.OutputFlag) { case "text": - resp, err := bond.HumanReadableString() + resp, err := delegation.HumanReadableString() if err != nil { return err } fmt.Println(resp) case "json": - cdc.MustUnmarshalBinary(res, bond) - output, err := wire.MarshalJSONIndent(cdc, bond) + cdc.MustUnmarshalBinary(res, delegation) + output, err := wire.MarshalJSONIndent(cdc, delegation) if err != nil { return err } @@ -157,7 +157,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query all the validators bonded to a delegation +// get the command to query all the delegations made from one delegator func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegations [delegator-addr]", @@ -196,3 +196,190 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { } return cmd } + +// get the command to query a single unbonding-delegation record +func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetUBDKey(delAddr, valAddr, cdc) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + ubd := new(stake.UnbondingDelegation) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := ubd.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + cdc.MustUnmarshalBinary(res, ubd) + output, err := wire.MarshalJSONIndent(cdc, ubd) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsValidator) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) + if err != nil { + return err + } + key := stake.GetUBDsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var ubds []stake.UnbondingDelegation + for _, KV := range resKVs { + var ubd stake.UnbondingDelegation + cdc.MustUnmarshalBinary(KV.Value, &ubd) + ubds = append(ubds, ubd) + } + + output, err := wire.MarshalJSONIndent(cdc, ubds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} + +// get the command to query a single unbonding-delegation record +func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegation", + Short: "Query an unbonding-delegation record based on delegator and validator address", + RunE: func(cmd *cobra.Command, args []string) error { + + valSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + valDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + + key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr, cdc) + ctx := context.NewCoreContextFromViper() + res, err := ctx.QueryStore(key, storeName) + if err != nil { + return err + } + + // parse out the unbonding delegation + red := new(stake.Redelegation) + + switch viper.Get(cli.OutputFlag) { + case "text": + resp, err := red.HumanReadableString() + if err != nil { + return err + } + fmt.Println(resp) + case "json": + cdc.MustUnmarshalBinary(res, red) + output, err := wire.MarshalJSONIndent(cdc, red) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + } + return nil + }, + } + + cmd.Flags().AddFlagSet(fsRedelegation) + cmd.Flags().AddFlagSet(fsDelegator) + return cmd +} + +// get the command to query all the unbonding-delegation records for a delegator +func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbonding-delegations [delegator-addr]", + Short: "Query all unbonding-delegations records for one delegator", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(args[0]) + if err != nil { + return err + } + key := stake.GetREDsKey(delegatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resKVs, err := ctx.QuerySubspace(cdc, key, storeName) + if err != nil { + return err + } + + // parse out the validators + var reds []stake.Redelegation + for _, KV := range resKVs { + var red stake.Redelegation + cdc.MustUnmarshalBinary(KV.Value, &red) + reds = append(reds, red) + } + + output, err := wire.MarshalJSONIndent(cdc, reds) + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, + } + return cmd +} diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index 7245e420b26c..7543d2707b24 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -3,6 +3,7 @@ package cli import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -104,11 +105,11 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command { return cmd } -// create edit validator command +// delegate command func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", - Short: "delegate coins to an existing validator", + Short: "delegate liquid tokens to an validator", RunE: func(cmd *cobra.Command, args []string) error { amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { @@ -143,33 +144,185 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { } // create edit validator command -func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { +func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "unbond", - Short: "unbond shares from a validator", + Use: "redelegate", + Short: "redelegate illiquid tokens from one validator to another", + } + cmd.AddCommand( + GetCmdBeginRedelegate(storeName, cdc), + GetCmdCompleteRedelegate(cdc), + ) + return cmd +} + +// redelegate command +func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin redelegation", + RunE: func(cmd *cobra.Command, args []string) error { + + var err error + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } + validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + if err != nil { + return err + } + validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorSrcAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr, sharesAmount) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + + cmd.Flags().AddFlagSet(fsShares) + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercentStr string, + delegatorAddr, validatorAddr sdk.Address) (sharesAmount sdk.Rat, err error) { + + switch { + case sharesAmountStr != "" && sharesPercentStr != "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr == "" && sharesPercentStr == "": + return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both") + case sharesAmountStr != "": + sharesAmount, err = sdk.NewRatFromDecimal(sharesAmountStr) + if err != nil { + return sharesAmount, err + } + if !sharesAmount.GT(sdk.ZeroRat()) { + return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)") + } + case sharesPercentStr != "": + var sharesPercent sdk.Rat + sharesPercent, err = sdk.NewRatFromDecimal(sharesPercentStr) + if err != nil { + return sharesAmount, err + } + if !sharesPercent.GT(sdk.ZeroRat()) || !sharesPercent.LTE(sdk.OneRat()) { + return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)") + } + + // make a query to get the existing delegation shares + key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) + ctx := context.NewCoreContextFromViper() + resQuery, err := ctx.QueryStore(key, storeName) + if err != nil { + return sharesAmount, err + } + var delegation stake.Delegation + err = cdc.UnmarshalBinary(resQuery, &delegation) + if err != nil { + return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err) + } + + sharesAmount = sharesPercent.Mul(delegation.Shares) + } + return +} + +// redelegate command +func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete redelegation", RunE: func(cmd *cobra.Command, args []string) error { - // check the shares before broadcasting - sharesStr := viper.GetString(FlagShares) - var shares sdk.Rat - if sharesStr != "MAX" { - var err error - shares, err = sdk.NewRatFromDecimal(sharesStr) - if err != nil { - return err - } - if !shares.GT(sdk.ZeroRat()) { - return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") - } + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc)) + validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err } + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsRedelegation) + return cmd +} + +// create edit validator command +func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbond", + Short: "begin or complete unbonding shares from a validator", + } + cmd.AddCommand( + GetCmdBeginUnbonding(storeName, cdc), + GetCmdCompleteUnbonding(cdc), + ) + return cmd +} + +// create edit validator command +func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "begin", + Short: "begin unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + if err != nil { + return err + } validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) if err != nil { return err } - msg := stake.NewMsgUnbond(delegatorAddr, validatorAddr, sharesStr) + // get the shares amount + sharesAmountStr := viper.GetString(FlagSharesAmount) + sharesPercentStr := viper.GetString(FlagSharesPercent) + sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr, + delegatorAddr, validatorAddr) + if err != nil { + return err + } + + msg := stake.NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sharesAmount) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -189,3 +342,35 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsValidator) return cmd } + +// create edit validator command +func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "complete", + Short: "complete unbonding", + RunE: func(cmd *cobra.Command, args []string) error { + + delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator)) + validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator)) + if err != nil { + return err + } + + msg := stake.NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + + // build and sign the transaction, then broadcast to Tendermint + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, + } + cmd.Flags().AddFlagSet(fsDelegator) + cmd.Flags().AddFlagSet(fsValidator) + return cmd +} diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index ec0e81330ad0..fd213382b4b7 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -13,18 +13,30 @@ import ( ) func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) { + + r.HandleFunc( + "/stake/{delegator}/delegation/{validator}", + delegationHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + + r.HandleFunc( + "/stake/{delegator}/ubd/{validator}", + ubdHandlerFn(ctx, "stake", cdc), + ).Methods("GET") + r.HandleFunc( - "/stake/{delegator}/bonding_status/{validator}", - bondingStatusHandlerFn(ctx, "stake", cdc), + "/stake/{delegator}/red/{validator_src}/{validator_dst}", + redHandlerFn(ctx, "stake", cdc), ).Methods("GET") + r.HandleFunc( "/stake/validators", validatorsHandlerFn(ctx, "stake", cdc), ).Methods("GET") } -// http request handler to query delegator bonding status -func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { +// http request handler to query a delegation +func delegationHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // read parameters @@ -51,25 +63,147 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire res, err := ctx.QueryStore(key, storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't query bond. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + var delegation stake.Delegation + err = cdc.UnmarshalBinary(res, &delegation) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't decode delegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(delegation) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an unbonding-delegation +func ubdHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validator := vars["validator"] + + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorAddr, err := sdk.GetValAddressBech32(bech32validator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetUBDKey(delegatorAddr, validatorAddr, cdc) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this record + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + var ubd stake.UnbondingDelegation + err = cdc.UnmarshalBinary(res, &ubd) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't decode unbonding-delegation. Error: %s", err.Error()))) + return + } + + output, err := cdc.MarshalJSON(ubd) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} + +// http request handler to query an redelegation +func redHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + // read parameters + vars := mux.Vars(r) + bech32delegator := vars["delegator"] + bech32validatorSrc := vars["validator_src"] + bech32validatorDst := vars["validator_dst"] + + delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorSrcAddr, err := sdk.GetValAddressBech32(bech32validatorSrc) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + validatorDstAddr, err := sdk.GetValAddressBech32(bech32validatorDst) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr, cdc) + + res, err := ctx.QueryStore(key, storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error()))) return } - // the query will return empty if there is no data for this bond + // the query will return empty if there is no data for this record if len(res) == 0 { w.WriteHeader(http.StatusNoContent) return } - var bond stake.Delegation - err = cdc.UnmarshalBinary(res, &bond) + var red stake.Redelegation + err = cdc.UnmarshalBinary(res, &red) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode bond. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("couldn't decode redelegation. Error: %s", err.Error()))) return } - output, err := cdc.MarshalJSON(bond) + output, err := cdc.MarshalJSON(red) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) diff --git a/x/stake/client/rest/tx.go b/x/stake/client/rest/tx.go index 09a27de9fe95..47860318cb8f 100644 --- a/x/stake/client/rest/tx.go +++ b/x/stake/client/rest/tx.go @@ -24,31 +24,50 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k ).Methods("POST") } -type msgDelegateInput struct { +type msgDelegationsInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 Bond sdk.Coin `json:"bond"` } -type msgUnbondInput struct { +type msgBeginRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteRedelegateInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32 + ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32 +} +type msgBeginUnbondingInput struct { + DelegatorAddr string `json:"delegator_addr"` // in bech32 + ValidatorAddr string `json:"validator_addr"` // in bech32 + SharesAmount string `json:"shares"` +} +type msgCompleteUnbondingInput struct { DelegatorAddr string `json:"delegator_addr"` // in bech32 ValidatorAddr string `json:"validator_addr"` // in bech32 - Shares string `json:"shares"` } -type editDelegationsBody struct { - LocalAccountName string `json:"name"` - Password string `json:"password"` - ChainID string `json:"chain_id"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` - Gas int64 `json:"gas"` - Delegate []msgDelegateInput `json:"delegate"` - Unbond []msgUnbondInput `json:"unbond"` +// request body for edit delegations +type EditDelegationsBody struct { + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` + Gas int64 `json:"gas"` + Delegations []msgDelegationsInput `json:"delegations"` + BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"` + CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"` + BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"` + CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"` } func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - var m editDelegationsBody + var m EditDelegationsBody body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -70,24 +89,29 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte } // build messages - messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond)) + messages := make([]sdk.Msg, len(m.Delegations)+ + len(m.BeginRedelegates)+ + len(m.CompleteRedelegates)+ + len(m.BeginUnbondings)+ + len(m.CompleteUnbondings)) + i := 0 - for _, msg := range m.Delegate { + for _, msg := range m.Delegations { delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode delegator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode validator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } if !bytes.Equal(info.Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("must use own delegator address")) + w.Write([]byte("Must use own delegator address")) return } messages[i] = stake.MsgDelegate{ @@ -97,28 +121,131 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte } i++ } - for _, msg := range m.Unbond { + + for _, msg := range m.BeginRedelegates { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteRedelegates { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + messages[i] = stake.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } + i++ + } + + for _, msg := range m.BeginUnbondings { + delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) + return + } + if !bytes.Equal(info.Address(), delegatorAddr) { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte("Must use own delegator address")) + return + } + validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) + return + } + shares, err := sdk.NewRatFromDecimal(msg.SharesAmount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error()))) + return + } + messages[i] = stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: shares, + } + i++ + } + + for _, msg := range m.CompleteUnbondings { delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode delegator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error()))) return } validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr) if err != nil { w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(fmt.Sprintf("couldn't decode validator. Error: %s", err.Error()))) + w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error()))) return } if !bytes.Equal(info.Address(), delegatorAddr) { w.WriteHeader(http.StatusUnauthorized) - w.Write([]byte("must use own delegator address")) + w.Write([]byte("Must use own delegator address")) return } - messages[i] = stake.MsgUnbond{ + messages[i] = stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Shares: msg.Shares, } i++ } diff --git a/x/stake/delegation.go b/x/stake/delegation.go deleted file mode 100644 index 8b047accedc1..000000000000 --- a/x/stake/delegation.go +++ /dev/null @@ -1,52 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Delegation represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -type Delegation struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares sdk.Rat `json:"shares"` - Height int64 `json:"height"` // Last height bond updated -} - -func (b Delegation) equal(b2 Delegation) bool { - return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) && - bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) && - b.Height == b2.Height && - b.Shares.Equal(b2.Shares) -} - -// ensure fulfills the sdk validator types -var _ sdk.Delegation = Delegation{} - -// nolint - for sdk.Delegation -func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr } -func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr } -func (b Delegation) GetBondShares() sdk.Rat { return b.Shares } - -//Human Friendly pretty printer -func (b Delegation) HumanReadableString() (string, error) { - bechAcc, err := sdk.Bech32ifyAcc(b.DelegatorAddr) - if err != nil { - return "", err - } - bechVal, err := sdk.Bech32ifyAcc(b.ValidatorAddr) - if err != nil { - return "", err - } - resp := "Delegation \n" - resp += fmt.Sprintf("Delegator: %s\n", bechAcc) - resp += fmt.Sprintf("Validator: %s\n", bechVal) - resp += fmt.Sprintf("Shares: %s", b.Shares.String()) - resp += fmt.Sprintf("Height: %d", b.Height) - - return resp, nil -} diff --git a/x/stake/errors.go b/x/stake/errors.go deleted file mode 100644 index 796d638dbcec..000000000000 --- a/x/stake/errors.go +++ /dev/null @@ -1,117 +0,0 @@ -// nolint -package stake - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -type CodeType = sdk.CodeType - -const ( - DefaultCodespace sdk.CodespaceType = 4 - - // Gaia errors reserve 200 ~ 299. - CodeInvalidValidator CodeType = 201 - CodeInvalidBond CodeType = 202 - CodeInvalidInput CodeType = 203 - CodeValidatorJailed CodeType = 204 - CodeUnauthorized CodeType = sdk.CodeUnauthorized - CodeInternal CodeType = sdk.CodeInternal - CodeUnknownRequest CodeType = sdk.CodeUnknownRequest -) - -// NOTE: Don't stringer this, we'll put better messages in later. -func codeToDefaultMsg(code CodeType) string { - switch code { - case CodeInvalidValidator: - return "invalid Validator" - case CodeInvalidBond: - return "invalid Bond" - case CodeInvalidInput: - return "invalid Input" - case CodeUnauthorized: - return "unauthorized" - case CodeInternal: - return "internal Error" - case CodeUnknownRequest: - return "unknown request" - default: - return sdk.CodeToDefaultMsg(code) - } -} - -//---------------------------------------- -// Error constructors - -func ErrNotEnoughBondShares(codespace sdk.CodespaceType, shares string) sdk.Error { - return newError(codespace, CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares)) -} -func ErrValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "cannot bond to an empty validator") -} -func ErrBadBondingDenom(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "invalid coin denomination") -} -func ErrBadBondingAmount(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidBond, "amount must be > 0") -} -func ErrNoBondingAcct(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "no bond account for this (address, validator) pair") -} -func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "commission must be positive") -} -func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") -} -func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator does not exist for that address") -} -func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "delegator does not exist for that address") -} -func ErrValidatorExistsAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator") -} -func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") -} -func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "missing signature") -} -func ErrBondNotNominated(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "cannot bond to non-nominated account") -} -func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "validator does not exist for that address") -} -func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "delegator does not contain validator bond") -} -func ErrInsufficientFunds(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "insufficient bond shares") -} -func ErrBadShares(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidInput, "bad shares provided as input, must be MAX or decimal") -} -func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "error removing validator") -} - -//---------------------------------------- - -// TODO group with code from x/bank/errors.go - -func msgOrDefaultMsg(msg string, code CodeType) string { - if msg != "" { - return msg - } - return codeToDefaultMsg(code) -} - -func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error { - msg = msgOrDefaultMsg(msg, code) - return sdk.NewError(codespace, code, msg) -} diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 43ea61d8b775..6a15ebeb42e3 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -4,63 +4,39 @@ import ( tmtypes "github.com/tendermint/tendermint/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) -// GenesisState - all staking state that must be provided at genesis -type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Validators []Validator `json:"validators"` - Bonds []Delegation `json:"bonds"` -} - -func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { - return GenesisState{ - Pool: pool, - Params: params, - Validators: validators, - Bonds: bonds, - } -} - -// get raw genesis raw message for testing -func DefaultGenesisState() GenesisState { - return GenesisState{ - Pool: InitialPool(), - Params: DefaultParams(), - } -} - // InitGenesis - store genesis parameters -func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - store := ctx.KVStore(k.storeKey) - k.setPool(ctx, data.Pool) - k.setNewParams(ctx, data.Params) +func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { + keeper.SetPool(ctx, data.Pool) + keeper.SetNewParams(ctx, data.Params) + keeper.InitIntraTxCounter(ctx) for _, validator := range data.Validators { // set validator - k.setValidator(ctx, validator) + keeper.SetValidator(ctx, validator) // manually set indexes for the first time - k.setValidatorByPubKeyIndex(ctx, validator) - k.setValidatorByPowerIndex(ctx, validator, data.Pool) + keeper.SetValidatorByPubKeyIndex(ctx, validator) + keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) if validator.Status() == sdk.Bonded { - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) + keeper.SetValidatorBondedIndex(ctx, validator) } } for _, bond := range data.Bonds { - k.setDelegation(ctx, bond) + keeper.SetDelegation(ctx, bond) } - k.updateBondedValidatorsFull(ctx, store) + keeper.UpdateBondedValidatorsFull(ctx) } // WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - validators := k.getAllValidators(ctx) - bonds := k.getAllDelegations(ctx) - return GenesisState{ +func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + validators := keeper.GetAllValidators(ctx) + bonds := keeper.GetAllDelegations(ctx) + return types.GenesisState{ pool, params, validators, @@ -69,8 +45,8 @@ func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { } // WriteValidators - output current validator set -func WriteValidators(ctx sdk.Context, k Keeper) (vals []tmtypes.GenesisValidator) { - k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { +func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) { + keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) { vals = append(vals, tmtypes.GenesisValidator{ PubKey: validator.GetPubKey(), Power: validator.GetPower().Evaluate(), diff --git a/x/stake/handler.go b/x/stake/handler.go index f366989b62a7..9555d270e692 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,24 +1,32 @@ package stake import ( - "bytes" + abci "github.com/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) -func NewHandler(k Keeper) sdk.Handler { +func NewHandler(k keeper.Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // NOTE msg already has validate basic run switch msg := msg.(type) { - case MsgCreateValidator: + case types.MsgCreateValidator: return handleMsgCreateValidator(ctx, msg, k) - case MsgEditValidator: + case types.MsgEditValidator: return handleMsgEditValidator(ctx, msg, k) - case MsgDelegate: + case types.MsgDelegate: return handleMsgDelegate(ctx, msg, k) - case MsgUnbond: - return handleMsgUnbond(ctx, msg, k) + case types.MsgBeginRedelegate: + return handleMsgBeginRedelegate(ctx, msg, k) + case types.MsgCompleteRedelegate: + return handleMsgCompleteRedelegate(ctx, msg, k) + case types.MsgBeginUnbonding: + return handleMsgBeginUnbonding(ctx, msg, k) + case types.MsgCompleteUnbonding: + return handleMsgCompleteUnbonding(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -26,25 +34,25 @@ func NewHandler(k Keeper) sdk.Handler { } // Called every block, process inflation, update validator set -func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { +func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) { pool := k.GetPool(ctx) - // Process Validator Provisions - blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + // Process types.Validator Provisions + blockTime := ctx.BlockHeader().Time if pool.InflationLastTime+blockTime >= 3600 { pool.InflationLastTime = blockTime - pool = k.processProvisions(ctx) + pool = k.ProcessProvisions(ctx) } // save the params - k.setPool(ctx, pool) + k.SetPool(ctx, pool) // reset the intra-transaction counter - k.setIntraTxCounter(ctx, 0) + k.SetIntraTxCounter(ctx, 0) // calculate validator set changes - ValidatorUpdates = k.getTendermintUpdates(ctx) - k.clearTendermintUpdates(ctx) + ValidatorUpdates = k.GetTendermintUpdates(ctx) + k.ClearTendermintUpdates(ctx) return } @@ -53,212 +61,150 @@ func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) { // These functions assume everything has been authenticated, // now we just perform action and save -func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k Keeper) sdk.Result { +func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { - return ErrValidatorExistsAddr(k.codespace).Result() - } - if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() + return ErrValidatorAlreadyExists(k.Codespace()).Result() } - if ctx.IsCheckTx() { - return sdk.Result{} + if msg.SelfDelegation.Denom != k.GetParams(ctx).BondDenom { + return ErrBadDenom(k.Codespace()).Result() } validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) - k.setValidator(ctx, validator) - k.setValidatorByPubKeyIndex(ctx, validator) - tags := sdk.NewTags( - "action", []byte("createValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), - ) + k.SetValidator(ctx, validator) + k.SetValidatorByPubKeyIndex(ctx, validator) - // move coins from the msg.Address account to a (self-bond) delegator account + // move coins from the msg.Address account to a (self-delegation) delegator account // the validator account and global shares are updated within here - delegateTags, err := delegate(ctx, k, msg.ValidatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.ValidatorAddr, msg.SelfDelegation, validator) if err != nil { return err.Result() } - tags = tags.AppendTags(delegateTags) + + tags := sdk.NewTags( + tags.Action, tags.ActionCreateValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(msg.Description.Moniker), + tags.Identity, []byte(msg.Description.Identity), + ) return sdk.Result{ Tags: tags, } } -func handleMsgEditValidator(ctx sdk.Context, msg MsgEditValidator, k Keeper) sdk.Result { +func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result { // validator must already be registered validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() - } - if ctx.IsCheckTx() { - return sdk.Result{} + return ErrNoValidatorFound(k.Codespace()).Result() } - // XXX move to types // replace all editable fields (clients should autofill existing values) - validator.Description.Moniker = msg.Description.Moniker - validator.Description.Identity = msg.Description.Identity - validator.Description.Website = msg.Description.Website - validator.Description.Details = msg.Description.Details + description, err := validator.Description.UpdateDescription(msg.Description) + if err != nil { + return err.Result() + } + validator.Description = description - k.updateValidator(ctx, validator) + k.UpdateValidator(ctx, validator) tags := sdk.NewTags( - "action", []byte("editValidator"), - "validator", msg.ValidatorAddr.Bytes(), - "moniker", []byte(msg.Description.Moniker), - "identity", []byte(msg.Description.Identity), + tags.Action, tags.ActionEditValidator, + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + tags.Moniker, []byte(description.Moniker), + tags.Identity, []byte(description.Identity), ) return sdk.Result{ Tags: tags, } } -func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { +func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result { validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadValidatorAddr(k.codespace).Result() + return ErrNoValidatorFound(k.Codespace()).Result() } if msg.Bond.Denom != k.GetParams(ctx).BondDenom { - return ErrBadBondingDenom(k.codespace).Result() + return ErrBadDenom(k.Codespace()).Result() } if validator.Revoked == true { - return ErrValidatorRevoked(k.codespace).Result() - } - if ctx.IsCheckTx() { - return sdk.Result{} + return ErrValidatorRevoked(k.Codespace()).Result() } - tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, validator) + _, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Bond, validator) if err != nil { return err.Result() } + + tags := sdk.NewTags( + tags.Action, tags.ActionDelegate, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.DstValidator, []byte(msg.ValidatorAddr.String()), + ) return sdk.Result{ Tags: tags, } } -// common functionality between handlers -func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, validator Validator) (sdk.Tags, sdk.Error) { - - // Get or create the delegator bond - bond, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) - if !found { - bond = Delegation{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validator.Owner, - Shares: sdk.ZeroRat(), - } - } - - // Account new shares, save - pool := k.GetPool(ctx) - _, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) +func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result { + err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount) if err != nil { - return nil, err + return err.Result() } - validator, pool, newShares := validator.addTokensFromDel(pool, bondAmt.Amount) - bond.Shares = bond.Shares.Add(newShares) - // Update bond height - bond.Height = ctx.BlockHeight() - - k.setPool(ctx, pool) - k.setDelegation(ctx, bond) - k.updateValidator(ctx, validator) - tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "validator", validator.Owner.Bytes()) - return tags, nil + tags := sdk.NewTags( + tags.Action, tags.ActionBeginUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{Tags: tags} } -func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { - - // check if bond has any shares in it unbond - bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.ValidatorAddr) - if !found { - return ErrNoDelegatorForAddress(k.codespace).Result() - } - - var delShares sdk.Rat - - // test that there are enough shares to unbond - if msg.Shares == "MAX" { - if !bond.Shares.GT(sdk.ZeroRat()) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } else { - var err sdk.Error - delShares, err = sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return err.Result() - } - if bond.Shares.LT(delShares) { - return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() - } - } - - // get validator - validator, found := k.GetValidator(ctx, msg.ValidatorAddr) - if !found { - return ErrNoValidatorForAddress(k.codespace).Result() - } +func handleMsgCompleteUnbonding(ctx sdk.Context, msg types.MsgCompleteUnbonding, k keeper.Keeper) sdk.Result { - if ctx.IsCheckTx() { - return sdk.Result{} - } - - // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - if msg.Shares == "MAX" { - delShares = bond.Shares + err := k.CompleteUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + if err != nil { + return err.Result() } - // subtract bond tokens from delegator bond - bond.Shares = bond.Shares.Sub(delShares) - - // remove the bond - revokeValidator := false - if bond.Shares.IsZero() { - - // if the bond is the owner of the validator then - // trigger a revoke validator - if bytes.Equal(bond.DelegatorAddr, validator.Owner) && - validator.Revoked == false { - revokeValidator = true - } + tags := sdk.NewTags( + tags.Action, ActionCompleteUnbonding, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorAddr.String()), + ) - k.removeDelegation(ctx, bond) - } else { - // Update bond height - bond.Height = ctx.BlockHeight() - k.setDelegation(ctx, bond) - } + return sdk.Result{Tags: tags} +} - // Add the coins - pool := k.GetPool(ctx) - validator, pool, returnAmount := validator.removeDelShares(pool, delShares) - k.setPool(ctx, pool) - returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} - k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) - - ///////////////////////////////////// - // revoke validator if necessary - if revokeValidator { - validator.Revoked = true +func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result { + err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, + msg.ValidatorDstAddr, msg.SharesAmount) + if err != nil { + return err.Result() } - validator = k.updateValidator(ctx, validator) + tags := sdk.NewTags( + tags.Action, tags.ActionBeginRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} +} - if validator.DelegatorShares.IsZero() { - k.removeValidator(ctx, validator.Owner) +func handleMsgCompleteRedelegate(ctx sdk.Context, msg types.MsgCompleteRedelegate, k keeper.Keeper) sdk.Result { + err := k.CompleteRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr) + if err != nil { + return err.Result() } - tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes()) - return sdk.Result{ - Tags: tags, - } + tags := sdk.NewTags( + tags.Action, tags.ActionCompleteRedelegation, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()), + tags.DstValidator, []byte(msg.ValidatorDstAddr.String()), + ) + return sdk.Result{Tags: tags} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 56c2adf9d529..80225fd510d8 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,7 +1,6 @@ package stake import ( - "strconv" "testing" "github.com/stretchr/testify/assert" @@ -10,38 +9,48 @@ import ( crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" + keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //______________________________________________________________________ -func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator { +func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgCreateValidator { return MsgCreateValidator{ - Description: Description{}, - ValidatorAddr: address, - PubKey: pubKey, - Bond: sdk.Coin{"steak", amt}, + Description: Description{}, + ValidatorAddr: address, + PubKey: pubKey, + SelfDelegation: sdk.Coin{"steak", sdk.NewInt(amt)}, } } -func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt sdk.Int) MsgDelegate { +func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, ValidatorAddr: validatorAddr, - Bond: sdk.Coin{"steak", amt}, + Bond: sdk.Coin{"steak", sdk.NewInt(amt)}, } } +// 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 := addrs[0], addrs[1] + validatorAddr, validatorAddr3 := keep.Addrs[0], keep.Addrs[1] - initBond := sdk.NewInt(1000000) - initBondStr := "1000" - ctx, _, keeper := createTestInput(t, false, initBond) + initBond := int64(1000000) + ctx, _, keeper := keep.CreateTestInput(t, false, initBond) + _ = setInstantUnbondPeriod(keeper, ctx) // create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -49,7 +58,7 @@ func TestValidatorByPowerIndex(t *testing.T) { bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) gotBond := bond.Shares.Evaluate() - require.Equal(t, initBond.Int64(), gotBond, + require.Equal(t, initBond, gotBond, "initBond: %v\ngotBond: %v\nbond: %v\n", initBond, gotBond, bond) @@ -57,60 +66,62 @@ func TestValidatorByPowerIndex(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) pool := keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + power := keep.GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // create a second validator keep it bonded - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, pks[2], sdk.NewInt(1000000)) + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // slash and revoke the first validator - keeper.Slash(ctx, pks[0], 0, sdk.NewRat(1, 2)) - keeper.Revoke(ctx, pks[0]) + keeper.Slash(ctx, keep.PKs[0], 0, sdk.NewRat(1, 2)) + keeper.Revoke(ctx, keep.PKs[0]) validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded require.Equal(t, int64(500000), validator.PoolShares.Amount.Evaluate()) // ensure is unbonded // the old power record should have been deleted as the power changed - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + assert.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 := GetValidatorsByPowerKey(validator, pool) - require.True(t, keeper.validatorByPowerIndexExists(ctx, power2)) + power2 := GetValidatorsByPowerIndexKey(validator, pool) + require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2)) // inflate a bunch for i := 0; i < 20000; i++ { - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) } // now the new record power index should be the same as the original record - power3 := GetValidatorsByPowerKey(validator, pool) + power3 := GetValidatorsByPowerIndexKey(validator, pool) assert.Equal(t, power2, power3) // unbond self-delegation - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "MAX") - got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\ninitBondStr: %v\n", got, msgUnbond, initBondStr) + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(1000000)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // verify that by power key nolonger exists _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power3)) + assert.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power3)) } func TestDuplicatesMsgCreateValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000)) + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) - validatorAddr := addrs[0] - pk := pks[0] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, sdk.NewInt(10)) + validatorAddr := keep.Addrs[0] + pk := keep.PKs[0] + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "%v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) @@ -124,41 +135,41 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, Description{}, validator.Description) // one validator cannot bond twice - msgCreateValidator.PubKey = pks[1] + msgCreateValidator.PubKey = keep.PKs[1] got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.False(t, got.IsOK(), "%v", got) } func TestIncrementsMsgDelegate(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) params := keeper.GetParams(ctx) - bondAmount := sdk.NewInt(10) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + bondAmount := int64(10) + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] // first create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], bondAmount) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) require.Equal(t, sdk.Bonded, validator.Status()) - assert.Equal(t, bondAmount, validator.DelegatorShares.EvaluateInt()) - assert.Equal(t, bondAmount, validator.PoolShares.Bonded().EvaluateInt(), "validator: %v", validator) + assert.Equal(t, bondAmount, validator.DelegatorShares.Evaluate()) + assert.Equal(t, bondAmount, validator.PoolShares.Bonded().Evaluate(), "validator: %v", validator) _, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr) require.True(t, found) - assert.Equal(t, bondAmount, bond.Shares.EvaluateInt()) + assert.Equal(t, bondAmount, bond.Shares.Evaluate()) pool := keeper.GetPool(ctx) exRate := validator.DelegatorShareExRate(pool) require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate) - assert.Equal(t, bondAmount, pool.BondedShares.EvaluateInt()) + assert.Equal(t, bondAmount, pool.BondedShares.Evaluate()) assert.Equal(t, bondAmount, pool.BondedTokens) // just send the same msgbond multiple times @@ -180,14 +191,14 @@ func TestIncrementsMsgDelegate(t *testing.T) { exRate := validator.DelegatorShareExRate(pool) require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i) - expBond := sdk.NewInt(int64(i + 1)).Mul(bondAmount) - expDelegatorShares := sdk.NewInt(int64(i + 2)).Mul(bondAmount) // (1 self delegation) - expDelegatorAcc := initBond.Sub(expBond) + expBond := int64(i+1) * bondAmount + expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation) + expDelegatorAcc := sdk.NewInt(initBond - expBond) require.Equal(t, bond.Height, int64(i), "Incorrect bond height") - gotBond := bond.Shares.EvaluateInt() - gotDelegatorShares := validator.DelegatorShares.EvaluateInt() + gotBond := bond.Shares.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -203,14 +214,14 @@ func TestIncrementsMsgDelegate(t *testing.T) { } func TestIncrementsMsgUnbond(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) // create validator, delegate - validatorAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -220,16 +231,19 @@ func TestIncrementsMsgUnbond(t *testing.T) { validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, initBond.MulRaw(2), validator.DelegatorShares.EvaluateInt()) - assert.Equal(t, initBond.MulRaw(2), validator.PoolShares.Bonded().EvaluateInt()) + assert.Equal(t, initBond*2, validator.DelegatorShares.Evaluate()) + assert.Equal(t, initBond*2, validator.PoolShares.Bonded().Evaluate()) // just send the same msgUnbond multiple times // TODO use decimals here - unbondShares, unbondSharesStr := int64(10), "10" - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) + unbondShares := sdk.NewRat(10) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { - got := handleMsgUnbond(ctx, msgUnbond, keeper) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, 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 @@ -238,12 +252,12 @@ func TestIncrementsMsgUnbond(t *testing.T) { bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) - expBond := initBond.SubRaw(int64(i+1) * unbondShares) - expDelegatorShares := initBond.MulRaw(2).SubRaw(int64(i+1) * unbondShares) - expDelegatorAcc := initBond.Sub(expBond) + expBond := initBond - int64(i+1)*unbondShares.Evaluate() + expDelegatorShares := 2*initBond - int64(i+1)*unbondShares.Evaluate() + expDelegatorAcc := sdk.NewInt(initBond - expBond) - gotBond := bond.Shares.EvaluateInt() - gotDelegatorShares := validator.DelegatorShares.EvaluateInt() + gotBond := bond.Shares.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, @@ -263,41 +277,42 @@ func TestIncrementsMsgUnbond(t *testing.T) { //1<<63 + 1, // more than int64 1<<63 - 1, 1 << 31, - initBond.Int64(), + initBond, } for _, c := range errorCases { - unbondShares := strconv.Itoa(int(c)) - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondShares) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares := sdk.NewRat(int64(c)) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail") } - leftBonded := initBond.SubRaw(unbondShares * int64(numUnbonds)) + leftBonded := initBond - int64(numUnbonds)*unbondShares.Evaluate() // should be unable to unbond one more than we have - unbondSharesStr = strconv.Itoa(int(leftBonded.Int64()) + 1) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares = sdk.NewRat(leftBonded + 1) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) assert.False(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares.String(), leftBonded) // should be able to unbond just what we have - unbondSharesStr = strconv.Itoa(int(leftBonded.Int64())) - msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) - got = handleMsgUnbond(ctx, msgUnbond, keeper) + unbondShares = sdk.NewRat(leftBonded) + msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) assert.True(t, got.IsOK(), - "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded) } func TestMultipleMsgCreateValidator(t *testing.T) { - initBond := sdk.NewInt(1000) - ctx, accMapper, keeper := createTestInput(t, false, initBond) - params := keeper.GetParams(ctx) - validatorAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + initBond := int64(1000) + ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond) + params := setInstantUnbondPeriod(keeper, ctx) + + validatorAddrs := []sdk.Address{keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]} // bond them all for i, validatorAddr := range validatorAddrs { - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[i], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[i], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -305,7 +320,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) { validators := keeper.GetValidators(ctx, 100) require.Equal(t, (i + 1), len(validators)) val := validators[i] - balanceExpd := initBond.SubRaw(10) + balanceExpd := sdk.NewInt(initBond - 10) balanceGot := accMapper.GetAccount(ctx, val.Owner).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, 10, int(val.DelegatorShares.Evaluate()), "expected %d shares, got %d", 10, val.DelegatorShares) @@ -316,8 +331,11 @@ func TestMultipleMsgCreateValidator(t *testing.T) { for i, validatorAddr := range validatorAddrs { validatorPre, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "10") // self-delegation - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) // self-delegation + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -328,24 +346,25 @@ func TestMultipleMsgCreateValidator(t *testing.T) { _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) - expBalance := initBond + expBalance := sdk.NewInt(initBond) gotBalance := accMapper.GetAccount(ctx, validatorPre.Owner).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 := createTestInput(t, false, sdk.NewInt(1000)) - validatorAddr, delegatorAddrs := addrs[0], addrs[1:] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddrs := keep.Addrs[0], keep.Addrs[1:] + _ = setInstantUnbondPeriod(keeper, ctx) //first make a validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) 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.NewInt(10)) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -357,8 +376,11 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, "10") - got := handleMsgUnbond(ctx, msgUnbond, keeper) + msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded @@ -368,37 +390,173 @@ func TestMultipleMsgDelegate(t *testing.T) { } func TestRevokeValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000)) - validatorAddr, delegatorAddr := addrs[0], addrs[1] + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1] + _ = setInstantUnbondPeriod(keeper, ctx) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10)) + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewInt(10)) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) + validator, _ := keeper.GetValidator(ctx, validatorAddr) + // unbond the validators bond portion - msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondValidator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + msgBeginUnbondingValidator := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingValidator := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingValidator, keeper) + require.True(t, got.IsOK(), "expected no error") + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.True(t, validator.Revoked) + require.True(t, validator.Revoked, "%v", validator) // test that this address cannot yet be bonded too because is revoked got = handleMsgDelegate(ctx, msgDelegate, keeper) assert.False(t, got.IsOK(), "expected error, got %v", got) // test that the delegator can still withdraw their bonds - msgUnbondDelegator := NewMsgUnbond(delegatorAddr, validatorAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper) - require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10)) + msgCompleteUnbondingDelegator := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingDelegator, keeper) + require.True(t, got.IsOK(), "expected no error") // verify that the pubkey can now be reused got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) assert.True(t, got.IsOK(), "expected ok, got %v", got) } + +func TestUnbondingPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := keep.Addrs[0] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validator + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin unbonding + msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) + got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") + + // cannot complete unbonding at same time + msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // cannot complete unbonding at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, !got.IsOK(), "expected no error") + + // can complete unbonding at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestRedelegationPeriod(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2 := keep.Addrs[0], keep.Addrs[1] + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 7 + keeper.SetParams(ctx, params) + + // create the validators + msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot complete redelegation at same time + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // cannot complete redelegation at time 6 seconds later + origHeader := ctx.BlockHeader() + headerTime6 := origHeader + headerTime6.Time += 6 + ctx = ctx.WithBlockHeader(headerTime6) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error") + + // can complete redelegation at time 7 seconds later + headerTime7 := origHeader + headerTime7.Time += 7 + ctx = ctx.WithBlockHeader(headerTime7) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + +func TestTransitiveRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], 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], 10) + got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10)) + 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(validatorAddr, validatorAddr2, validatorAddr3, sdk.NewRat(10)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // complete first redelegation + msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2) + got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") + + // 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") +} diff --git a/x/stake/keeper.go b/x/stake/keeper.go deleted file mode 100644 index dd7a44663648..000000000000 --- a/x/stake/keeper.go +++ /dev/null @@ -1,859 +0,0 @@ -package stake - -import ( - "bytes" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/bank" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" -) - -// keeper of the staking store -type Keeper struct { - storeKey sdk.StoreKey - cdc *wire.Codec - coinKeeper bank.Keeper - - // codespace - codespace sdk.CodespaceType -} - -func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { - keeper := Keeper{ - storeKey: key, - cdc: cdc, - coinKeeper: ck, - codespace: codespace, - } - return keeper -} - -//_________________________________________________________________________ - -// get a single validator -func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Validator, found bool) { - store := ctx.KVStore(k.storeKey) - return k.getValidator(store, addr) -} - -// get a single validator by pubkey -func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) { - store := ctx.KVStore(k.storeKey) - addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) - if addr == nil { - return validator, false - } - return k.getValidator(store, addr) -} - -// get a single validator (reuse store) -func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) { - b := store.Get(GetValidatorKey(addr)) - if b == nil { - return validator, false - } - k.cdc.MustUnmarshalBinary(b, &validator) - return validator, true -} - -// set the main record holding validator details -func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { - store := ctx.KVStore(k.storeKey) - // set main store - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bz) -} - -func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) { - store := ctx.KVStore(k.storeKey) - // set pointer by pubkey - store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) -} - -func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) { - store := ctx.KVStore(k.storeKey) - store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner) -} - -// used in testing -func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { - store := ctx.KVStore(k.storeKey) - return store.Get(power) != nil -} - -// Get the set of all validators with no limits, used during genesis dump -func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators = append(validators, validator) - iterator.Next() - } - return validators -} - -// Get the set of all validators, retrieve a maxRetrieve number of records -func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators Validators) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - - validators = make([]Validator, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - validators[i] = validator - iterator.Next() - } - return validators[:i] // trim -} - -//___________________________________________________________________________ - -// get the group of the bonded validators -func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []Validator) { - store := ctx.KVStore(k.storeKey) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - validators = make([]Validator, maxValidators) - - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := 0 - for ; iterator.Valid(); iterator.Next() { - - // sanity check - if i > int(maxValidators-1) { - panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - validators[i] = validator - i++ - } - iterator.Close() - return validators[:i] // trim -} - -// get the group of bonded validators sorted by power-rank -func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []Validator { - store := ctx.KVStore(k.storeKey) - maxValidators := k.GetParams(ctx).MaxValidators - validators := make([]Validator, maxValidators) - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - i := 0 - for { - if !iterator.Valid() || i > int(maxValidators-1) { - iterator.Close() - break - } - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - // Reached to revoked validators, stop iterating - if validator.Revoked { - iterator.Close() - break - } - if validator.Status() == sdk.Bonded { - validators[i] = validator - i++ - } - iterator.Next() - } - return validators[:i] // trim -} - -//_________________________________________________________________________ -// Accumulated updates to the active/bonded validator set for tendermint - -// get the most recently updated validators -func (k Keeper) getTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { - store := ctx.KVStore(k.storeKey) - - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest - for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val abci.Validator - k.cdc.MustUnmarshalBinary(valBytes, &val) - updates = append(updates, val) - } - iterator.Close() - return -} - -// remove all validator update entries after applied to Tendermint -func (k Keeper) clearTendermintUpdates(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - - // delete subspace - iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) - for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) - } - iterator.Close() -} - -//___________________________________________________________________________ - -// perfom all the nessisary steps for when a validator changes its power -// updates all validator stores as well as tendermint update store -// may kick out validators if new validator is entering the bonded validator group -func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator { - store := ctx.KVStore(k.storeKey) - pool := k.getPool(store) - ownerAddr := validator.Owner - - // always update the main list ordered by owner address before exiting - defer func() { - bz := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(ownerAddr), bz) - }() - - // retrieve the old validator record - oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) - - if validator.Revoked && oldValidator.Status() == sdk.Bonded { - validator = k.unbondValidator(ctx, store, validator) - - // need to also clear the cliff validator spot because the revoke has - // opened up a new spot which will be filled when - // updateValidatorsBonded is called - k.clearCliffValidator(ctx) - } - - powerIncreasing := false - if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { - powerIncreasing = true - } - - // if already a validator, copy the old block height and counter, else set them - if oldFound && oldValidator.Status() == sdk.Bonded { - validator.BondHeight = oldValidator.BondHeight - validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter - } else { - validator.BondHeight = ctx.BlockHeight() - counter := k.getIntraTxCounter(ctx) - validator.BondIntraTxCounter = counter - k.setIntraTxCounter(ctx, counter+1) - } - - // update the list ordered by voting power - if oldFound { - store.Delete(GetValidatorsByPowerKey(oldValidator, pool)) - } - valPower := GetValidatorsByPowerKey(validator, pool) - store.Set(valPower, validator.Owner) - - // efficiency case: - // if already bonded and power increasing only need to update tendermint - if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { - bz := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(ownerAddr), bz) - return validator - } - - // efficiency case: - // if was unbonded/or is a new validator - and the new power is less than the cliff validator - cliffPower := k.getCliffValidatorPower(ctx) - if cliffPower != nil && - (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && - bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower - return validator - } - - // update the validator set for this validator - updatedVal := k.updateBondedValidators(ctx, store, validator) - if updatedVal.Owner != nil { // updates to validator occurred to be updated - validator = updatedVal - } - return validator -} - -// Update the validator group and kick out any old validators. In addition this -// function adds (or doesn't add) a validator which has updated its bonded -// tokens to the validator group. -> this validator is specified through the -// updatedValidatorAddr term. -// -// The correct subset is retrieved by iterating through an index of the -// validators sorted by power, stored using the ValidatorsByPowerKey. -// Simultaneously the current validator records are updated in store with the -// ValidatorsBondedKey. This store is used to determine if a validator is a -// validator without needing to iterate over the subspace as we do in -// GetValidators. -// -// Optionally also return the validator from a retrieve address if the validator has been bonded -func (k Keeper) updateBondedValidators(ctx sdk.Context, store sdk.KVStore, - newValidator Validator) (updatedVal Validator) { - - kickCliffValidator := false - oldCliffValidatorAddr := k.getCliffValidator(ctx) - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - // TODO benchmark if we should read the current power and not write if it's the same - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, or under the - // situation that this is the "new validator" just use the validator - // provided because it has not yet been updated in the main validator - // store - ownerAddr := iterator.Value() - if bytes.Equal(ownerAddr, newValidator.Owner) { - validator = newValidator - } else { - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - } - - // if not previously a validator (and unrevoked), - // kick the cliff validator / bond this new validator - if validator.Status() != sdk.Bonded && !validator.Revoked { - kickCliffValidator = true - - validator = k.bondValidator(ctx, store, validator) - if bytes.Equal(ownerAddr, newValidator.Owner) { - updatedVal = validator - } - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - if oldCliffValidatorAddr != nil && kickCliffValidator { - validator, found := k.getValidator(store, oldCliffValidatorAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) - } - k.unbondValidator(ctx, store, validator) - } - - return -} - -// full update of the bonded validator set, many can be added/kicked -func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) { - // clear the current validators store, add to the ToKickOut temp store - toKickOut := make(map[string]byte) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - for ; iterator.Valid(); iterator.Next() { - ownerAddr := iterator.Value() - toKickOut[string(ownerAddr)] = 0 // set anything - } - iterator.Close() - - // add the actual validator power sorted store - maxValidators := k.GetParams(ctx).MaxValidators - iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest - bondedValidatorsCount := 0 - var validator Validator - for { - if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { - - if bondedValidatorsCount == int(maxValidators) { // is cliff validator - k.setCliffValidator(ctx, validator, k.GetPool(ctx)) - } - iterator.Close() - break - } - - // either retrieve the original validator from the store, - // or under the situation that this is the "new validator" just - // use the validator provided because it has not yet been updated - // in the main validator store - ownerAddr := iterator.Value() - var found bool - validator, found = k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - - _, found = toKickOut[string(ownerAddr)] - if found { - delete(toKickOut, string(ownerAddr)) - } else { - - // if it wasn't in the toKickOut group it means - // this wasn't a previously a validator, therefor - // update the validator to enter the validator group - validator = k.bondValidator(ctx, store, validator) - } - - if validator.Revoked && validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) - } else { - bondedValidatorsCount++ - } - - iterator.Next() - } - - // perform the actual kicks - for key := range toKickOut { - ownerAddr := []byte(key) - validator, found := k.getValidator(store, ownerAddr) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) - } - k.unbondValidator(ctx, store, validator) - } - return -} - -// perform all the store operations for when a validator status becomes unbonded -func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Unbonded { - panic(fmt.Sprintf("should not already be be unbonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) - k.setPool(ctx, pool) - - // save the now unbonded validator record - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - // also remove from the Bonded Validators Store - store.Delete(GetValidatorsBondedKey(validator.PubKey)) - return validator -} - -// perform all the store operations for when a validator status becomes bonded -func (k Keeper) bondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator { - pool := k.GetPool(ctx) - - // sanity check - if validator.Status() == sdk.Bonded { - panic(fmt.Sprintf("should not already be be bonded, validator: %v\n", validator)) - } - - // set the status - validator, pool = validator.UpdateStatus(pool, sdk.Bonded) - k.setPool(ctx, pool) - - // save the now bonded validator record to the three referenced stores - bzVal := k.cdc.MustMarshalBinary(validator) - store.Set(GetValidatorKey(validator.Owner), bzVal) - store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner) - - // add to accumulated changes for tendermint - bzABCI := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc)) - store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) - - return validator -} - -func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { - - // first retrieve the old validator record - validator, found := k.GetValidator(ctx, address) - if !found { - return - } - - // delete the old validator record - store := ctx.KVStore(k.storeKey) - pool := k.getPool(store) - store.Delete(GetValidatorKey(address)) - store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) - store.Delete(GetValidatorsByPowerKey(validator, pool)) - - // delete from the current and power weighted validator groups if the validator - // is bonded - and add validator with zero power to the validator updates - if store.Get(GetValidatorsBondedKey(validator.PubKey)) == nil { - return - } - store.Delete(GetValidatorsBondedKey(validator.PubKey)) - - bz := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc)) - store.Set(GetTendermintUpdatesKey(address), bz) -} - -//_____________________________________________________________________ - -// load a delegator bond -func (k Keeper) GetDelegation(ctx sdk.Context, - delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) { - - store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) - if delegatorBytes == nil { - return bond, false - } - - k.cdc.MustUnmarshalBinary(delegatorBytes, &bond) - return bond, true -} - -// load all delegations used during genesis dump -func (k Keeper) getAllDelegations(ctx sdk.Context) (delegations []Delegation) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, DelegationKey) - - i := 0 - for ; ; i++ { - if !iterator.Valid() { - iterator.Close() - break - } - bondBytes := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &delegation) - delegations = append(delegations, delegation) - iterator.Next() - } - return delegations[:i] // trim -} - -// load all bonds of a delegator -func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest - - bonds = make([]Delegation, maxRetrieve) - i := 0 - for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { - iterator.Close() - break - } - bondBytes := iterator.Value() - var bond Delegation - k.cdc.MustUnmarshalBinary(bondBytes, &bond) - bonds[i] = bond - iterator.Next() - } - return bonds[:i] // trim -} - -func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(bond) - store.Set(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc), b) -} - -func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) { - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc)) -} - -//_______________________________________________________________________ - -// load/save the global staking params -func (k Keeper) GetParams(ctx sdk.Context) Params { - store := ctx.KVStore(k.storeKey) - return k.getParams(store) -} -func (k Keeper) getParams(store sdk.KVStore) (params Params) { - b := store.Get(ParamKey) - if b == nil { - panic("Stored params should not have been nil") - } - - k.cdc.MustUnmarshalBinary(b, ¶ms) - return -} - -// Need a distinct function because setParams depends on an existing previous -// record of params to exist (to check if maxValidators has changed) - and we -// panic on retrieval if it doesn't exist - hence if we use setParams for the very -// first params set it will panic. -func (k Keeper) setNewParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -// Public version of setNewParams -func (k Keeper) SetNewParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -func (k Keeper) setParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(k.storeKey) - exParams := k.getParams(store) - - // if max validator count changes, must recalculate validator set - if exParams.MaxValidators != params.MaxValidators { - k.updateBondedValidatorsFull(ctx, store) - } - b := k.cdc.MustMarshalBinary(params) - store.Set(ParamKey, b) -} - -//_______________________________________________________________________ - -// load/save the pool -func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { - store := ctx.KVStore(k.storeKey) - return k.getPool(store) -} -func (k Keeper) getPool(store sdk.KVStore) (pool Pool) { - b := store.Get(PoolKey) - if b == nil { - panic("Stored pool should not have been nil") - } - k.cdc.MustUnmarshalBinary(b, &pool) - return -} - -func (k Keeper) setPool(ctx sdk.Context, pool Pool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) - store.Set(PoolKey, b) -} - -// Public version of setpool -func (k Keeper) SetPool(ctx sdk.Context, pool Pool) { - store := ctx.KVStore(k.storeKey) - b := k.cdc.MustMarshalBinary(pool) - store.Set(PoolKey, b) -} - -//__________________________________________________________________________ - -// get the current in-block validator operation counter -func (k Keeper) getIntraTxCounter(ctx sdk.Context) int16 { - store := ctx.KVStore(k.storeKey) - b := store.Get(IntraTxCounterKey) - if b == nil { - return 0 - } - var counter int16 - k.cdc.MustUnmarshalBinary(b, &counter) - return counter -} - -// set the current in-block validator operation counter -func (k Keeper) setIntraTxCounter(ctx sdk.Context, counter int16) { - store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinary(counter) - store.Set(IntraTxCounterKey, bz) -} - -//__________________________________________________________________________ - -// get the current validator on the cliff -func (k Keeper) getCliffValidator(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorCliffKey) -} - -// get the current power of the validator on the cliff -func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte { - store := ctx.KVStore(k.storeKey) - return store.Get(ValidatorPowerCliffKey) -} - -// set the current validator and power of the validator on the cliff -func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Pool) { - store := ctx.KVStore(k.storeKey) - bz := GetValidatorsByPowerKey(validator, pool) - store.Set(ValidatorPowerCliffKey, bz) - store.Set(ValidatorCliffKey, validator.Owner) -} - -// clear the current validator and power of the validator on the cliff -func (k Keeper) clearCliffValidator(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(ValidatorPowerCliffKey) - store.Delete(ValidatorCliffKey) -} - -//__________________________________________________________________________ - -// Implements ValidatorSet - -var _ sdk.ValidatorSet = Keeper{} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var validator Validator - k.cdc.MustUnmarshalBinary(bz, &validator) - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { - store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - address := iterator.Value() - validator, found := k.getValidator(store, address) - if !found { - panic(fmt.Sprintf("validator record not found for address: %v\n", address)) - } - - stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// get the sdk.validator for a particular address -func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator { - val, found := k.GetValidator(ctx, addr) - if !found { - return nil - } - return val -} - -// total power from the bond -func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { - pool := k.GetPool(ctx) - return pool.BondedShares -} - -//__________________________________________________________________________ - -// Implements DelegationSet - -var _ sdk.ValidatorSet = Keeper{} - -// get the delegation for a particular set of delegator and validator addresses -func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { - bond, ok := k.GetDelegation(ctx, addrDel, addrVal) - if !ok { - return nil - } - return bond -} - -// Returns self as it is both a validatorset and delegationset -func (k Keeper) GetValidatorSet() sdk.ValidatorSet { - return k -} - -// iterate through the active validator set and perform the provided function -func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { - store := ctx.KVStore(k.storeKey) - key := GetDelegationsKey(delAddr, k.cdc) - iterator := sdk.KVStorePrefixIterator(store, key) - i := int64(0) - for ; iterator.Valid(); iterator.Next() { - bz := iterator.Value() - var delegation Delegation - k.cdc.MustUnmarshalBinary(bz, &delegation) - stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? - if stop { - break - } - i++ - } - iterator.Close() -} - -// slash a validator -func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) { - // TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957 - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("attempted to slash a nonexistent validator with address %s", pubkey.Address())) - } - sharesToRemove := val.PoolShares.Amount.Mul(fraction) - pool := k.GetPool(ctx) - val, pool, burned := val.removePoolShares(pool, sharesToRemove) - k.setPool(ctx, pool) // update the pool - k.updateValidator(ctx, val) // update the validator, possibly kicking it out - logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %v tokens", pubkey.Address(), fraction, sharesToRemove, burned)) - return -} - -// revoke a validator -func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("validator with pubkey %s not found, cannot revoke", pubkey)) - } - val.Revoked = true - k.updateValidator(ctx, val) // update the validator, now revoked - logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) - return -} - -// unrevoke a validator -func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { - logger := ctx.Logger().With("module", "x/stake") - val, found := k.GetValidatorByPubKey(ctx, pubkey) - if !found { - panic(fmt.Errorf("validator with pubkey %s not found, cannot unrevoke", pubkey)) - } - val.Revoked = false - k.updateValidator(ctx, val) // update the validator, now unrevoked - logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) - return -} diff --git a/x/stake/store.md b/x/stake/keeper/_store.md similarity index 100% rename from x/stake/store.md rename to x/stake/keeper/_store.md diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go new file mode 100644 index 000000000000..e9155d7f9870 --- /dev/null +++ b/x/stake/keeper/delegation.go @@ -0,0 +1,356 @@ +package keeper + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// load a delegation +func (k Keeper) GetDelegation(ctx sdk.Context, + delegatorAddr, validatorAddr sdk.Address) (delegation types.Delegation, found bool) { + + store := ctx.KVStore(k.storeKey) + delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) + if delegatorBytes == nil { + return delegation, false + } + + k.cdc.MustUnmarshalBinary(delegatorBytes, &delegation) + return delegation, true +} + +// load all delegations used during genesis dump +func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + bondBytes := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegations = append(delegations, delegation) + iterator.Next() + } + iterator.Close() + return delegations +} + +// load all delegations for a delegator +func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, + maxRetrieve int16) (delegations []types.Delegation) { + + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest + + delegations = make([]types.Delegation, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + bondBytes := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegations[i] = delegation + iterator.Next() + } + iterator.Close() + return delegations[:i] // trim +} + +// set the delegation +func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(delegation) + store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc), b) +} + +// remove the delegation +func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// load a unbonding delegation +func (k Keeper) GetUnbondingDelegation(ctx sdk.Context, + DelegatorAddr, ValidatorAddr sdk.Address) (ubd types.UnbondingDelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr, k.cdc) + bz := store.Get(ubdKey) + if bz == nil { + return ubd, false + } + + k.cdc.MustUnmarshalBinary(bz, &ubd) + return ubd, true +} + +// set the unbonding delegation and associated index +func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(ubd) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + store.Set(ubdKey, bz) + store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc), ubdKey) +} + +// remove the unbonding delegation object and associated index +func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { + store := ctx.KVStore(k.storeKey) + ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc) + store.Delete(ubdKey) + store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// load a redelegation +func (k Keeper) GetRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.Address) (red types.Redelegation, found bool) { + + store := ctx.KVStore(k.storeKey) + redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr, k.cdc) + bz := store.Get(redKey) + if bz == nil { + return red, false + } + + k.cdc.MustUnmarshalBinary(bz, &red) + return red, true +} + +// has a redelegation +func (k Keeper) HasReceivingRedelegation(ctx sdk.Context, + DelegatorAddr, ValidatorDstAddr sdk.Address) bool { + + store := ctx.KVStore(k.storeKey) + prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest + + found := false + if iterator.Valid() { + //record found + found = true + } + iterator.Close() + return found +} + +// set a redelegation and associated index +func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(red) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + store.Set(redKey, bz) + store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) + store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey) +} + +// remove a redelegation object and associated index +func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) { + store := ctx.KVStore(k.storeKey) + redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc) + store.Delete(redKey) + store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) + store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)) +} + +//_____________________________________________________________________________________ + +// Perform a delegation, set/update everything necessary within the store +func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.Address, bondAmt sdk.Coin, + validator types.Validator) (newShares sdk.Rat, err sdk.Error) { + + // Get or create the delegator delegation + delegation, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) + if !found { + delegation = types.Delegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validator.Owner, + Shares: sdk.ZeroRat(), + } + } + + // Account new shares, save + pool := k.GetPool(ctx) + _, _, err = k.coinKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt}) + if err != nil { + return + } + validator, pool, newShares = validator.AddTokensFromDel(pool, bondAmt.Amount.Int64()) + delegation.Shares = delegation.Shares.Add(newShares) + + // Update delegation height + delegation.Height = ctx.BlockHeight() + + k.SetPool(ctx, pool) + k.SetDelegation(ctx, delegation) + k.UpdateValidator(ctx, validator) + + return +} + +// unbond the the delegation return +func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address, + shares sdk.Rat) (amount int64, err sdk.Error) { + + // check if delegation has any shares in it unbond + delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + err = types.ErrNoDelegatorForAddress(k.Codespace()) + return + } + + // retrieve the amount to remove + if delegation.Shares.LT(shares) { + err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) + return + } + + // get validator + validator, found := k.GetValidator(ctx, validatorAddr) + if !found { + err = types.ErrNoValidatorFound(k.Codespace()) + return + } + + // subtract shares from delegator + delegation.Shares = delegation.Shares.Sub(shares) + + // remove the delegation + if delegation.Shares.IsZero() { + + // if the delegation is the owner of the validator then + // trigger a revoke validator + if bytes.Equal(delegation.DelegatorAddr, validator.Owner) && validator.Revoked == false { + validator.Revoked = true + } + k.RemoveDelegation(ctx, delegation) + } else { + // Update height + delegation.Height = ctx.BlockHeight() + k.SetDelegation(ctx, delegation) + } + + // remove the coins from the validator + pool := k.GetPool(ctx) + validator, pool, amount = validator.RemoveDelShares(pool, shares) + + k.SetPool(ctx, pool) + + // update then remove validator if necessary + validator = k.UpdateValidator(ctx, validator) + if validator.DelegatorShares.IsZero() { + k.RemoveValidator(ctx, validator.Owner) + } + + return amount, nil +} + +//______________________________________________________________________________________________________ + +// complete unbonding an unbonding record +func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error { + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorAddr, sharesAmount) + if err != nil { + return err + } + + // create the unbonding delegation + params := k.GetParams(ctx) + minTime := ctx.BlockHeader().Time + params.UnbondingTime + + ubd := types.UnbondingDelegation{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + MinTime: minTime, + Balance: sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}, + } + k.SetUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address) sdk.Error { + + ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + if !found { + return types.ErrNoUnbondingDelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if ubd.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "unbonding", "unit-time", ubd.MinTime, ctxTime) + } + + k.coinKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance}) + k.RemoveUnbondingDelegation(ctx, ubd) + return nil +} + +// complete unbonding an unbonding record +func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error { + + // check if this is a transitive redelegation + if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) { + return types.ErrTransitiveRedelegation(k.Codespace()) + } + + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount) + if err != nil { + return err + } + + params := k.GetParams(ctx) + returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)} + dstValidator, found := k.GetValidator(ctx, validatorDstAddr) + if !found { + return types.ErrBadRedelegationDst(k.Codespace()) + } + sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator) + + // create the unbonding delegation + minTime := ctx.BlockHeader().Time + params.UnbondingTime + + red := types.Redelegation{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + MinTime: minTime, + SharesDst: sharesCreated, + SharesSrc: sharesAmount, + } + k.SetRedelegation(ctx, red) + return nil +} + +// complete unbonding an ongoing redelegation +func (k Keeper) CompleteRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) sdk.Error { + + red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr) + if !found { + return types.ErrNoRedelegation(k.Codespace()) + } + + // ensure that enough time has passed + ctxTime := ctx.BlockHeader().Time + if red.MinTime > ctxTime { + return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime) + } + + k.RemoveRedelegation(ctx, red) + return nil +} diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go new file mode 100644 index 000000000000..d7c1ac984956 --- /dev/null +++ b/x/stake/keeper/delegation_test.go @@ -0,0 +1,223 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// tests GetDelegation, GetDelegations, SetDelegation, RemoveDelegation, GetDelegations +func TestDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + //construct the validators + amts := []int64{9, 8, 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] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + // first add a validators[0] to delegate too + bond1to1 := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: sdk.NewRat(9), + } + + // check the empty keeper first + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.False(t, found) + + // set and retrieve a record + keeper.SetDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bond1to1.Equal(resBond)) + + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.SetDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, bond1to1.Equal(resBond)) + + // add some more records + bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} + bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} + bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} + bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} + bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewRat(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.GetDelegations(ctx, addrDels[0], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bond1to1.Equal(resBonds[0])) + assert.True(t, bond1to2.Equal(resBonds[1])) + assert.True(t, bond1to3.Equal(resBonds[2])) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bond2to1.Equal(resBonds[0])) + assert.True(t, bond2to2.Equal(resBonds[1])) + assert.True(t, bond2to3.Equal(resBonds[2])) + allBonds := keeper.GetAllDelegations(ctx) + require.Equal(t, 6, len(allBonds)) + assert.True(t, bond1to1.Equal(allBonds[0])) + assert.True(t, bond1to2.Equal(allBonds[1])) + assert.True(t, bond1to3.Equal(allBonds[2])) + assert.True(t, bond2to1.Equal(allBonds[3])) + assert.True(t, bond2to2.Equal(allBonds[4])) + assert.True(t, bond2to3.Equal(allBonds[5])) + + // delete a record + keeper.RemoveDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) + assert.False(t, found) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) + require.Equal(t, 2, len(resBonds)) + assert.True(t, bond2to1.Equal(resBonds[0])) + assert.True(t, bond2to2.Equal(resBonds[1])) + + // delete all the records from delegator 2 + keeper.RemoveDelegation(ctx, bond2to1) + keeper.RemoveDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) + assert.False(t, found) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) + assert.False(t, found) + resBonds = keeper.GetDelegations(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, 0) + + ubd := types.UnbondingDelegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + CreationHeight: 0, + MinTime: 0, + Balance: sdk.NewCoin("steak", 5), + } + + // set and retrieve a record + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, ubd.Equal(resBond)) + + // modify a records, save, and retrieve + ubd.Balance = sdk.NewCoin("steak", 21) + keeper.SetUnbondingDelegation(ctx, ubd) + resBond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.True(t, found) + assert.True(t, ubd.Equal(resBond)) + + // delete a record + keeper.RemoveUnbondingDelegation(ctx, ubd) + _, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0]) + assert.False(t, found) +} + +func TestUnbondDelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + pool.LooseTokens = 10 + + //create a validator and a delegator to that validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, issuedShares := validator.AddTokensFromDel(pool, 10) + require.Equal(t, int64(10), issuedShares.Evaluate()) + keeper.SetPool(ctx, pool) + validator = keeper.UpdateValidator(ctx, validator) + + pool = keeper.GetPool(ctx) + require.Equal(t, int64(10), pool.BondedTokens) + require.Equal(t, int64(10), validator.PoolShares.Bonded().Evaluate()) + + delegation := types.Delegation{ + DelegatorAddr: addrDels[0], + ValidatorAddr: addrVals[0], + Shares: issuedShares, + } + keeper.SetDelegation(ctx, delegation) + + var err error + var amount int64 + amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6)) + require.NoError(t, err) + assert.Equal(t, int64(6), 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) + + assert.Equal(t, int64(4), delegation.Shares.Evaluate()) + assert.Equal(t, int64(4), validator.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(6), pool.LooseTokens, "%v", pool) + assert.Equal(t, int64(4), pool.BondedTokens) +} + +// tests Get/Set/Remove/Has UnbondingDelegation +func TestRedelegation(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + + rd := types.Redelegation{ + DelegatorAddr: addrDels[0], + ValidatorSrcAddr: addrVals[0], + ValidatorDstAddr: addrVals[1], + CreationHeight: 0, + MinTime: 0, + SharesSrc: sdk.NewRat(5), + SharesDst: sdk.NewRat(5), + } + + // test shouldn't have and redelegations + has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + assert.False(t, has) + + // set and retrieve a record + keeper.SetRedelegation(ctx, rd) + resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.True(t, found) + assert.True(t, rd.Equal(resBond)) + + // check if has the redelegation + has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1]) + assert.True(t, has) + + // modify a records, save, and retrieve + rd.SharesSrc = sdk.NewRat(21) + rd.SharesDst = sdk.NewRat(21) + keeper.SetRedelegation(ctx, rd) + resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.True(t, found) + assert.True(t, rd.Equal(resBond)) + + // delete a record + keeper.RemoveRedelegation(ctx, rd) + _, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) + assert.False(t, found) +} diff --git a/x/stake/inflation.go b/x/stake/keeper/inflation.go similarity index 61% rename from x/stake/inflation.go rename to x/stake/keeper/inflation.go index b123dd2dda88..0574b8ecbf9c 100644 --- a/x/stake/inflation.go +++ b/x/stake/keeper/inflation.go @@ -1,35 +1,32 @@ -package stake +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) const ( hrsPerYr = 8766 // as defined by a julian year of 365.25 days - precision = 100000000000 // increased to this precision for accuracy with tests on tick_test.go + precision = 100000000000 // increased to this precision for accuracy ) -var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days +var hrsPerYrRat = sdk.NewRat(hrsPerYr) // process provisions for an hour period -func (k Keeper) processProvisions(ctx sdk.Context) Pool { +func (k Keeper) ProcessProvisions(ctx sdk.Context) types.Pool { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx) + pool.Inflation = k.NextInflation(ctx) - // Because the validators hold a relative bonded share (`GlobalStakeShare`), when - // more bonded tokens are added proportionally to all validators the only term - // which needs to be updated is the `BondedPool`. So for each previsions cycle: - - provisions := pool.Inflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat).EvaluateInt() + provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate() // TODO add to the fees provisions - pool.LooseUnbondedTokens = pool.LooseUnbondedTokens.Add(provisions) + pool.LooseTokens += provisions return pool } // get the next inflation rate for the hour -func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { +func (k Keeper) NextInflation(ctx sdk.Context) (inflation sdk.Rat) { params := k.GetParams(ctx) pool := k.GetPool(ctx) @@ -40,7 +37,7 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat().Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat().Sub(pool.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat) // increase the new annual inflation for this next cycle diff --git a/x/stake/inflation_test.go b/x/stake/keeper/inflation_test.go similarity index 68% rename from x/stake/inflation_test.go rename to x/stake/keeper/inflation_test.go index 8a117fc92bcf..cb2f6007a46e 100644 --- a/x/stake/inflation_test.go +++ b/x/stake/keeper/inflation_test.go @@ -1,65 +1,64 @@ -package stake +package keeper import ( "math/rand" "strconv" "testing" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) //changing the int in NewSource will allow you to test different, deterministic, sets of operations var r = rand.New(rand.NewSource(6595)) func TestGetInflation(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) params := keeper.GetParams(ctx) hrsPerYrRat := sdk.NewRat(hrsPerYr) // Governing Mechanism: - // bondedRatio = BondedTokens / TotalSupply - // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange - - zero := sdk.ZeroInt() - one := sdk.OneInt() + // BondedRatio = BondedTokens / TotalSupply + // inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange tests := []struct { name string - setBondedTokens, setLooseTokens sdk.Int + setBondedTokens, setLooseTokens int64 setInflation, expectedChange sdk.Rat }{ // with 0% bonded atom supply the inflation should increase by InflationRateChange - {"test 1", zero, zero, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, + {"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, // 100% bonded, starting at 20% inflation and being reduced // (1 - (1/0.67))*(0.13/8667) - {"test 2", one, zero, sdk.NewRat(20, 100), + {"test 2", 1, 0, sdk.NewRat(20, 100), sdk.OneRat().Sub(sdk.OneRat().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, // 50% bonded, starting at 10% inflation and being increased - {"test 3", one, one, sdk.NewRat(10, 100), + {"test 3", 1, 1, sdk.NewRat(10, 100), sdk.OneRat().Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, // test 7% minimum stop (testing with 100% bonded) - {"test 4", one, zero, sdk.NewRat(7, 100), sdk.ZeroRat()}, - {"test 5", one, zero, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, + {"test 4", 1, 0, sdk.NewRat(7, 100), sdk.ZeroRat()}, + {"test 5", 1, 0, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, // test 20% maximum stop (testing with 0% bonded) - {"test 6", zero, zero, sdk.NewRat(20, 100), sdk.ZeroRat()}, - {"test 7", zero, zero, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, + {"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat()}, + {"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, // perfect balance shouldn't change inflation - {"test 8", sdk.NewInt(67), sdk.NewInt(33), sdk.NewRat(15, 100), sdk.ZeroRat()}, + {"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()}, } for _, tc := range tests { - pool.BondedTokens, pool.LooseUnbondedTokens = tc.setBondedTokens, tc.setLooseTokens + pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens pool.Inflation = tc.setInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) - inflation := keeper.nextInflation(ctx) + inflation := keeper.NextInflation(ctx) diffInflation := inflation.Sub(tc.setInflation) assert.True(t, diffInflation.Equal(tc.expectedChange), @@ -69,17 +68,18 @@ func TestGetInflation(t *testing.T) { // Test that provisions are correctly added to the pool and validators each hour for 1 year func TestProcessProvisions(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( - initialTotalTokens int64 = 550000000 - initialBondedTokens int64 = 250000000 - initialUnbondedTokens int64 = 300000000 - cumulativeExpProvs sdk.Int = sdk.ZeroInt() - validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} - bondedValidators uint16 = 2 + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 250000000 + initialUnbondedTokens int64 = 300000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 2 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -89,7 +89,7 @@ func TestProcessProvisions(t *testing.T) { for hr := 0; hr < 8766; hr++ { pool := keeper.GetPool(ctx) _, expProvisions, _ := updateProvisions(t, keeper, pool, ctx, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) + cumulativeExpProvs = cumulativeExpProvs + expProvisions } //get the pool and do the final value checks from checkFinalPoolValues @@ -100,17 +100,18 @@ func TestProcessProvisions(t *testing.T) { // Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate // Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years) func TestHourlyInflationRateOfChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( - initialTotalTokens int64 = 550000000 - initialBondedTokens int64 = 150000000 - initialUnbondedTokens int64 = 400000000 - cumulativeExpProvs sdk.Int = sdk.ZeroInt() - validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} - bondedValidators uint16 = 1 + initialTotalTokens int64 = 550000000 + initialBondedTokens int64 = 150000000 + initialUnbondedTokens int64 = 400000000 + cumulativeExpProvs int64 + validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000} + bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens // create some validators some bonded, some unbonded _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) @@ -121,7 +122,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { pool := keeper.GetPool(ctx) previousInflation := pool.Inflation updatedInflation, expProvisions, pool := updateProvisions(t, keeper, pool, ctx, hr) - cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions) + cumulativeExpProvs = cumulativeExpProvs + expProvisions msg := strconv.Itoa(hr) checkInflation(t, pool, previousInflation, updatedInflation, msg) } @@ -133,7 +134,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) { //Test that a large unbonding will significantly lower the bonded ratio func TestLargeUnbond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -147,32 +148,33 @@ func TestLargeUnbond(t *testing.T) { validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000} bondedValidators uint16 = 7 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[0]) + validator, found := keeper.GetValidator(ctx, Addrs[0]) assert.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() // validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) bondedShares = bondedShares.Sub(bondSharesVal0) - val0UnbondedTokens = pool.unbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() - unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.unbondedShareExRate())) + val0UnbondedTokens = pool.UnbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate() + unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.UnbondedShareExRate())) // unbonded shares should increase assert.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1))) // Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75) - assert.True(t, (pool.bondedRatio().LT(initialBondedRatio))) + assert.True(t, (pool.BondedRatio().LT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -181,7 +183,7 @@ func TestLargeUnbond(t *testing.T) { //Test that a large bonding will significantly increase the bonded ratio func TestLargeBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) + ctx, _, keeper := CreateTestInput(t, false, 0) pool := keeper.GetPool(ctx) var ( @@ -193,24 +195,25 @@ func TestLargeBond(t *testing.T) { validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000} bondedValidators uint16 = 1 ) + pool.LooseTokens = initialTotalTokens _, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators) checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens) pool = keeper.GetPool(ctx) - validator, found := keeper.GetValidator(ctx, addrs[9]) + validator, found := keeper.GetValidator(ctx, Addrs[9]) assert.True(t, found) // initialBondedRatio that we can use to compare to the new values after the unbond - initialBondedRatio := pool.bondedRatio() + initialBondedRatio := pool.BondedRatio() - params := DefaultParams() + params := types.DefaultParams() params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) // validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens) - pool, validator, _, _ = OpBondOrUnbond(r, pool, validator) - keeper.setPool(ctx, pool) + pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator) + keeper.SetPool(ctx, pool) // process provisions after the bonding, to compare the difference in expProvisions and expInflation _, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0) @@ -219,7 +222,7 @@ func TestLargeBond(t *testing.T) { // unbonded shares should decrease assert.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1))) // Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%) - assert.True(t, (pool.bondedRatio().GT(initialBondedRatio))) + assert.True(t, (pool.BondedRatio().GT(initialBondedRatio))) // Final check that the pool equals initial values + provisions and adjustments we recorded pool = keeper.GetPool(ctx) @@ -228,20 +231,19 @@ func TestLargeBond(t *testing.T) { // Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators func TestInflationWithRandomOperations(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt()) - params := DefaultParams() - keeper.setParams(ctx, params) + ctx, _, keeper := CreateTestInput(t, false, 0) + params := types.DefaultParams() + keeper.SetParams(ctx, params) numValidators := 20 // start off by randomly setting up 20 validators - pool, validators := randomSetup(r, numValidators) + pool, validators := types.RandomSetup(r, numValidators) require.Equal(t, numValidators, len(validators)) for i := 0; i < len(validators); i++ { - keeper.setValidator(ctx, validators[i]) + keeper.SetValidator(ctx, validators[i]) } - - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Used to rotate validators so each random operation is applied to a different validator validatorCounter := 0 @@ -250,30 +252,30 @@ func TestInflationWithRandomOperations(t *testing.T) { for i := 0; i < numValidators; i++ { pool := keeper.GetPool(ctx) - // Get inflation before randomOperation, for comparison later + // Get inflation before RandomOperation, for comparison later previousInflation := pool.Inflation // Perform the random operation, and record how validators are modified - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, pool, validators[validatorCounter]) - validatorsMod := make([]Validator, len(validators)) + poolMod, validatorMod, tokens, msg := types.RandomOperation(r)(r, pool, validators[validatorCounter]) + validatorsMod := make([]types.Validator, len(validators)) copy(validatorsMod[:], validators[:]) require.Equal(t, numValidators, len(validators), "i %v", validatorCounter) require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter) validatorsMod[validatorCounter] = validatorMod - assertInvariants(t, msg, + types.AssertInvariants(t, msg, pool, validators, poolMod, validatorsMod, tokens) // set pool and validators after the random operation pool = poolMod - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) validators = validatorsMod // Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation - updatedInflation := keeper.nextInflation(ctx) + updatedInflation := keeper.NextInflation(ctx) pool.Inflation = updatedInflation - keeper.setPool(ctx, pool) + keeper.SetPool(ctx, pool) // Ensure inflation changes as expected when random operations are applied. checkInflation(t, pool, previousInflation, updatedInflation, msg) @@ -285,41 +287,43 @@ func TestInflationWithRandomOperations(t *testing.T) { ////////////////////////////////HELPER FUNCTIONS BELOW///////////////////////////////////// // Final check on the global pool values for what the total tokens accumulated from each hour of provisions -func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens int64, cumulativeExpProvs sdk.Int) { - calculatedTotalTokens := cumulativeExpProvs.AddRaw(initialTotalTokens) - - assert.Equal(t, calculatedTotalTokens.Int64(), pool.TokenSupply().Int64()) +func checkFinalPoolValues(t *testing.T, pool types.Pool, initialTotalTokens, cumulativeExpProvs int64) { + calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs + assert.Equal(t, calculatedTotalTokens, pool.TokenSupply()) } // Processes provisions are added to the pool correctly every hour // Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests -func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, hr int) (sdk.Rat, sdk.Int, Pool) { - expInflation := keeper.nextInflation(ctx) - expProvisions := (expInflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat)).EvaluateInt() +func updateProvisions(t *testing.T, keeper Keeper, pool types.Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, types.Pool) { + expInflation := keeper.NextInflation(ctx) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() startTotalSupply := pool.TokenSupply() - pool = keeper.processProvisions(ctx) - keeper.setPool(ctx, pool) + pool = keeper.ProcessProvisions(ctx) + keeper.SetPool(ctx, pool) //check provisions were added to pool - require.Equal(t, startTotalSupply.Add(expProvisions).Int64(), pool.TokenSupply().Int64()) + require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) + return expInflation, expProvisions, pool } // Deterministic setup of validators and pool // Allows you to decide how many validators to setup // Allows you to pick which validators are bonded by adjusting the MaxValidators of params -func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, maxValidators uint16) ([]Validator, Keeper, Pool) { - params := DefaultParams() +func setupTestValidators(pool types.Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, + maxValidators uint16) ([]types.Validator, Keeper, types.Pool) { + + params := types.DefaultParams() params.MaxValidators = maxValidators - keeper.setParams(ctx, params) + keeper.SetParams(ctx, params) numValidators := len(validatorTokens) - validators := make([]Validator, numValidators) + validators := make([]types.Validator, numValidators) for i := 0; i < numValidators; i++ { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i], pool, _ = validators[i].addTokensFromDel(pool, sdk.NewInt(validatorTokens[i])) - keeper.setPool(ctx, pool) - validators[i] = keeper.updateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, validatorTokens[i]) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order pool = keeper.GetPool(ctx) } @@ -327,28 +331,28 @@ func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTok } // Checks that the deterministic validator setup you wanted matches the values in the pool -func checkValidatorSetup(t *testing.T, pool Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { - assert.Equal(t, initialTotalTokens, pool.TokenSupply().Int64()) - assert.Equal(t, initialBondedTokens, pool.BondedTokens.Int64()) - assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens.Int64()) +func checkValidatorSetup(t *testing.T, pool types.Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) { + assert.Equal(t, initialTotalTokens, pool.TokenSupply(), "%v", pool) + assert.Equal(t, initialBondedTokens, pool.BondedTokens, "%v", pool) + assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens, "%v", pool) // test initial bonded ratio - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.bondedRatio()) + assert.True(t, pool.BondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.BondedRatio()) // test the value of validator shares - assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) + assert.True(t, pool.BondedShareExRate().Equal(sdk.OneRat()), "%v", pool.BondedShareExRate()) } // Checks that The inflation will correctly increase or decrease after an update to the pool -func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) { +func checkInflation(t *testing.T, pool types.Pool, previousInflation, updatedInflation sdk.Rat, msg string) { inflationChange := updatedInflation.Sub(previousInflation) switch { //BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)): assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg) //BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio - case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): + case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)): if previousInflation.Equal(sdk.NewRat(20, 100)) { assert.Equal(t, true, inflationChange.IsZero(), msg) @@ -358,11 +362,11 @@ func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation } //ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7% - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)): assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg) //ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%. - case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): + case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)): if previousInflation.Equal(sdk.NewRat(7, 100)) { assert.Equal(t, true, inflationChange.IsZero(), msg) diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go new file mode 100644 index 000000000000..187649c5f766 --- /dev/null +++ b/x/stake/keeper/keeper.go @@ -0,0 +1,122 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *wire.Codec + coinKeeper bank.Keeper + + // codespace + codespace sdk.CodespaceType +} + +func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + coinKeeper: ck, + codespace: codespace, + } + return keeper +} + +//_________________________________________________________________________ + +// return the codespace +func (k Keeper) Codespace() sdk.CodespaceType { + return k.codespace +} + +//_________________________________________________________________________ +// some generic reads/writes that don't need their own files + +// load/save the global staking params +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + store := ctx.KVStore(k.storeKey) + + b := store.Get(ParamKey) + if b == nil { + panic("Stored params should not have been nil") + } + + k.cdc.MustUnmarshalBinary(b, ¶ms) + return +} + +// Need a distinct function because setParams depends on an existing previous +// record of params to exist (to check if maxValidators has changed) - and we +// panic on retrieval if it doesn't exist - hence if we use setParams for the very +// first params set it will panic. +func (k Keeper) SetNewParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +// set the params +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + store := ctx.KVStore(k.storeKey) + exParams := k.GetParams(ctx) + + // if max validator count changes, must recalculate validator set + if exParams.MaxValidators != params.MaxValidators { + k.UpdateBondedValidatorsFull(ctx) + } + b := k.cdc.MustMarshalBinary(params) + store.Set(ParamKey, b) +} + +//_______________________________________________________________________ + +// load/save the pool +func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + panic("Stored pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(b, &pool) + return +} + +// set the pool +func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(pool) + store.Set(PoolKey, b) +} + +//__________________________________________________________________________ + +// get the current in-block validator operation counter +func (k Keeper) InitIntraTxCounter(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + if b == nil { + k.SetIntraTxCounter(ctx, 0) + } +} + +// get the current in-block validator operation counter +func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { + store := ctx.KVStore(k.storeKey) + b := store.Get(IntraTxCounterKey) + var counter int16 + k.cdc.MustUnmarshalBinary(b, &counter) + return counter +} + +// set the current in-block validator operation counter +func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(counter) + store.Set(IntraTxCounterKey, bz) +} diff --git a/x/stake/keeper/keeper_test.go b/x/stake/keeper/keeper_test.go new file mode 100644 index 000000000000..1be58728505b --- /dev/null +++ b/x/stake/keeper/keeper_test.go @@ -0,0 +1,39 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +func TestParams(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expParams := types.DefaultParams() + + //check that the empty keeper loads the default + resParams := keeper.GetParams(ctx) + assert.True(t, expParams.Equal(resParams)) + + //modify a params, save, and retrieve + expParams.MaxValidators = 777 + keeper.SetParams(ctx, expParams) + resParams = keeper.GetParams(ctx) + assert.True(t, expParams.Equal(resParams)) +} + +func TestPool(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + expPool := types.InitialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + assert.True(t, expPool.Equal(resPool)) + + //modify a params, save, and retrieve + expPool.BondedTokens = 777 + keeper.SetPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + assert.True(t, expPool.Equal(resPool)) +} diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go new file mode 100644 index 000000000000..87dc511d617a --- /dev/null +++ b/x/stake/keeper/key.go @@ -0,0 +1,197 @@ +package keeper + +import ( + "encoding/binary" + + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// TODO remove some of these prefixes once have working multistore + +//nolint +var ( + // Keys for store prefixes + ParamKey = []byte{0x00} // key for parameters relating to staking + PoolKey = []byte{0x01} // key for the staking pools + ValidatorsKey = []byte{0x02} // prefix for each key to a validator + ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey + ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators + ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power + ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator + ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff + TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated + IntraTxCounterKey = []byte{0x09} // key for intra-block tx index + DelegationKey = []byte{0x0A} // key for a delegation + UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner + RedelegationKey = []byte{0x0D} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by validator owner + RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by validator owner +) + +const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch + +// get the key for the validator with address +func GetValidatorKey(ownerAddr sdk.Address) []byte { + return append(ValidatorsKey, ownerAddr.Bytes()...) +} + +// get the key for the validator with pubkey +func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { + return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) +} + +// get the key for the current validator group, ordered like tendermint +func GetValidatorsBondedIndexKey(ownerAddr sdk.Address) []byte { + return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...) +} + +// get the power which is the key for the validator used in the power-store +func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte { + + // NOTE the address doesn't need to be stored because counter bytes must always be different + return GetValidatorPowerRank(validator, pool) +} + +// get the power of a validator +func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte { + + power := validator.EquivalentBondedShares(pool) + powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) + + // TODO ensure that the key will be a readable string.. probably should add seperators and have + revokedBytes := make([]byte, 1) + if validator.Revoked { + revokedBytes[0] = byte(0x01) + } else { + revokedBytes[0] = byte(0x00) + } + + // TODO ensure that the key will be a readable string.. probably should add seperators and have + // heightBytes and counterBytes represent strings like powerBytes does + heightBytes := make([]byte, binary.MaxVarintLen64) + binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) + counterBytes := make([]byte, 2) + binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) + + return append(ValidatorsByPowerIndexKey, + append(revokedBytes, + append(powerBytes, + append(heightBytes, counterBytes...)...)...)...) +} + +// get the key for the accumulated update validators +func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { + return append(TendermintUpdatesKey, ownerAddr.Bytes()...) +} + +//________________________________________________________________________________ + +// get the key for delegator bond with validator +func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +} + +// get the prefix for a delegator for all validators +func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(DelegationKey, res...) +} + +//________________________________________________________________________________ + +// get the key for an unbonding delegation +func GetUBDKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetUBDsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) +} + +// get the index-key for an unbonding delegation, stored by validator-index +func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetUBDsByValIndexKey(validatorAddr, cdc), delegatorAddr.Bytes()...) +} + +//______________ + +// get the prefix for all unbonding delegations from a delegator +func GetUBDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(UnbondingDelegationKey, res...) +} + +// get the prefix keyspace for the indexs of unbonding delegations for a validator +func GetUBDsByValIndexKey(validatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorAddr) + return append(UnbondingDelegationByValIndexKey, res...) +} + +//________________________________________________________________________________ + +// get the key for a redelegation +func GetREDKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsKey(delegatorAddr, cdc), + append( + validatorSrcAddr.Bytes(), + validatorDstAddr.Bytes()...)..., + ) +} + +// get the index-key for a redelegation, stored by source-validator-index +func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsFromValSrcIndexKey(validatorSrcAddr, cdc), + append( + delegatorAddr.Bytes(), + validatorDstAddr.Bytes()...)..., + ) +} + +// get the index-key for a redelegation, stored by destination-validator-index +func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsToValDstIndexKey(validatorDstAddr, cdc), + append( + delegatorAddr.Bytes(), + validatorSrcAddr.Bytes()...)..., + ) +} + +//______________ + +// get the prefix keyspace for redelegations from a delegator +func GetREDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&delegatorAddr) + return append(RedelegationKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating away from a source validator +func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorSrcAddr) + return append(RedelegationByValSrcIndexKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + res := cdc.MustMarshalBinary(&validatorDstAddr) + return append(RedelegationByValDstIndexKey, res...) +} + +// get the prefix keyspace for all redelegations redelegating towards a destination validator +// from a particular delegator +func GetREDsByDelToValDstIndexKey(delegatorAddr sdk.Address, + validatorDstAddr sdk.Address, cdc *wire.Codec) []byte { + + return append( + GetREDsToValDstIndexKey(validatorDstAddr, cdc), + delegatorAddr.Bytes()...) +} diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go new file mode 100644 index 000000000000..55ad658a2def --- /dev/null +++ b/x/stake/keeper/sdk_types.go @@ -0,0 +1,104 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// Implements ValidatorSet +var _ sdk.ValidatorSet = Keeper{} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} + +// get the sdk.validator for a particular address +func (k Keeper) Validator(ctx sdk.Context, address sdk.Address) sdk.Validator { + val, found := k.GetValidator(ctx, address) + if !found { + return nil + } + return val +} + +// total power from the bond +func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat { + pool := k.GetPool(ctx) + return pool.BondedShares +} + +//__________________________________________________________________________ + +// Implements DelegationSet + +var _ sdk.DelegationSet = Keeper{} + +// Returns self as it is both a validatorset and delegationset +func (k Keeper) GetValidatorSet() sdk.ValidatorSet { + return k +} + +// get the delegation for a particular set of delegator and validator addresses +func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation { + bond, ok := k.GetDelegation(ctx, addrDel, addrVal) + if !ok { + return nil + } + return bond +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + key := GetDelegationsKey(delAddr, k.cdc) + iterator := sdk.KVStorePrefixIterator(store, key) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + bz := iterator.Value() + var delegation types.Delegation + k.cdc.MustUnmarshalBinary(bz, &delegation) + stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to? + if stop { + break + } + i++ + } + iterator.Close() +} diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go new file mode 100644 index 000000000000..dda6530eabe9 --- /dev/null +++ b/x/stake/keeper/slash.go @@ -0,0 +1,59 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// NOTE the current slash functionality doesn't take into consideration unbonding/rebonding records +// or the time of breach. This will be updated in slashing v2 +// slash a validator +func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) { + + // TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957 + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address())) + } + sharesToRemove := validator.PoolShares.Amount.Mul(fraction) + pool := k.GetPool(ctx) + validator, pool, burned := validator.RemovePoolShares(pool, sharesToRemove) + k.SetPool(ctx, pool) // update the pool + k.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out + + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned)) + return +} + +// revoke a validator +func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) { + + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey)) + } + validator.Revoked = true + k.UpdateValidator(ctx, validator) // update the validator, now revoked + + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address())) + return +} + +// unrevoke a validator +func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) { + + validator, found := k.GetValidatorByPubKey(ctx, pubkey) + if !found { + panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey)) + } + validator.Revoked = false + k.UpdateValidator(ctx, validator) // update the validator, now unrevoked + + logger := ctx.Logger().With("module", "x/stake") + logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address())) + return +} diff --git a/x/stake/test_common.go b/x/stake/keeper/test_common.go similarity index 63% rename from x/stake/test_common.go rename to x/stake/keeper/test_common.go index 19e35c277f7e..eea71c07e7ba 100644 --- a/x/stake/test_common.go +++ b/x/stake/keeper/test_common.go @@ -1,4 +1,4 @@ -package stake +package keeper import ( "bytes" @@ -18,35 +18,52 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/stake/types" ) // dummy addresses used for testing var ( - addrs = createTestAddrs(100) - pks = createTestPubKeys(100) + Addrs = createTestAddrs(100) + PKs = createTestPubKeys(100) emptyAddr sdk.Address emptyPubkey crypto.PubKey + + addrDels = []sdk.Address{ + Addrs[0], + Addrs[1], + } + addrVals = []sdk.Address{ + Addrs[2], + Addrs[3], + Addrs[4], + Addrs[5], + Addrs[6], + } ) //_______________________________________________________________________________________ // intended to be used with require/assert: require.True(ValEq(...)) -func ValEq(t *testing.T, exp, got Validator) (*testing.T, bool, string, Validator, Validator) { - return t, exp.equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func ValEq(t *testing.T, exp, got types.Validator) (*testing.T, bool, string, types.Validator, types.Validator) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got } //_______________________________________________________________________________________ -func makeTestCodec() *wire.Codec { +// create a codec used only for testing +func MakeTestCodec() *wire.Codec { var cdc = wire.NewCodec() // Register Msgs cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil) cdc.RegisterConcrete(bank.MsgIssue{}, "test/stake/Issue", nil) - cdc.RegisterConcrete(MsgCreateValidator{}, "test/stake/CreateValidator", nil) - cdc.RegisterConcrete(MsgEditValidator{}, "test/stake/EditValidator", nil) - cdc.RegisterConcrete(MsgUnbond{}, "test/stake/Unbond", nil) + cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil) + cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil) + cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil) + cdc.RegisterConcrete(types.MsgCompleteUnbonding{}, "test/stake/CompleteUnbonding", nil) + cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil) + cdc.RegisterConcrete(types.MsgCompleteRedelegate{}, "test/stake/CompleteRedelegate", nil) // Register AppAccount cdc.RegisterInterface((*auth.Account)(nil), nil) @@ -56,8 +73,9 @@ func makeTestCodec() *wire.Codec { return cdc } -func paramsNoInflation() Params { - return Params{ +// default params without inflation +func ParamsNoInflation() types.Params { + return types.Params{ InflationRateChange: sdk.ZeroRat(), InflationMax: sdk.ZeroRat(), InflationMin: sdk.ZeroRat(), @@ -68,7 +86,7 @@ func paramsNoInflation() Params { } // hogpodge of all sorts of input required for testing -func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Context, auth.AccountMapper, Keeper) { +func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) { keyStake := sdk.NewKVStoreKey("stake") keyAcc := sdk.NewKVStoreKey("acc") @@ -81,28 +99,32 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Conte require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) - cdc := makeTestCodec() + cdc := MakeTestCodec() accountMapper := auth.NewAccountMapper( cdc, // amino codec keyAcc, // target store &auth.BaseAccount{}, // prototype ) ck := bank.NewKeeper(accountMapper) - keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) - keeper.setPool(ctx, InitialPool()) - keeper.setNewParams(ctx, DefaultParams()) - - // fill all the addresses with some coins - for _, addr := range addrs { + keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace) + keeper.SetPool(ctx, types.InitialPool()) + keeper.SetNewParams(ctx, types.DefaultParams()) + keeper.InitIntraTxCounter(ctx) + + // fill all the addresses with some coins, set the loose pool tokens simultaneously + for _, addr := range Addrs { + pool := keeper.GetPool(ctx) ck.AddCoins(ctx, addr, sdk.Coins{ - {keeper.GetParams(ctx).BondDenom, initCoins}, + {keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, }) + pool.LooseTokens += initCoins + keeper.SetPool(ctx, pool) } return ctx, accountMapper, keeper } -func newPubKey(pk string) (res crypto.PubKey) { +func NewPubKey(pk string) (res crypto.PubKey) { pkBytes, err := hex.DecodeString(pk) if err != nil { panic(err) @@ -114,7 +136,7 @@ func newPubKey(pk string) (res crypto.PubKey) { } // for incode address generation -func testAddr(addr string, bech string) sdk.Address { +func TestAddr(addr string, bech string) sdk.Address { res, err := sdk.GetAccAddressHex(addr) if err != nil { @@ -151,7 +173,7 @@ func createTestAddrs(numAddrs int) []sdk.Address { buffer.WriteString(numString) //adding on final two digits to make addresses unique res, _ := sdk.GetAccAddressHex(buffer.String()) bech, _ := sdk.Bech32ifyAcc(res) - addresses = append(addresses, testAddr(buffer.String(), bech)) + addresses = append(addresses, TestAddr(buffer.String(), bech)) buffer.Reset() } return addresses @@ -166,8 +188,16 @@ func createTestPubKeys(numPubKeys int) []crypto.PubKey { numString := strconv.Itoa(i) buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string buffer.WriteString(numString) //adding on final two digits to make pubkeys unique - publicKeys = append(publicKeys, newPubKey(buffer.String())) + publicKeys = append(publicKeys, NewPubKey(buffer.String())) buffer.Reset() } return publicKeys } + +//_____________________________________________________________________________________ + +// does a certain by-power index record exist +func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) bool { + store := ctx.KVStore(keeper.storeKey) + return store.Get(power) != nil +} diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go new file mode 100644 index 000000000000..b018aea449a9 --- /dev/null +++ b/x/stake/keeper/validator.go @@ -0,0 +1,537 @@ +package keeper + +import ( + "bytes" + "fmt" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// get a single validator +func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(GetValidatorKey(addr)) + if b == nil { + return validator, false + } + k.cdc.MustUnmarshalBinary(b, &validator) + return validator, true +} + +// get a single validator by pubkey +func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) { + store := ctx.KVStore(k.storeKey) + addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey)) + if addr == nil { + return validator, false + } + return k.GetValidator(ctx, addr) +} + +// set the main record holding validator details +func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + // set main store + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bz) +} + +// validator index +func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + // set pointer by pubkey + store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.Owner) +} + +// validator index +func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) { + store := ctx.KVStore(k.storeKey) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) +} + +// used in testing +func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool { + store := ctx.KVStore(k.storeKey) + return store.Get(power) != nil +} + +// Get the set of all validators with no limits, used during genesis dump +func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + i := 0 + for ; ; i++ { + if !iterator.Valid() { + break + } + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + validators = append(validators, validator) + iterator.Next() + } + iterator.Close() + return validators +} + +// Get the set of all validators, retrieve a maxRetrieve number of records +func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) + + validators = make([]types.Validator, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + break + } + bz := iterator.Value() + var validator types.Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + validators[i] = validator + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//___________________________________________________________________________ + +// get the group of the bonded validators +func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { + store := ctx.KVStore(k.storeKey) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + validators = make([]types.Validator, maxValidators) + + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + i := 0 + for ; iterator.Valid(); iterator.Next() { + + // sanity check + if i > int(maxValidators-1) { + panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") + } + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + + validators[i] = validator + i++ + } + iterator.Close() + return validators[:i] // trim +} + +// get the group of bonded validators sorted by power-rank +func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator { + store := ctx.KVStore(k.storeKey) + maxValidators := k.GetParams(ctx).MaxValidators + validators := make([]types.Validator, maxValidators) + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + i := 0 + for { + if !iterator.Valid() || i > int(maxValidators-1) { + break + } + address := iterator.Value() + validator, found := k.GetValidator(ctx, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + if validator.Status() == sdk.Bonded { + validators[i] = validator + i++ + } + iterator.Next() + } + iterator.Close() + return validators[:i] // trim +} + +//_________________________________________________________________________ +// Accumulated updates to the active/bonded validator set for tendermint + +// get the most recently updated validators +func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { + store := ctx.KVStore(k.storeKey) + + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val abci.Validator + k.cdc.MustUnmarshalBinary(valBytes, &val) + updates = append(updates, val) + } + iterator.Close() + return +} + +// remove all validator update entries after applied to Tendermint +func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + + // delete subspace + iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } + iterator.Close() +} + +//___________________________________________________________________________ + +// perfom all the nessisary steps for when a validator changes its power +// updates all validator stores as well as tendermint update store +// may kick out validators if new validator is entering the bonded validator group +func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator { + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + ownerAddr := validator.Owner + + // always update the main list ordered by owner address before exiting + defer func() { + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(ownerAddr), bz) + }() + + // retrieve the old validator record + oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) + + if validator.Revoked && oldValidator.Status() == sdk.Bonded { + validator = k.unbondValidator(ctx, validator) + + // need to also clear the cliff validator spot because the revoke has + // opened up a new spot which will be filled when + // updateValidatorsBonded is called + k.clearCliffValidator(ctx) + } + + powerIncreasing := false + if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) { + powerIncreasing = true + } + + // if already a validator, copy the old block height and counter, else set them + if oldFound && oldValidator.Status() == sdk.Bonded { + validator.BondHeight = oldValidator.BondHeight + validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter + } else { + validator.BondHeight = ctx.BlockHeight() + counter := k.GetIntraTxCounter(ctx) + validator.BondIntraTxCounter = counter + k.SetIntraTxCounter(ctx, counter+1) + } + + // update the list ordered by voting power + if oldFound { + store.Delete(GetValidatorsByPowerIndexKey(oldValidator, pool)) + } + valPower := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(valPower, validator.Owner) + + // efficiency case: + // if already bonded and power increasing only need to update tendermint + if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded { + bz := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc)) + store.Set(GetTendermintUpdatesKey(ownerAddr), bz) + return validator + } + + // efficiency case: + // if was unbonded/or is a new validator - and the new power is less than the cliff validator + cliffPower := k.getCliffValidatorPower(ctx) + if cliffPower != nil && + (!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) && + bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower + return validator + } + + // update the validator set for this validator + updatedVal := k.UpdateBondedValidators(ctx, validator) + if updatedVal.Owner != nil { // updates to validator occurred to be updated + validator = updatedVal + } + return validator +} + +// Update the validator group and kick out any old validators. In addition this +// function adds (or doesn't add) a validator which has updated its bonded +// tokens to the validator group. -> this validator is specified through the +// updatedValidatorAddr term. +// +// The correct subset is retrieved by iterating through an index of the +// validators sorted by power, stored using the ValidatorsByPowerIndexKey. +// Simultaneously the current validator records are updated in store with the +// ValidatorsBondedIndexKey. This store is used to determine if a validator is a +// validator without needing to iterate over the subspace as we do in +// GetValidators. +// +// Optionally also return the validator from a retrieve address if the validator has been bonded +func (k Keeper) UpdateBondedValidators(ctx sdk.Context, + newValidator types.Validator) (updatedVal types.Validator) { + + store := ctx.KVStore(k.storeKey) + + kickCliffValidator := false + oldCliffValidatorAddr := k.getCliffValidator(ctx) + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + // TODO benchmark if we should read the current power and not write if it's the same + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } + break + } + + // either retrieve the original validator from the store, or under the + // situation that this is the "new validator" just use the validator + // provided because it has not yet been updated in the main validator + // store + ownerAddr := iterator.Value() + if bytes.Equal(ownerAddr, newValidator.Owner) { + validator = newValidator + } else { + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + } + + // if not previously a validator (and unrevoked), + // kick the cliff validator / bond this new validator + if validator.Status() != sdk.Bonded && !validator.Revoked { + kickCliffValidator = true + + validator = k.bondValidator(ctx, validator) + if bytes.Equal(ownerAddr, newValidator.Owner) { + updatedVal = validator + } + } + + if validator.Revoked && validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } else { + bondedValidatorsCount++ + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + if oldCliffValidatorAddr != nil && kickCliffValidator { + validator, found := k.GetValidator(ctx, oldCliffValidatorAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr)) + } + k.unbondValidator(ctx, validator) + } + + return +} + +// full update of the bonded validator set, many can be added/kicked +func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) { + + store := ctx.KVStore(k.storeKey) + + // clear the current validators store, add to the ToKickOut temp store + toKickOut := make(map[string]byte) + iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + for ; iterator.Valid(); iterator.Next() { + ownerAddr := iterator.Value() + toKickOut[string(ownerAddr)] = 0 // set anything + } + iterator.Close() + + // add the actual validator power sorted store + maxValidators := k.GetParams(ctx).MaxValidators + iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest + bondedValidatorsCount := 0 + var validator types.Validator + for { + if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) { + + if bondedValidatorsCount == int(maxValidators) { // is cliff validator + k.setCliffValidator(ctx, validator, k.GetPool(ctx)) + } + break + } + + // either retrieve the original validator from the store, + // or under the situation that this is the "new validator" just + // use the validator provided because it has not yet been updated + // in the main validator store + ownerAddr := iterator.Value() + var found bool + validator, found = k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + + _, found = toKickOut[string(ownerAddr)] + if found { + delete(toKickOut, string(ownerAddr)) + } else { + + // if it wasn't in the toKickOut group it means + // this wasn't a previously a validator, therefor + // update the validator to enter the validator group + validator = k.bondValidator(ctx, validator) + } + + if validator.Revoked && validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr)) + } else { + bondedValidatorsCount++ + } + + iterator.Next() + } + iterator.Close() + + // perform the actual kicks + for key := range toKickOut { + ownerAddr := []byte(key) + validator, found := k.GetValidator(ctx, ownerAddr) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr)) + } + k.unbondValidator(ctx, validator) + } + return +} + +// perform all the store operations for when a validator status becomes unbonded +func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Unbonded { + panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + k.SetPool(ctx, pool) + + // save the now unbonded validator record + bzVal := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bzVal) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc)) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + // also remove from the Bonded types.Validators Store + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + return validator +} + +// perform all the store operations for when a validator status becomes bonded +func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator { + + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + + // sanity check + if validator.Status() == sdk.Bonded { + panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator)) + } + + // set the status + validator, pool = validator.UpdateStatus(pool, sdk.Bonded) + k.SetPool(ctx, pool) + + // save the now bonded validator record to the three referenced stores + bzVal := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bzVal) + store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner) + + // add to accumulated changes for tendermint + bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc)) + store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI) + + return validator +} + +// remove the validator record and associated indexes +func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.Address) { + + // first retrieve the old validator record + validator, found := k.GetValidator(ctx, address) + if !found { + return + } + + // delete the old validator record + store := ctx.KVStore(k.storeKey) + pool := k.GetPool(ctx) + store.Delete(GetValidatorKey(address)) + store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey)) + store.Delete(GetValidatorsByPowerIndexKey(validator, pool)) + + // delete from the current and power weighted validator groups if the validator + // is bonded - and add validator with zero power to the validator updates + if store.Get(GetValidatorsBondedIndexKey(validator.Owner)) == nil { + return + } + store.Delete(GetValidatorsBondedIndexKey(validator.Owner)) + + bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc)) + store.Set(GetTendermintUpdatesKey(address), bz) +} + +//__________________________________________________________________________ + +// get the current validator on the cliff +func (k Keeper) getCliffValidator(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorCliffIndexKey) +} + +// get the current power of the validator on the cliff +func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte { + store := ctx.KVStore(k.storeKey) + return store.Get(ValidatorPowerCliffKey) +} + +// set the current validator and power of the validator on the cliff +func (k Keeper) setCliffValidator(ctx sdk.Context, validator types.Validator, pool types.Pool) { + store := ctx.KVStore(k.storeKey) + bz := GetValidatorsByPowerIndexKey(validator, pool) + store.Set(ValidatorPowerCliffKey, bz) + store.Set(ValidatorCliffIndexKey, validator.Owner) +} + +// clear the current validator and power of the validator on the cliff +func (k Keeper) clearCliffValidator(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(ValidatorPowerCliffKey) + store.Delete(ValidatorCliffIndexKey) +} diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go new file mode 100644 index 000000000000..245954ad1319 --- /dev/null +++ b/x/stake/keeper/validator_test.go @@ -0,0 +1,674 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSetValidator(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 10) + pool := keeper.GetPool(ctx) + + // test how the validator is set from a purely unbonbed pool + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, _ = validator.AddTokensFromDel(pool, 10) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + + // after the save the validator should be bonded + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status()) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) + + // Check each store for being saved + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + assert.True(ValEq(t, validator, resVal)) + + resVals := keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + resVals = keeper.GetValidatorsByPower(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validator, resVals[0])) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + assert.Equal(t, validator.ABCIValidator(keeper.cdc), updates[0]) + +} + +func TestUpdateValidatorByPowerIndex(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + // create a random pool + pool.LooseTokens = 10000 + pool.BondedTokens = 1234 + pool.BondedShares = sdk.NewRat(124) + pool.UnbondingTokens = 13934 + pool.UnbondingShares = sdk.NewRat(145) + pool.UnbondedTokens = 154 + pool.UnbondedShares = sdk.NewRat(1333) + keeper.SetPool(ctx, pool) + + // add a validator + validator := types.NewValidator(addrVals[0], PKs[0], types.Description{}) + validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, 100) + require.Equal(t, sdk.Unbonded, validator.Status()) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validator) + validator, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) + + pool = keeper.GetPool(ctx) + power := GetValidatorsByPowerIndexKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) + + // burn half the delegator shares + validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) + assert.Equal(t, int64(50), burned) + keeper.SetPool(ctx, pool) // update the pool + keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out + assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) + + pool = keeper.GetPool(ctx) + validator, found = keeper.GetValidator(ctx, addrVals[0]) + power = GetValidatorsByPowerIndexKey(validator, pool) + assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) +} + +// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator +func TestValidatorBasics(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + pool := keeper.GetPool(ctx) + + //construct the validators + var validators [3]types.Validator + amts := []int64{9, 8, 7} + for i, amt := range amts { + validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewUnbondedShares(sdk.ZeroRat()) + validators[i].AddTokensFromDel(pool, amt) + } + + // check the empty keeper first + _, found := keeper.GetValidator(ctx, addrVals[0]) + assert.False(t, found) + resVals := keeper.GetValidatorsBonded(ctx) + assert.Zero(t, len(resVals)) + + // set and retrieve a record + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found := keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // modify a records, save, and retrieve + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(10)) + validators[0].DelegatorShares = sdk.NewRat(10) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resVal, found = keeper.GetValidator(ctx, addrVals[0]) + require.True(t, found) + assert.True(ValEq(t, validators[0], resVal)) + + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) + + // add other validators + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(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.GetValidatorsBonded(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 + keeper.RemoveValidator(ctx, validators[1].Owner) + _, found = keeper.GetValidator(ctx, addrVals[1]) + assert.False(t, found) +} + +// test how the validators are sorted, tests GetValidatorsByPower +func GetValidatorSortingUnmixed(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // 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(Addrs[i], PKs[i], types.Description{}) + validators[i].PoolShares = types.NewBondedShares(sdk.NewRat(amt)) + validators[i].DelegatorShares = sdk.NewRat(amt) + keeper.UpdateValidator(ctx, validators[i]) + } + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) + + // test a basic increase in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(500)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + + // test a decrease in voting power + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(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].PoolShares = types.NewBondedShares(sdk.NewRat(200)) + ctx = ctx.WithBlockHeight(10) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + assert.True(ValEq(t, validators[3], resValidators[0])) + assert.True(ValEq(t, validators[4], resValidators[1])) + assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) + assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) + + // no change in voting power - no change in sort + ctx = ctx.WithBlockHeight(20) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(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].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(300)) + keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, len(resValidators), n) + ctx = ctx.WithBlockHeight(30) + keeper.UpdateValidator(ctx, validators[4]) + resValidators = keeper.GetValidatorsByPower(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, 1000) + + // 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(Addrs[i], PKs[i], types.Description{}) + validators[i].DelegatorShares = sdk.NewRat(amt) + } + validators[0].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[0])) + validators[1].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[1])) + validators[2].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[2])) + validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(amts[3])) + validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(amts[4])) + for i := range amts { + keeper.UpdateValidator(ctx, validators[i]) + } + val0, found := keeper.GetValidator(ctx, Addrs[0]) + require.True(t, found) + val1, found := keeper.GetValidator(ctx, Addrs[1]) + require.True(t, found) + val2, found := keeper.GetValidator(ctx, Addrs[2]) + require.True(t, found) + val3, found := keeper.GetValidator(ctx, Addrs[3]) + require.True(t, found) + val4, found := keeper.GetValidator(ctx, Addrs[4]) + require.True(t, found) + assert.Equal(t, sdk.Unbonded, val0.Status()) + assert.Equal(t, sdk.Unbonded, val1.Status()) + assert.Equal(t, sdk.Unbonded, val2.Status()) + assert.Equal(t, sdk.Bonded, val3.Status()) + assert.Equal(t, sdk.Bonded, val4.Status()) + + // first make sure everything made it in to the gotValidator group + resValidators := keeper.GetValidatorsByPower(ctx) + require.Equal(t, n, len(resValidators)) + assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) + assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) + assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) + assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) + assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) + assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) +} + +// TODO separate out into multiple tests +func TestGetValidatorsEdgeCases(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + 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 := []int64{0, 100, 400, 400} + var validators [4]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + validators[i] = keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + resValidators := keeper.GetValidatorsByPower(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) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 500) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(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].Owner) + require.True(t, found) + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 1) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(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 + validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewRat(201)) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(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 + validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 200) + keeper.SetPool(ctx, pool) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + resValidators = keeper.GetValidatorsByPower(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].Owner) + require.Equal(t, exists, true) + require.Equal(t, int64(40), validator.BondHeight) +} + +func TestValidatorBondHeight(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + // now 2 max resValidators + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + // initialize some validators into the state + pool := keeper.GetPool(ctx) + var validators [3]types.Validator + validators[0] = types.NewValidator(Addrs[0], PKs[0], types.Description{}) + validators[1] = types.NewValidator(Addrs[1], PKs[1], types.Description{}) + validators[2] = types.NewValidator(Addrs[2], PKs[2], types.Description{}) + + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 200) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 100) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 100) + + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(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] = keeper.UpdateValidator(ctx, validators[1]) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators := keeper.GetValidatorsByPower(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])) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 50) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 50) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, params.MaxValidators, uint16(len(resValidators))) + validators[1] = keeper.UpdateValidator(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, 1000) + params := keeper.GetParams(ctx) + max := 2 + params.MaxValidators = uint16(2) + keeper.SetParams(ctx, params) + + // initialize some validators into the state + amts := []int64{0, 100, 400, 400, 200} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + for i := range amts { + var found bool + validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) + require.True(t, found) + } + assert.Equal(t, sdk.Unbonded, validators[0].Status()) + assert.Equal(t, sdk.Unbonded, 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.GetValidatorsByPower(ctx) + require.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, 600) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + resValidators = keeper.GetValidatorsByPower(ctx) + require.Equal(t, max, len(resValidators)) + assert.True(ValEq(t, validators[0], resValidators[0])) + assert.True(ValEq(t, validators[2], resValidators[1])) +} + +// clear the tracked changes to the gotValidator set +func TestClearTendermintUpdates(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{100, 400, 200} + validators := make([]types.Validator, len(amts)) + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + keeper.UpdateValidator(ctx, validators[i]) + } + + updates := keeper.GetTendermintUpdates(ctx) + assert.Equal(t, len(amts), len(updates)) + keeper.ClearTendermintUpdates(ctx) + updates = keeper.GetTendermintUpdates(ctx) + assert.Equal(t, 0, len(updates)) +} + +func TestGetTendermintUpdatesAllNone(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + + // test from nothing to something + // tendermintUpdate set: {} -> {c1, c3} + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) + assert.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1]) + + // test from something to nothing + // tendermintUpdate set: {} -> {c1, c2, c3, c4} + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + keeper.RemoveValidator(ctx, validators[0].Owner) + keeper.RemoveValidator(ctx, validators[1].Owner) + + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey) + assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey) + assert.Equal(t, int64(0), updates[0].Power) + assert.Equal(t, int64(0), updates[1].Power) +} + +func TestGetTendermintUpdatesIdentical(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test identical, + // tendermintUpdate set: {} -> {} + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) +} + +func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test single value change + // tendermintUpdate set: {} -> {c1'} + validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(600)) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + + updates := keeper.GetTendermintUpdates(ctx) + + require.Equal(t, 1, len(updates)) + assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) +} + +func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20} + var validators [2]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test multiple value change + // tendermintUpdate set: {c1, c3} -> {c1', c3'} + pool := keeper.GetPool(ctx) + validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 190) + validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 80) + keeper.SetPool(ctx, pool) + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates)) + require.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0]) + require.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1]) +} + +func TestGetTendermintUpdatesInserted(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + + amts := []int64{10, 20, 5, 15, 25} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[0]) + + // test validtor added at the beginning + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[3] = keeper.UpdateValidator(ctx, validators[3]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[3].ABCIValidator(keeper.cdc), updates[0]) + + // test validtor added at the end + // tendermintUpdate set: {} -> {c0} + keeper.ClearTendermintUpdates(ctx) + validators[4] = keeper.UpdateValidator(ctx, validators[4]) + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 1, len(updates)) + require.Equal(t, validators[4].ABCIValidator(keeper.cdc), updates[0]) +} + +func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { + ctx, _, keeper := CreateTestInput(t, false, 1000) + params := types.DefaultParams() + params.MaxValidators = 2 + keeper.SetParams(ctx, params) + + amts := []int64{10, 20, 5} + var validators [5]types.Validator + for i, amt := range amts { + pool := keeper.GetPool(ctx) + validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{}) + validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt) + keeper.SetPool(ctx, pool) + } + validators[0] = keeper.UpdateValidator(ctx, validators[0]) + validators[1] = keeper.UpdateValidator(ctx, validators[1]) + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + // test validator added at the end but not inserted in the valset + // tendermintUpdate set: {} -> {} + keeper.UpdateValidator(ctx, validators[2]) + updates := keeper.GetTendermintUpdates(ctx) + require.Equal(t, 0, len(updates)) + + // test validator change its power and become a gotValidator (pushing out an existing) + // tendermintUpdate set: {} -> {c0, c4} + keeper.ClearTendermintUpdates(ctx) + assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx))) + + pool := keeper.GetPool(ctx) + validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 10) + keeper.SetPool(ctx, pool) + validators[2] = keeper.UpdateValidator(ctx, validators[2]) + + updates = keeper.GetTendermintUpdates(ctx) + require.Equal(t, 2, len(updates), "%v", updates) + require.Equal(t, validators[0].ABCIValidatorZero(keeper.cdc), updates[0]) + require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[1]) +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go deleted file mode 100644 index 20062f00e45e..000000000000 --- a/x/stake/keeper_keys.go +++ /dev/null @@ -1,89 +0,0 @@ -package stake - -import ( - "encoding/binary" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" - crypto "github.com/tendermint/go-crypto" -) - -// TODO remove some of these prefixes once have working multistore - -//nolint -var ( - // Keys for store prefixes - ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey - ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators - ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power - ValidatorCliffKey = []byte{0x06} // key for block-local tx index - ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index - TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated - DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond - IntraTxCounterKey = []byte{0x10} // key for block-local tx index -) - -const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch - -// get the key for the validator with address -func GetValidatorKey(ownerAddr sdk.Address) []byte { - return append(ValidatorsKey, ownerAddr.Bytes()...) -} - -// get the key for the validator with pubkey -func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte { - return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...) -} - -// get the key for the current validator group, ordered like tendermint -func GetValidatorsBondedKey(pk crypto.PubKey) []byte { - addr := pk.Address() - return append(ValidatorsBondedKey, addr.Bytes()...) -} - -// get the key for the validator used in the power-store -func GetValidatorsByPowerKey(validator Validator, pool Pool) []byte { - - power := validator.EquivalentBondedShares(pool) - powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first) - - // TODO ensure that the key will be a readable string.. probably should add seperators and have - revokedBytes := make([]byte, 1) - if validator.Revoked { - revokedBytes[0] = byte(0x01) - } else { - revokedBytes[0] = byte(0x00) - } - // heightBytes and counterBytes represent strings like powerBytes does - heightBytes := make([]byte, binary.MaxVarintLen64) - binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) - counterBytes := make([]byte, 2) - binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) - return append(ValidatorsByPowerKey, - append(revokedBytes, - append(powerBytes, - append(heightBytes, - append(counterBytes, validator.Owner.Bytes()...)...)...)...)...) // TODO don't technically need to store owner -} - -// get the key for the accumulated update validators -func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { - return append(TendermintUpdatesKey, ownerAddr.Bytes()...) -} - -// get the key for delegator bond with validator -func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...) -} - -// get the prefix for a delegator for all validators -func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegationKey, res...) -} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go deleted file mode 100644 index 7b8b9ddccd26..000000000000 --- a/x/stake/keeper_test.go +++ /dev/null @@ -1,792 +0,0 @@ -package stake - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - addrDels = []sdk.Address{ - addrs[0], - addrs[1], - } - addrVals = []sdk.Address{ - addrs[2], - addrs[3], - addrs[4], - addrs[5], - addrs[6], - } -) - -func TestUpdateValidatorByPowerIndex(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - // create a random pool - pool.BondedTokens = sdk.NewInt(1234) - pool.BondedShares = sdk.NewRat(124) - pool.UnbondingTokens = sdk.NewInt(13934) - pool.UnbondingShares = sdk.NewRat(145) - pool.UnbondedTokens = sdk.NewInt(154) - pool.UnbondedShares = sdk.NewRat(1333) - keeper.setPool(ctx, pool) - - // add a validator - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, delSharesCreated := validator.addTokensFromDel(pool, sdk.NewInt(100)) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate()) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool) - - pool = keeper.GetPool(ctx) - power := GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) - - // burn half the delegator shares - validator, pool, burned := validator.removeDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2))) - assert.Equal(t, int64(50), burned.Int64()) - keeper.setPool(ctx, pool) // update the pool - keeper.updateValidator(ctx, validator) // update the validator, possibly kicking it out - assert.False(t, keeper.validatorByPowerIndexExists(ctx, power)) - - pool = keeper.GetPool(ctx) - validator, found = keeper.GetValidator(ctx, addrVals[0]) - power = GetValidatorsByPowerKey(validator, pool) - assert.True(t, keeper.validatorByPowerIndexExists(ctx, power)) -} - -func TestSetValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - // test how the validator is set from a purely unbonbed pool - validator := NewValidator(addrVals[0], pks[0], Description{}) - validator, pool, _ = validator.addTokensFromDel(pool, sdk.NewInt(10)) - require.Equal(t, sdk.Unbonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - keeper.setPool(ctx, pool) - keeper.updateValidator(ctx, validator) - - // after the save the validator should be bonded - validator, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - require.Equal(t, sdk.Bonded, validator.Status()) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) - assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares)) - - // Check each store for being saved - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - assert.True(ValEq(t, validator, resVal)) - - resVals := keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - resVals = keeper.GetValidatorsByPower(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validator, resVals[0])) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - assert.Equal(t, validator.abciValidator(keeper.cdc), updates[0]) - -} - -// This function tests updateValidator, GetValidator, GetValidatorsBonded, removeValidator -func TestValidatorBasics(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - //construct the validators - var validators [3]Validator - amts := []int64{9, 8, 7} - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.ZeroRat()) - validators[i].addTokensFromDel(pool, sdk.NewInt(amt)) - } - - // check the empty keeper first - _, found := keeper.GetValidator(ctx, addrVals[0]) - assert.False(t, found) - resVals := keeper.GetValidatorsBonded(ctx) - assert.Zero(t, len(resVals)) - - // set and retrieve a record - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found := keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // modify a records, save, and retrieve - validators[0].PoolShares = NewBondedShares(sdk.NewRat(10)) - validators[0].DelegatorShares = sdk.NewRat(10) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resVal, found = keeper.GetValidator(ctx, addrVals[0]) - require.True(t, found) - assert.True(ValEq(t, validators[0], resVal)) - - resVals = keeper.GetValidatorsBonded(ctx) - require.Equal(t, 1, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[0])) - - // add other validators - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(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.GetValidatorsBonded(ctx) - require.Equal(t, 3, len(resVals)) - assert.True(ValEq(t, validators[0], resVals[2])) // order doesn't matter here - assert.True(ValEq(t, validators[1], resVals[1])) - assert.True(ValEq(t, validators[2], resVals[0])) - - // remove a record - keeper.removeValidator(ctx, validators[1].Owner) - _, found = keeper.GetValidator(ctx, addrVals[1]) - assert.False(t, found) -} - -// test how the validators are sorted, tests GetValidatorsByPower -func GetValidatorSortingUnmixed(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - // initialize some validators into the state - amts := []int64{0, 100, 1, 400, 200} - n := len(amts) - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewBondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) - - // test a basic increase in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(500)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - - // test a decrease in voting power - validators[3].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(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].PoolShares = NewBondedShares(sdk.NewRat(200)) - ctx = ctx.WithBlockHeight(10) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - assert.True(ValEq(t, validators[3], resValidators[0])) - assert.True(ValEq(t, validators[4], resValidators[1])) - assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators) - assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators) - - // no change in voting power - no change in sort - ctx = ctx.WithBlockHeight(20) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(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].PoolShares = NewBondedShares(sdk.NewRat(300)) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(300)) - keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, len(resValidators), n) - ctx = ctx.WithBlockHeight(30) - keeper.updateValidator(ctx, validators[4]) - resValidators = keeper.GetValidatorsByPower(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.NewInt(0)) - - // 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]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(amts[0])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(amts[1])) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(amts[2])) - validators[3].PoolShares = NewBondedShares(sdk.NewRat(amts[3])) - validators[4].PoolShares = NewBondedShares(sdk.NewRat(amts[4])) - for i := range amts { - keeper.updateValidator(ctx, validators[i]) - } - val0, found := keeper.GetValidator(ctx, addrs[0]) - require.True(t, found) - val1, found := keeper.GetValidator(ctx, addrs[1]) - require.True(t, found) - val2, found := keeper.GetValidator(ctx, addrs[2]) - require.True(t, found) - val3, found := keeper.GetValidator(ctx, addrs[3]) - require.True(t, found) - val4, found := keeper.GetValidator(ctx, addrs[4]) - require.True(t, found) - assert.Equal(t, sdk.Unbonded, val0.Status()) - assert.Equal(t, sdk.Unbonded, val1.Status()) - assert.Equal(t, sdk.Unbonded, val2.Status()) - assert.Equal(t, sdk.Bonded, val3.Status()) - assert.Equal(t, sdk.Bonded, val4.Status()) - - // first make sure everything made it in to the gotValidator group - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, n, len(resValidators)) - assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators) - assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators) - assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators) - assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators) - assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators) - assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators) -} - -// TODO separate out into multiple tests -func TestGetValidatorsEdgeCases(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - 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 := []int64{0, 100, 400, 400} - var validators [4]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - validators[i] = keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - resValidators := keeper.GetValidatorsByPower(ctx) - require.Equal(t, nMax, uint16(len(resValidators))) - assert.True(ValEq(t, validators[2], resValidators[0])) - assert.True(ValEq(t, validators[3], resValidators[1])) - - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(500)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(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].Owner) - require.True(t, found) - validators[3].PoolShares = NewUnbondedShares(sdk.NewRat(401)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(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 - validators[3].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(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 - validators[3].PoolShares = NewBondedShares(sdk.NewRat(400)) - validators[3] = keeper.updateValidator(ctx, validators[3]) - resValidators = keeper.GetValidatorsByPower(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].Owner) - require.Equal(t, exists, true) - require.Equal(t, int64(40), validator.BondHeight) -} - -func TestValidatorBondHeight(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - // now 2 max resValidators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - // initialize some validators into the state - var validators [3]Validator - validators[0] = NewValidator(addrs[0], pks[0], Description{}) - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(200)) - validators[0].DelegatorShares = sdk.NewRat(200) - validators[1] = NewValidator(addrs[1], pks[1], Description{}) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[1].DelegatorShares = sdk.NewRat(100) - validators[2] = NewValidator(addrs[2], pks[2], Description{}) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(100)) - validators[2].DelegatorShares = sdk.NewRat(100) - - validators[0] = keeper.updateValidator(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] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators := keeper.GetValidatorsByPower(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])) - validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(150)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = keeper.updateValidator(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.NewInt(0)) - params := keeper.GetParams(ctx) - max := 2 - params.MaxValidators = uint16(2) - keeper.setParams(ctx, params) - - // initialize some validators into the state - amts := []int64{0, 100, 400, 400, 200} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - for i := range amts { - var found bool - validators[i], found = keeper.GetValidator(ctx, validators[i].Owner) - require.True(t, found) - } - assert.Equal(t, sdk.Unbonded, validators[0].Status()) - assert.Equal(t, sdk.Unbonded, 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.GetValidatorsByPower(ctx) - require.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 - validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - resValidators = keeper.GetValidatorsByPower(ctx) - require.Equal(t, max, len(resValidators)) - assert.True(ValEq(t, validators[0], resValidators[0])) - assert.True(ValEq(t, validators[2], resValidators[1])) -} - -// clear the tracked changes to the gotValidator set -func TestClearTendermintUpdates(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{100, 400, 200} - validators := make([]Validator, len(amts)) - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - keeper.updateValidator(ctx, validators[i]) - } - - updates := keeper.getTendermintUpdates(ctx) - assert.Equal(t, len(amts), len(updates)) - keeper.clearTendermintUpdates(ctx) - updates = keeper.getTendermintUpdates(ctx) - assert.Equal(t, 0, len(updates)) -} - -func TestGetTendermintUpdatesAllNone(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // test from nothing to something - // tendermintUpdate set: {} -> {c1, c3} - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - assert.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) - - // test from something to nothing - // tendermintUpdate set: {} -> {c1, c2, c3, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - keeper.removeValidator(ctx, validators[0].Owner) - keeper.removeValidator(ctx, validators[1].Owner) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey) - assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey) - assert.Equal(t, int64(0), updates[0].Power) - assert.Equal(t, int64(0), updates[1].Power) -} - -func TestGetTendermintUpdatesIdentical(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test identical, - // tendermintUpdate set: {} -> {} - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) -} - -func TestGetTendermintUpdatesSingleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test single value change - // tendermintUpdate set: {} -> {c1'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(600)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - - updates := keeper.getTendermintUpdates(ctx) - - require.Equal(t, 1, len(updates)) - assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20} - var validators [2]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test multiple value change - // tendermintUpdate set: {c1, c3} -> {c1', c3'} - validators[0].PoolShares = NewBondedShares(sdk.NewRat(200)) - validators[1].PoolShares = NewBondedShares(sdk.NewRat(100)) - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates)) - require.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0]) - require.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1]) -} - -func TestGetTendermintUpdatesInserted(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - amts := []int64{10, 20, 5, 15, 25} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - validators[2] = keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the beginning - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[3] = keeper.updateValidator(ctx, validators[3]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[3].abciValidator(keeper.cdc), updates[0]) - - // test validtor added at the end - // tendermintUpdate set: {} -> {c0} - keeper.clearTendermintUpdates(ctx) - validators[4] = keeper.updateValidator(ctx, validators[4]) - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 1, len(updates)) - require.Equal(t, validators[4].abciValidator(keeper.cdc), updates[0]) -} - -func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - params := DefaultParams() - params.MaxValidators = 2 - keeper.setParams(ctx, params) - - amts := []int64{10, 20, 5} - var validators [5]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrs[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - validators[0] = keeper.updateValidator(ctx, validators[0]) - validators[1] = keeper.updateValidator(ctx, validators[1]) - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - // test validator added at the end but not inserted in the valset - // tendermintUpdate set: {} -> {} - keeper.updateValidator(ctx, validators[2]) - updates := keeper.getTendermintUpdates(ctx) - require.Equal(t, 0, len(updates)) - - // test validator change its power and become a gotValidator (pushing out an existing) - // tendermintUpdate set: {} -> {c0, c4} - keeper.clearTendermintUpdates(ctx) - assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx))) - - validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(15)) - validators[2] = keeper.updateValidator(ctx, validators[2]) - - updates = keeper.getTendermintUpdates(ctx) - require.Equal(t, 2, len(updates), "%v", updates) - require.Equal(t, validators[0].abciValidatorZero(keeper.cdc), updates[0]) - require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[1]) -} - -// tests GetDelegation, GetDelegations, SetDelegation, removeDelegation, GetBonds -func TestBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = NewValidator(addrVals[i], pks[i], Description{}) - validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) - validators[i].DelegatorShares = sdk.NewRat(amt) - } - - // first add a validators[0] to delegate too - validators[0] = keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - // check the empty keeper first - _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - validators[1] = keeper.updateValidator(ctx, validators[1]) - validators[2] = keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(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.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - allBonds := keeper.getAllDelegations(ctx) - require.Equal(t, 6, len(allBonds)) - assert.True(t, bond1to1.equal(allBonds[0])) - assert.True(t, bond1to2.equal(allBonds[1])) - assert.True(t, bond1to3.equal(allBonds[2])) - assert.True(t, bond2to1.equal(allBonds[3])) - assert.True(t, bond2to2.equal(allBonds[4])) - assert.True(t, bond2to3.equal(allBonds[5])) - - // delete a record - keeper.removeDelegation(ctx, bond2to3) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 2, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - - // delete all the records from delegator 2 - keeper.removeDelegation(ctx, bond2to1) - keeper.removeDelegation(ctx, bond2to2) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) - assert.False(t, found) - _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) - assert.False(t, found) - resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 0, len(resBonds)) -} - -func TestParams(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - expParams := DefaultParams() - - //check that the empty keeper loads the default - resParams := keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) - - //modify a params, save, and retrieve - expParams.MaxValidators = 777 - keeper.setParams(ctx, expParams) - resParams = keeper.GetParams(ctx) - assert.True(t, expParams.equal(resParams)) -} - -func TestPool(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - expPool := InitialPool() - - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) - - //modify a params, save, and retrieve - expPool.BondedTokens = sdk.NewInt(777) - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.True(t, expPool.equal(resPool)) -} diff --git a/x/stake/msg.go b/x/stake/msg.go deleted file mode 100644 index b764a2388d6a..000000000000 --- a/x/stake/msg.go +++ /dev/null @@ -1,243 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// name to idetify transaction types -const MsgType = "stake" - -// XXX remove: think it makes more sense belonging with the Params so we can -// initialize at genesis - to allow for the same tests we should should make -// the ValidateBasic() function a return from an initializable function -// ValidateBasic(bondDenom string) function -const StakingToken = "steak" - -//Verify interface at compile time -var _, _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}, &MsgUnbond{} - -//______________________________________________________________________ - -// MsgCreateValidator - struct for unbonding transactions -type MsgCreateValidator struct { - Description - ValidatorAddr sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pubkey"` - Bond sdk.Coin `json:"bond"` -} - -func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, - bond sdk.Coin, description Description) MsgCreateValidator { - return MsgCreateValidator{ - Description: description, - ValidatorAddr: validatorAddr, - PubKey: pubkey, - Bond: bond, - } -} - -//nolint -func (msg MsgCreateValidator) Type() string { return MsgType } -func (msg MsgCreateValidator) GetSigners() []sdk.Address { - return []sdk.Address{msg.ValidatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgCreateValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - PubKey string `json:"pubkey"` - Bond sdk.Coin `json:"bond"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - PubKey: sdk.MustBech32ifyValPub(msg.PubKey), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgCreateValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount.Sign() != 1 { - return ErrBadBondingAmount(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "description must be included") - } - return nil -} - -//______________________________________________________________________ - -// MsgEditValidator - struct for editing a validator -type MsgEditValidator struct { - Description - ValidatorAddr sdk.Address `json:"address"` -} - -func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { - return MsgEditValidator{ - Description: description, - ValidatorAddr: validatorAddr, - } -} - -//nolint -func (msg MsgEditValidator) Type() string { return MsgType } -func (msg MsgEditValidator) GetSigners() []sdk.Address { - return []sdk.Address{msg.ValidatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgEditValidator) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - Description - ValidatorAddr string `json:"address"` - }{ - Description: msg.Description, - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgEditValidator) ValidateBasic() sdk.Error { - if msg.ValidatorAddr == nil { - return ErrValidatorEmpty(DefaultCodespace) - } - empty := Description{} - if msg.Description == empty { - return newError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") - } - return nil -} - -//______________________________________________________________________ - -// MsgDelegate - struct for bonding transactions -type MsgDelegate struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` -} - -func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Bond: bond, - } -} - -//nolint -func (msg MsgDelegate) Type() string { return MsgType } -func (msg MsgDelegate) GetSigners() []sdk.Address { - return []sdk.Address{msg.DelegatorAddr} -} - -// get the bytes for the message signer to sign on -func (msg MsgDelegate) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Bond sdk.Coin `json:"bond"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Bond: msg.Bond, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgDelegate) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Bond.Denom != StakingToken { - return ErrBadBondingDenom(DefaultCodespace) - } - if msg.Bond.Amount.Sign() != 1 { - return ErrBadBondingAmount(DefaultCodespace) - } - return nil -} - -//______________________________________________________________________ - -// MsgUnbond - struct for unbonding transactions -type MsgUnbond struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - ValidatorAddr sdk.Address `json:"validator_addr"` - Shares string `json:"shares"` -} - -func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUnbond { - return MsgUnbond{ - DelegatorAddr: delegatorAddr, - ValidatorAddr: validatorAddr, - Shares: shares, - } -} - -//nolint -func (msg MsgUnbond) Type() string { return MsgType } -func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } - -// get the bytes for the message signer to sign on -func (msg MsgUnbond) GetSignBytes() []byte { - b, err := msgCdc.MarshalJSON(struct { - DelegatorAddr string `json:"delegator_addr"` - ValidatorAddr string `json:"validator_addr"` - Shares string `json:"shares"` - }{ - DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), - ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), - Shares: msg.Shares, - }) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgUnbond) ValidateBasic() sdk.Error { - if msg.DelegatorAddr == nil { - return ErrBadDelegatorAddr(DefaultCodespace) - } - if msg.ValidatorAddr == nil { - return ErrBadValidatorAddr(DefaultCodespace) - } - if msg.Shares != "MAX" { - rat, err := sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return ErrBadShares(DefaultCodespace) - } - if rat.IsZero() || rat.LT(sdk.ZeroRat()) { - return ErrBadShares(DefaultCodespace) - } - } - return nil -} diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go deleted file mode 100644 index c14698d1b991..000000000000 --- a/x/stake/msg_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -var ( - coinPos = sdk.NewCoin("steak", 1000) - coinZero = sdk.NewCoin("steak", 0) - coinNeg = sdk.NewCoin("steak", -10000) - coinPosNotAtoms = sdk.NewCoin("foo", 10000) - coinZeroNotAtoms = sdk.NewCoin("foo", 0) - coinNegNotAtoms = sdk.NewCoin("foo", -10000) -) - -// test ValidateBasic for MsgCreateValidator -func TestMsgCreateValidator(t *testing.T) { - tests := []struct { - name, moniker, identity, website, details string - validatorAddr sdk.Address - pubkey crypto.PubKey - bond sdk.Coin - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true}, - {"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true}, - {"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, - {"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.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.Address - expectPass bool - }{ - {"basic good", "a", "b", "c", "d", addrs[0], true}, - {"partial description", "", "", "c", "", addrs[0], true}, - {"empty description", "", "", "", "", addrs[0], false}, - {"empty address", "a", "b", "c", "d", emptyAddr, false}, - } - - for _, tc := range tests { - description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditValidator(tc.validatorAddr, description) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgDelegate -func TestMsgDelegate(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - bond sdk.Coin - expectPass bool - }{ - {"basic good", addrs[0], addrs[1], coinPos, true}, - {"self bond", addrs[0], addrs[0], coinPos, true}, - {"empty delegator", emptyAddr, addrs[0], coinPos, false}, - {"empty validator", addrs[0], emptyAddr, coinPos, false}, - {"empty bond", addrs[0], addrs[1], coinZero, false}, - {"negative bond", addrs[0], addrs[1], coinNeg, false}, - {"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false}, - } - - for _, tc := range tests { - msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// test ValidateBasic for MsgUnbond -func TestMsgUnbond(t *testing.T) { - tests := []struct { - name string - delegatorAddr sdk.Address - validatorAddr sdk.Address - shares string - expectPass bool - }{ - {"max unbond", addrs[0], addrs[1], "MAX", true}, - {"decimal unbond", addrs[0], addrs[1], "0.1", true}, - {"negative decimal unbond", addrs[0], addrs[1], "-0.1", false}, - {"zero unbond", addrs[0], addrs[1], "0.0", false}, - {"invalid decimal", addrs[0], addrs[0], "sunny", false}, - {"empty delegator", emptyAddr, addrs[0], "0.1", false}, - {"empty validator", addrs[0], emptyAddr, "0.1", false}, - } - - for _, tc := range tests { - msg := NewMsgUnbond(tc.delegatorAddr, tc.validatorAddr, tc.shares) - if tc.expectPass { - assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) - } else { - assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) - } - } -} - -// TODO introduce with go-amino -//func TestSerializeMsg(t *testing.T) { - -//// make sure all types construct properly -//bondAmt := 1234321 -//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - -//tests := []struct { -//tx sdk.Msg -//}{ -//{NewMsgCreateValidator(addrs[0], pks[0], bond, Description{})}, -//{NewMsgEditValidator(addrs[0], Description{})}, -//{NewMsgDelegate(addrs[0], addrs[1], bond)}, -//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, -//} - -//for i, tc := range tests { -//var tx sdk.Tx -//bs := wire.BinaryBytes(tc.tx) -//err := wire.ReadBinaryBytes(bs, &tx) -//if assert.NoError(t, err, "%d", i) { -//assert.Equal(t, tc.tx, tx, "%d", i) -//} -//} -//} diff --git a/x/stake/pool.go b/x/stake/pool.go deleted file mode 100644 index 4b65c0e8e9d9..000000000000 --- a/x/stake/pool.go +++ /dev/null @@ -1,133 +0,0 @@ -package stake - -import ( - "bytes" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Pool - dynamic parameters of the current state -type Pool struct { - LooseUnbondedTokens sdk.Int `json:"loose_unbonded_tokens"` // tokens not associated with any validator - UnbondedTokens sdk.Int `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators - UnbondingTokens sdk.Int `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool - BondedTokens sdk.Int `json:"bonded_tokens"` // reserve of bonded tokens - UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool - BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation sdk.Rat `json:"inflation"` // current annual inflation rate - - DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) - - // Fee Related - PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations -} - -func (p Pool) equal(p2 Pool) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) - return bytes.Equal(bz1, bz2) -} - -// initial pool for testing -func InitialPool() Pool { - return Pool{ - LooseUnbondedTokens: sdk.ZeroInt(), - BondedTokens: sdk.ZeroInt(), - UnbondingTokens: sdk.ZeroInt(), - UnbondedTokens: sdk.ZeroInt(), - BondedShares: sdk.ZeroRat(), - UnbondingShares: sdk.ZeroRat(), - UnbondedShares: sdk.ZeroRat(), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - DateLastCommissionReset: 0, - PrevBondedShares: sdk.ZeroRat(), - } -} - -//____________________________________________________________________ - -// Sum total of all staking tokens in the pool -func (p Pool) TokenSupply() sdk.Int { - return p.LooseUnbondedTokens.Add(p.UnbondedTokens).Add(p.UnbondingTokens).Add(p.BondedTokens) -} - -//____________________________________________________________________ - -// get the bond ratio of the global state -func (p Pool) bondedRatio() sdk.Rat { - if p.TokenSupply().Sign() == 1 { - return sdk.NewRatFromInt(p.BondedTokens, p.TokenSupply()) - } - return sdk.ZeroRat() -} - -// get the exchange rate of bonded token per issued share -func (p Pool) bondedShareExRate() sdk.Rat { - if p.BondedShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.BondedTokens).Quo(p.BondedShares) -} - -// get the exchange rate of unbonding tokens held in validators per issued share -func (p Pool) unbondingShareExRate() sdk.Rat { - if p.UnbondingShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.UnbondingTokens).Quo(p.UnbondingShares) -} - -// get the exchange rate of unbonded tokens held in validators per issued share -func (p Pool) unbondedShareExRate() sdk.Rat { - if p.UnbondedShares.IsZero() { - return sdk.OneRat() - } - return sdk.NewRatFromInt(p.UnbondedTokens).Quo(p.UnbondedShares) -} - -//_______________________________________________________________________ - -func (p Pool) addTokensUnbonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) - p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount) - p.UnbondedTokens = p.UnbondedTokens.Add(amount) - return p, NewUnbondedShares(issuedSharesAmount) -} - -func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.unbondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.UnbondedShares = p.UnbondedShares.Sub(shares) - p.UnbondedTokens = p.UnbondedTokens.Sub(removedTokens) - return p, removedTokens -} - -func (p Pool) addTokensUnbonding(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens) - p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount) - p.UnbondingTokens = p.UnbondingTokens.Add(amount) - return p, NewUnbondingShares(issuedSharesAmount) -} - -func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.unbondingShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.UnbondingShares = p.UnbondingShares.Sub(shares) - p.UnbondingTokens = p.UnbondingTokens.Sub(removedTokens) - return p, removedTokens -} - -func (p Pool) addTokensBonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) { - issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) - p.BondedShares = p.BondedShares.Add(issuedSharesAmount) - p.BondedTokens = p.BondedTokens.Add(amount) - return p, NewBondedShares(issuedSharesAmount) -} - -func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) { - removedTokens = sdk.NewIntFromBigInt(p.bondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares - p.BondedShares = p.BondedShares.Sub(shares) - p.BondedTokens = p.BondedTokens.Sub(removedTokens) - return p, removedTokens -} diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go deleted file mode 100644 index 62ecdd1a61ea..000000000000 --- a/x/stake/pool_test.go +++ /dev/null @@ -1,131 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -func TestBondedRatio(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.LooseUnbondedTokens = sdk.NewInt(1) - pool.BondedTokens = sdk.NewInt(2) - - // bonded pool / total supply - require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) - - // avoids divide-by-zero - pool.LooseUnbondedTokens = sdk.NewInt(0) - pool.BondedTokens = sdk.NewInt(0) - require.Equal(t, pool.bondedRatio(), sdk.ZeroRat()) -} - -func TestBondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.BondedTokens = sdk.NewInt(3) - pool.BondedShares = sdk.NewRat(10) - - // bonded pool / bonded shares - require.Equal(t, pool.bondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.BondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.bondedShareExRate(), sdk.OneRat()) -} - -func TestUnbondingShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.UnbondingTokens = sdk.NewInt(3) - pool.UnbondingShares = sdk.NewRat(10) - - // unbonding pool / unbonding shares - require.Equal(t, pool.unbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondingShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondingShareExRate(), sdk.OneRat()) -} - -func TestUnbondedShareExRate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - pool.UnbondedTokens = sdk.NewInt(3) - pool.UnbondedShares = sdk.NewRat(10) - - // unbonded pool / unbonded shares - require.Equal(t, pool.unbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) - pool.UnbondedShares = sdk.ZeroRat() - - // avoids divide-by-zero - require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat()) -} - -func TestAddTokensBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensBonded(sdk.NewInt(10)) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Add(sdk.NewInt(10))) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens))) -} - -func TestRemoveSharesBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) - assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) - - // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Sub(tokensB)) - - // same number of bonded shares / tokens when exchange rate is one - assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens))) -} - -func TestAddTokensUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, sharesB := poolA.addTokensUnbonded(sdk.NewInt(10)) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and unbonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Add(sdk.NewInt(10))) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens))) -} - -func TestRemoveSharesUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) - assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) - - // correct changes to unbonded shares and bonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Sub(tokensB)) - - // same number of unbonded shares / tokens when exchange rate is one - assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens))) -} diff --git a/x/stake/stake.go b/x/stake/stake.go new file mode 100644 index 000000000000..5e4356b814f5 --- /dev/null +++ b/x/stake/stake.go @@ -0,0 +1,145 @@ +// nolint +package stake + +import ( + "github.com/cosmos/cosmos-sdk/x/stake/keeper" + "github.com/cosmos/cosmos-sdk/x/stake/tags" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +// keeper +type Keeper = keeper.Keeper + +var NewKeeper = keeper.NewKeeper + +// types +type Validator = types.Validator +type Description = types.Description +type Delegation = types.Delegation +type UnbondingDelegation = types.UnbondingDelegation +type Redelegation = types.Redelegation +type Params = types.Params +type Pool = types.Pool +type PoolShares = types.PoolShares +type MsgCreateValidator = types.MsgCreateValidator +type MsgEditValidator = types.MsgEditValidator +type MsgDelegate = types.MsgDelegate +type MsgBeginUnbonding = types.MsgBeginUnbonding +type MsgCompleteUnbonding = types.MsgCompleteUnbonding +type MsgBeginRedelegate = types.MsgBeginRedelegate +type MsgCompleteRedelegate = types.MsgCompleteRedelegate +type GenesisState = types.GenesisState + +var ( + GetValidatorKey = keeper.GetValidatorKey + GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey + GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey + GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey + GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey + GetDelegationKey = keeper.GetDelegationKey + GetDelegationsKey = keeper.GetDelegationsKey + ParamKey = keeper.ParamKey + PoolKey = keeper.PoolKey + ValidatorsKey = keeper.ValidatorsKey + ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey + ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey + ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey + ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey + ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey + TendermintUpdatesKey = keeper.TendermintUpdatesKey + DelegationKey = keeper.DelegationKey + IntraTxCounterKey = keeper.IntraTxCounterKey + GetUBDKey = keeper.GetUBDKey + GetUBDByValIndexKey = keeper.GetUBDByValIndexKey + GetUBDsKey = keeper.GetUBDsKey + GetUBDsByValIndexKey = keeper.GetUBDsByValIndexKey + GetREDKey = keeper.GetREDKey + GetREDByValSrcIndexKey = keeper.GetREDByValSrcIndexKey + GetREDByValDstIndexKey = keeper.GetREDByValDstIndexKey + GetREDsKey = keeper.GetREDsKey + GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey + GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey + GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + + DefaultParams = types.DefaultParams + InitialPool = types.InitialPool + NewUnbondedShares = types.NewUnbondedShares + NewUnbondingShares = types.NewUnbondingShares + NewBondedShares = types.NewBondedShares + NewValidator = types.NewValidator + NewDescription = types.NewDescription + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterWire = types.RegisterWire + + // messages + NewMsgCreateValidator = types.NewMsgCreateValidator + NewMsgEditValidator = types.NewMsgEditValidator + NewMsgDelegate = types.NewMsgDelegate + NewMsgBeginUnbonding = types.NewMsgBeginUnbonding + NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding + NewMsgBeginRedelegate = types.NewMsgBeginRedelegate + NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate +) + +// errors +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidValidator = types.CodeInvalidValidator + CodeInvalidDelegation = types.CodeInvalidDelegation + CodeInvalidInput = types.CodeInvalidInput + CodeValidatorJailed = types.CodeValidatorJailed + CodeUnauthorized = types.CodeUnauthorized + CodeInternal = types.CodeInternal + CodeUnknownRequest = types.CodeUnknownRequest +) + +var ( + ErrNilValidatorAddr = types.ErrNilValidatorAddr + ErrNoValidatorFound = types.ErrNoValidatorFound + ErrValidatorAlreadyExists = types.ErrValidatorAlreadyExists + ErrValidatorRevoked = types.ErrValidatorRevoked + ErrBadRemoveValidator = types.ErrBadRemoveValidator + ErrDescriptionLength = types.ErrDescriptionLength + ErrCommissionNegative = types.ErrCommissionNegative + ErrCommissionHuge = types.ErrCommissionHuge + + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrBadDenom = types.ErrBadDenom + ErrBadDelegationAmount = types.ErrBadDelegationAmount + ErrNoDelegation = types.ErrNoDelegation + ErrBadDelegatorAddr = types.ErrBadDelegatorAddr + ErrNoDelegatorForAddress = types.ErrNoDelegatorForAddress + ErrInsufficientShares = types.ErrInsufficientShares + ErrDelegationValidatorEmpty = types.ErrDelegationValidatorEmpty + ErrNotEnoughDelegationShares = types.ErrNotEnoughDelegationShares + ErrBadSharesAmount = types.ErrBadSharesAmount + ErrBadSharesPercent = types.ErrBadSharesPercent + + ErrNotMature = types.ErrNotMature + ErrNoUnbondingDelegation = types.ErrNoUnbondingDelegation + ErrNoRedelegation = types.ErrNoRedelegation + ErrBadRedelegationDst = types.ErrBadRedelegationDst + + ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven + ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven + ErrMissingSignature = types.ErrMissingSignature +) + +// tags +var ( + ActionCreateValidator = tags.ActionCreateValidator + ActionEditValidator = tags.ActionEditValidator + ActionDelegate = tags.ActionDelegate + ActionBeginUnbonding = tags.ActionBeginUnbonding + ActionCompleteUnbonding = tags.ActionCompleteUnbonding + ActionBeginRedelegation = tags.ActionBeginRedelegation + ActionCompleteRedelegation = tags.ActionCompleteRedelegation + + TagAction = tags.Action + TagSrcValidator = tags.SrcValidator + TagDstValidator = tags.DstValidator + TagDelegator = tags.Delegator + TagMoniker = tags.Moniker + TagIdentity = tags.Identity +) diff --git a/x/stake/tags/tags.go b/x/stake/tags/tags.go new file mode 100644 index 000000000000..edb4eda07b93 --- /dev/null +++ b/x/stake/tags/tags.go @@ -0,0 +1,23 @@ +// nolint +package tags + +import ( + "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionCreateValidator = []byte("create-validator") + ActionEditValidator = []byte("edit-validator") + ActionDelegate = []byte("delegate") + ActionBeginUnbonding = []byte("begin-unbonding") + ActionCompleteUnbonding = []byte("complete-unbonding") + ActionBeginRedelegation = []byte("begin-redelegation") + ActionCompleteRedelegation = []byte("complete-redelegation") + + Action = types.TagAction + SrcValidator = types.TagSrcValidator + DstValidator = types.TagDstValidator + Delegator = types.TagDelegator + Moniker = "moniker" + Identity = "Identity" +) diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go new file mode 100644 index 000000000000..235e1e60811d --- /dev/null +++ b/x/stake/types/delegation.go @@ -0,0 +1,140 @@ +package types + +import ( + "bytes" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type Delegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + Shares sdk.Rat `json:"shares"` + Height int64 `json:"height"` // Last height bond updated +} + +// two are equal +func (d Delegation) Equal(d2 Delegation) bool { + return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && + bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && + d.Height == d2.Height && + d.Shares.Equal(d2.Shares) +} + +// ensure fulfills the sdk validator types +var _ sdk.Delegation = Delegation{} + +// nolint - for sdk.Delegation +func (d Delegation) GetDelegator() sdk.Address { return d.DelegatorAddr } +func (d Delegation) GetValidator() sdk.Address { return d.ValidatorAddr } +func (d Delegation) GetBondShares() sdk.Rat { return d.Shares } + +//Human Friendly pretty printer +func (d Delegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) + if err != nil { + return "", err + } + resp := "Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Validator: %s\n", bechVal) + resp += fmt.Sprintf("Shares: %s", d.Shares.String()) + resp += fmt.Sprintf("Height: %d", d.Height) + + return resp, nil + +} + +//__________________________________________________________________ + +// element stored to represent the passive unbonding queue +type UnbondingDelegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator + ValidatorAddr sdk.Address `json:"validator_addr"` // validator unbonding from owner addr + CreationHeight int64 `json:"creation_height"` // height which the unbonding took place + MinTime int64 `json:"min_time"` // unix time for unbonding completion + Balance sdk.Coin `json:"balance"` // atoms to receive at completion +} + +// nolint +func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +//Human Friendly pretty printer +func (d UnbondingDelegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr) + if err != nil { + return "", err + } + resp := "Unbonding Delegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Validator: %s\n", bechVal) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Expected balance: %s", d.Balance.String()) + + return resp, nil + +} + +//__________________________________________________________________ + +// element stored to represent the passive redelegation queue +type Redelegation struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator + ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` // validator redelegation source owner addr + ValidatorDstAddr sdk.Address `json:"validator_dst_addr"` // validator redelegation destination owner addr + CreationHeight int64 `json:"creation_height"` // height which the redelegation took place + MinTime int64 `json:"min_time"` // unix time for redelegation completion + SharesSrc sdk.Rat `json:"shares` // amount of source shares redelegating + SharesDst sdk.Rat `json:"shares` // amount of destination shares redelegating +} + +// nolint +func (d Redelegation) Equal(d2 Redelegation) bool { + bz1 := MsgCdc.MustMarshalBinary(&d) + bz2 := MsgCdc.MustMarshalBinary(&d2) + return bytes.Equal(bz1, bz2) +} + +//Human Friendly pretty printer +func (d Redelegation) HumanReadableString() (string, error) { + bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr) + if err != nil { + return "", err + } + bechValSrc, err := sdk.Bech32ifyAcc(d.ValidatorSrcAddr) + if err != nil { + return "", err + } + bechValDst, err := sdk.Bech32ifyAcc(d.ValidatorDstAddr) + if err != nil { + return "", err + } + resp := "Redelegation \n" + resp += fmt.Sprintf("Delegator: %s\n", bechAcc) + resp += fmt.Sprintf("Source Validator: %s\n", bechValSrc) + resp += fmt.Sprintf("Destination Validator: %s\n", bechValDst) + resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight) + resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime) + resp += fmt.Sprintf("Source shares: %s", d.SharesSrc.String()) + resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String()) + + return resp, nil + +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go new file mode 100644 index 000000000000..622bd0e1a9da --- /dev/null +++ b/x/stake/types/errors.go @@ -0,0 +1,115 @@ +// nolint +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CodeType = sdk.CodeType + +const ( + DefaultCodespace sdk.CodespaceType = 4 + + CodeInvalidValidator CodeType = 101 + CodeInvalidDelegation CodeType = 102 + CodeInvalidInput CodeType = 103 + CodeValidatorJailed CodeType = 104 + CodeUnauthorized CodeType = sdk.CodeUnauthorized + CodeInternal CodeType = sdk.CodeInternal + CodeUnknownRequest CodeType = sdk.CodeUnknownRequest +) + +//validator +func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil") +} +func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address") +} +func ErrValidatorAlreadyExists(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator") +} +func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked") +} +func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator") +} +func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error { + msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max) + return sdk.NewError(codespace, CodeInvalidValidator, msg) +} +func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive") +} +func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%") +} + +// delegation +func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil") +} +func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination") +} +func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0") +} +func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair") +} +func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address") +} +func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation") +} +func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares") +} +func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator") +} +func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares)) +} +func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0") +} +func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1") +} + +// redelegation +func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error { + msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v", + operation, descriptor, got, min) + return sdk.NewError(codespace, CodeUnauthorized, msg) +} +func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") +} +func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") +} +func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") +} +func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") +} + +// messages +func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") +} +func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided") +} +func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidValidator, "missing signature") +} diff --git a/x/stake/types/genesis.go b/x/stake/types/genesis.go new file mode 100644 index 000000000000..d08c6b899341 --- /dev/null +++ b/x/stake/types/genesis.go @@ -0,0 +1,26 @@ +package types + +// GenesisState - all staking state that must be provided at genesis +type GenesisState struct { + Pool Pool `json:"pool"` + Params Params `json:"params"` + Validators []Validator `json:"validators"` + Bonds []Delegation `json:"bonds"` +} + +func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState { + return GenesisState{ + Pool: pool, + Params: params, + Validators: validators, + Bonds: bonds, + } +} + +// get raw genesis raw message for testing +func DefaultGenesisState() GenesisState { + return GenesisState{ + Pool: InitialPool(), + Params: DefaultParams(), + } +} diff --git a/x/stake/types/msg.go b/x/stake/types/msg.go new file mode 100644 index 000000000000..d5a568c39efd --- /dev/null +++ b/x/stake/types/msg.go @@ -0,0 +1,387 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// name to idetify transaction types +const MsgType = "stake" + +//Verify interface at compile time +var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{} +var _, _ sdk.Msg = &MsgBeginUnbonding{}, &MsgCompleteUnbonding{} +var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{} + +//______________________________________________________________________ + +// MsgCreateValidator - struct for unbonding transactions +type MsgCreateValidator struct { + Description + ValidatorAddr sdk.Address `json:"address"` + PubKey crypto.PubKey `json:"pubkey"` + SelfDelegation sdk.Coin `json:"self_delegation"` +} + +func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey, + selfDelegation sdk.Coin, description Description) MsgCreateValidator { + return MsgCreateValidator{ + Description: description, + ValidatorAddr: validatorAddr, + PubKey: pubkey, + SelfDelegation: selfDelegation, + } +} + +//nolint +func (msg MsgCreateValidator) Type() string { return MsgType } +func (msg MsgCreateValidator) GetSigners() []sdk.Address { + return []sdk.Address{msg.ValidatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgCreateValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + PubKey string `json:"pubkey"` + Bond sdk.Coin `json:"bond"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + PubKey: sdk.MustBech32ifyValPub(msg.PubKey), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCreateValidator) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.SelfDelegation.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included") + } + return nil +} + +//______________________________________________________________________ + +// MsgEditValidator - struct for editing a validator +type MsgEditValidator struct { + Description + ValidatorAddr sdk.Address `json:"address"` +} + +func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator { + return MsgEditValidator{ + Description: description, + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgEditValidator) Type() string { return MsgType } +func (msg MsgEditValidator) GetSigners() []sdk.Address { + return []sdk.Address{msg.ValidatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgEditValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + ValidatorAddr string `json:"address"` + }{ + Description: msg.Description, + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgEditValidator) ValidateBasic() sdk.Error { + if msg.ValidatorAddr == nil { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address") + } + empty := Description{} + if msg.Description == empty { + return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify") + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgDelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + Bond sdk.Coin `json:"bond"` +} + +func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + Bond: bond, + } +} + +//nolint +func (msg MsgDelegate) Type() string { return MsgType } +func (msg MsgDelegate) GetSigners() []sdk.Address { + return []sdk.Address{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgDelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + Bond sdk.Coin `json:"bond"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + Bond: msg.Bond, + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if !(msg.Bond.Amount.GT(sdk.ZeroInt())) { + return ErrBadDelegationAmount(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgBeginRedelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` + ValidatorDstAddr sdk.Address `json:"validator_dst_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginRedelegate { + + return MsgBeginRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginRedelegate) Type() string { return MsgType } +func (msg MsgBeginRedelegate) GetSigners() []sdk.Address { + return []sdk.Address{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgBeginRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorSrcAddr string `json:"validator_src_addr"` + ValidatorDstAddr string `json:"validator_dst_addr"` + SharesAmount string `json:"shares"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr), + ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr), + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + return nil +} + +// MsgDelegate - struct for bonding transactions +type MsgCompleteRedelegate struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorSrcAddr sdk.Address `json:"validator_source_addr"` + ValidatorDstAddr sdk.Address `json:"validator_destination_addr"` +} + +func NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, + validatorDstAddr sdk.Address) MsgCompleteRedelegate { + + return MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddr, + ValidatorSrcAddr: validatorSrcAddr, + ValidatorDstAddr: validatorDstAddr, + } +} + +//nolint +func (msg MsgCompleteRedelegate) Type() string { return MsgType } +func (msg MsgCompleteRedelegate) GetSigners() []sdk.Address { + return []sdk.Address{msg.DelegatorAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgCompleteRedelegate) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorSrcAddr string `json:"validator_src_addr"` + ValidatorDstAddr string `json:"validator_dst_addr"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr), + ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCompleteRedelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorSrcAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.ValidatorDstAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} + +//______________________________________________________________________ + +// MsgBeginUnbonding - struct for unbonding transactions +type MsgBeginUnbonding struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` + SharesAmount sdk.Rat `json:"shares_amount"` +} + +func NewMsgBeginUnbonding(delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginUnbonding { + return MsgBeginUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + SharesAmount: sharesAmount, + } +} + +//nolint +func (msg MsgBeginUnbonding) Type() string { return MsgType } +func (msg MsgBeginUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgBeginUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_addr"` + SharesAmount string `json:"shares_amount"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + SharesAmount: msg.SharesAmount.String(), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + if msg.SharesAmount.LTE(sdk.ZeroRat()) { + return ErrBadSharesAmount(DefaultCodespace) + } + return nil +} + +// MsgCompleteUnbonding - struct for unbonding transactions +type MsgCompleteUnbonding struct { + DelegatorAddr sdk.Address `json:"delegator_addr"` + ValidatorAddr sdk.Address `json:"validator_addr"` +} + +func NewMsgCompleteUnbonding(delegatorAddr, validatorAddr sdk.Address) MsgCompleteUnbonding { + return MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddr, + ValidatorAddr: validatorAddr, + } +} + +//nolint +func (msg MsgCompleteUnbonding) Type() string { return MsgType } +func (msg MsgCompleteUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } + +// get the bytes for the message signer to sign on +func (msg MsgCompleteUnbonding) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + DelegatorAddr string `json:"delegator_addr"` + ValidatorAddr string `json:"validator_src_addr"` + }{ + DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr), + ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr), + }) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgCompleteUnbonding) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrNilDelegatorAddr(DefaultCodespace) + } + if msg.ValidatorAddr == nil { + return ErrNilValidatorAddr(DefaultCodespace) + } + return nil +} diff --git a/x/stake/types/msg_test.go b/x/stake/types/msg_test.go new file mode 100644 index 000000000000..e02ed8c97aa7 --- /dev/null +++ b/x/stake/types/msg_test.go @@ -0,0 +1,226 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +var ( + coinPos = sdk.Coin{"steak", sdk.NewInt(1000)} + coinZero = sdk.Coin{"steak", sdk.NewInt(0)} + coinNeg = sdk.Coin{"steak", sdk.NewInt(-10000)} +) + +// test ValidateBasic for MsgCreateValidator +func TestMsgCreateValidator(t *testing.T) { + tests := []struct { + name, moniker, identity, website, details string + validatorAddr sdk.Address + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool + }{ + {"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true}, + {"partial description", "", "", "c", "", addr1, pk1, coinPos, true}, + {"empty description", "", "", "", "", addr1, pk1, coinPos, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", 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) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.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.Address + 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) + msg := NewMsgEditValidator(tc.validatorAddr, description) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgDelegate +func TestMsgDelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorAddr sdk.Address + bond sdk.Coin + expectPass bool + }{ + {"basic good", addr1, addr2, coinPos, true}, + {"self bond", addr1, addr1, coinPos, true}, + {"empty delegator", emptyAddr, addr1, coinPos, false}, + {"empty validator", addr1, emptyAddr, coinPos, false}, + {"empty bond", addr1, addr2, coinZero, false}, + {"negative bond", addr1, addr2, coinNeg, false}, + } + + for _, tc := range tests { + msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgBeginRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorSrcAddr sdk.Address + validatorDstAddr sdk.Address + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, addr3, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, addr3, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, addr3, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, addr3, sdk.NewRat(1, 10), false}, + {"empty source validator", addr1, emptyAddr, addr3, sdk.NewRat(1, 10), false}, + {"empty destination validator", addr1, addr2, emptyAddr, sdk.NewRat(1, 10), 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 TestMsgCompleteRedelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorSrcAddr sdk.Address + validatorDstAddr sdk.Address + expectPass bool + }{ + {"regular", addr1, addr2, addr3, true}, + {"empty delegator", emptyAddr, addr1, addr3, false}, + {"empty source validator", addr1, emptyAddr, addr3, false}, + {"empty destination validator", addr1, addr2, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr) + 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.Address + validatorAddr sdk.Address + sharesAmount sdk.Rat + expectPass bool + }{ + {"regular", addr1, addr2, sdk.NewRat(1, 10), true}, + {"negative decimal", addr1, addr2, sdk.NewRat(-1, 10), false}, + {"zero amount", addr1, addr2, sdk.ZeroRat(), false}, + {"empty delegator", emptyAddr, addr1, sdk.NewRat(1, 10), false}, + {"empty validator", addr1, emptyAddr, sdk.NewRat(1, 10), 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) + } + } +} + +// test ValidateBasic for MsgUnbond +func TestMsgCompleteUnbonding(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + validatorAddr sdk.Address + expectPass bool + }{ + {"regular", addr1, addr2, true}, + {"empty delegator", emptyAddr, addr1, false}, + {"empty validator", addr1, emptyAddr, false}, + } + + for _, tc := range tests { + msg := NewMsgCompleteUnbonding(tc.delegatorAddr, tc.validatorAddr) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } + } +} + +// TODO introduce with go-amino +//func TestSerializeMsg(t *testing.T) { + +//// make sure all types construct properly +//bondAmt := 1234321 +//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} + +//tests := []struct { +//tx sdk.Msg +//}{ +//{NewMsgCreateValidator(addr1, pk1, bond, Description{})}, +//{NewMsgEditValidator(addr1, Description{})}, +//{NewMsgDelegate(addr1, addr2, bond)}, +//{NewMsgUnbond(addr1, addr2, strconv.Itoa(bondAmt))}, +//} + +//for i, tc := range tests { +//var tx sdk.Tx +//bs := wire.BinaryBytes(tc.tx) +//err := wire.ReadBinaryBytes(bs, &tx) +//if assert.NoError(t, err, "%d", i) { +//assert.Equal(t, tc.tx, tx, "%d", i) +//} +//} +//} diff --git a/x/stake/params.go b/x/stake/types/params.go similarity index 80% rename from x/stake/params.go rename to x/stake/types/params.go index 026bd871f162..8c1b897f2e53 100644 --- a/x/stake/params.go +++ b/x/stake/types/params.go @@ -1,4 +1,4 @@ -package stake +package types import ( "bytes" @@ -13,13 +13,16 @@ type Params struct { InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + UnbondingTime int64 `json:"unbonding_time"` + MaxValidators uint16 `json:"max_validators"` // maximum number of validators BondDenom string `json:"bond_denom"` // bondable coin denomination } -func (p Params) equal(p2 Params) bool { - bz1 := msgCdc.MustMarshalBinary(&p) - bz2 := msgCdc.MustMarshalBinary(&p2) +// nolint +func (p Params) Equal(p2 Params) bool { + bz1 := MsgCdc.MustMarshalBinary(&p) + bz2 := MsgCdc.MustMarshalBinary(&p2) return bytes.Equal(bz1, bz2) } @@ -30,6 +33,7 @@ func DefaultParams() Params { InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), + UnbondingTime: 60 * 60 * 24 * 3, // 3 weeks in seconds MaxValidators: 100, BondDenom: "steak", } diff --git a/x/stake/types/pool.go b/x/stake/types/pool.go new file mode 100644 index 000000000000..cb2ad240ae3b --- /dev/null +++ b/x/stake/types/pool.go @@ -0,0 +1,159 @@ +package types + +import ( + "bytes" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Pool - dynamic parameters of the current state +type Pool struct { + LooseTokens int64 `json:"loose_tokens"` // tokens not associated with any validator + UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators + UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool + BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens + UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool + BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation sdk.Rat `json:"inflation"` // current annual inflation rate + + DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily) + + // Fee Related + PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations +} + +// nolint +func (p Pool) Equal(p2 Pool) bool { + bz1 := MsgCdc.MustMarshalBinary(&p) + bz2 := MsgCdc.MustMarshalBinary(&p2) + return bytes.Equal(bz1, bz2) +} + +// initial pool for testing +func InitialPool() Pool { + return Pool{ + LooseTokens: 0, + BondedTokens: 0, + UnbondingTokens: 0, + UnbondedTokens: 0, + BondedShares: sdk.ZeroRat(), + UnbondingShares: sdk.ZeroRat(), + UnbondedShares: sdk.ZeroRat(), + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + DateLastCommissionReset: 0, + PrevBondedShares: sdk.ZeroRat(), + } +} + +//____________________________________________________________________ + +// Sum total of all staking tokens in the pool +func (p Pool) TokenSupply() int64 { + return p.LooseTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens +} + +//____________________________________________________________________ + +// get the bond ratio of the global state +func (p Pool) BondedRatio() sdk.Rat { + if p.TokenSupply() > 0 { + return sdk.NewRat(p.BondedTokens, p.TokenSupply()) + } + return sdk.ZeroRat() +} + +// get the exchange rate of bonded token per issued share +func (p Pool) BondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares) +} + +// get the exchange rate of unbonding tokens held in validators per issued share +func (p Pool) UnbondingShareExRate() sdk.Rat { + if p.UnbondingShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.UnbondingTokens).Quo(p.UnbondingShares) +} + +// get the exchange rate of unbonded tokens held in validators per issued share +func (p Pool) UnbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { + return sdk.OneRat() + } + return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares) +} + +//_______________________________________________________________________ + +func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondedShareExRate()) // tokens * (shares/tokens) + p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount) + p.UnbondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewUnbondedShares(issuedSharesAmount) +} + +func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.UnbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondedShares = p.UnbondedShares.Sub(shares) + p.UnbondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonded tokens negative, pool: %v", p)) + } + return p, removedTokens +} + +func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondingShareExRate()) // tokens * (shares/tokens) + p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount) + p.UnbondingTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewUnbondingShares(issuedSharesAmount) +} + +func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.UnbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondingShares = p.UnbondingShares.Sub(shares) + p.UnbondingTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: unbonding tokens negative, pool: %v", p)) + } + return p, removedTokens +} + +func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares PoolShares) { + issuedSharesAmount := sdk.NewRat(amount).Quo(p.BondedShareExRate()) // tokens * (shares/tokens) + p.BondedShares = p.BondedShares.Add(issuedSharesAmount) + p.BondedTokens += amount + p.LooseTokens -= amount + if p.LooseTokens < 0 { + panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p)) + } + return p, NewBondedShares(issuedSharesAmount) +} + +func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { + removedTokens = p.BondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.BondedShares = p.BondedShares.Sub(shares) + p.BondedTokens -= removedTokens + p.LooseTokens += removedTokens + if p.UnbondedTokens < 0 { + panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p)) + } + return p, removedTokens +} diff --git a/x/stake/types/pool_test.go b/x/stake/types/pool_test.go new file mode 100644 index 000000000000..21dd4ff4d87a --- /dev/null +++ b/x/stake/types/pool_test.go @@ -0,0 +1,128 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestBondedRatio(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 1 + pool.BondedTokens = 2 + + // bonded pool / total supply + require.Equal(t, pool.BondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) + + // avoids divide-by-zero + pool.LooseTokens = 0 + pool.BondedTokens = 0 + require.Equal(t, pool.BondedRatio(), sdk.ZeroRat()) +} + +func TestBondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.BondedTokens = 3 + pool.BondedShares = sdk.NewRat(10) + + // bonded pool / bonded shares + require.Equal(t, pool.BondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.BondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.BondedShareExRate(), sdk.OneRat()) +} + +func TestUnbondingShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondingTokens = 3 + pool.UnbondingShares = sdk.NewRat(10) + + // unbonding pool / unbonding shares + require.Equal(t, pool.UnbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondingShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondingShareExRate(), sdk.OneRat()) +} + +func TestUnbondedShareExRate(t *testing.T) { + pool := InitialPool() + pool.UnbondedTokens = 3 + pool.UnbondedShares = sdk.NewRat(10) + + // unbonded pool / unbonded shares + require.Equal(t, pool.UnbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10))) + pool.UnbondedShares = sdk.ZeroRat() + + // avoids divide-by-zero + require.Equal(t, pool.UnbondedShareExRate(), sdk.OneRat()) +} + +func TestAddTokensBonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensBonded(10) + assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount)) + assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10) + + // same number of bonded shares / tokens when exchange rate is one + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestRemoveSharesBonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) + assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat()) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB) + + // same number of bonded shares / tokens when exchange rate is one + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) +} + +func TestAddTokensUnbonded(t *testing.T) { + + poolA := InitialPool() + poolA.LooseTokens = 10 + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, sharesB := poolA.addTokensUnbonded(10) + assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and unbonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount)) + assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10) + + // same number of unbonded shares / tokens when exchange rate is one + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} + +func TestRemoveSharesUnbonded(t *testing.T) { + + poolA := InitialPool() + poolA.UnbondedTokens = 10 + poolA.UnbondedShares = sdk.NewRat(10) + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) + assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat()) + + // correct changes to unbonded shares and bonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB) + + // same number of unbonded shares / tokens when exchange rate is one + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) +} diff --git a/x/stake/shares.go b/x/stake/types/shares.go similarity index 86% rename from x/stake/shares.go rename to x/stake/types/shares.go index 81b5ff97e649..09301d0db6c0 100644 --- a/x/stake/shares.go +++ b/x/stake/types/shares.go @@ -1,4 +1,4 @@ -package stake +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -70,10 +70,10 @@ func (s PoolShares) ToUnbonded(p Pool) PoolShares { var amount sdk.Rat switch s.Status { case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.unbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr + exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr case sdk.Unbonded: amount = s.Amount @@ -86,12 +86,12 @@ func (s PoolShares) ToUnbonding(p Pool) PoolShares { var amount sdk.Rat switch s.Status { case sdk.Bonded: - exRate := p.bondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr case sdk.Unbonding: amount = s.Amount case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr } return NewUnbondingShares(amount) @@ -104,10 +104,10 @@ func (s PoolShares) ToBonded(p Pool) PoolShares { case sdk.Bonded: amount = s.Amount case sdk.Unbonding: - exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr case sdk.Unbonded: - exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr } return NewUnbondedShares(amount) @@ -120,11 +120,11 @@ func (s PoolShares) ToBonded(p Pool) PoolShares { func (s PoolShares) Tokens(p Pool) sdk.Rat { switch s.Status { case sdk.Bonded: - return p.bondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + return p.BondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares case sdk.Unbonding: - return p.unbondingShareExRate().Mul(s.Amount) + return p.UnbondingShareExRate().Mul(s.Amount) case sdk.Unbonded: - return p.unbondedShareExRate().Mul(s.Amount) + return p.UnbondedShareExRate().Mul(s.Amount) default: panic("unknown share kind") } diff --git a/x/stake/types/test_common.go b/x/stake/types/test_common.go new file mode 100644 index 000000000000..d98deaeb5ed7 --- /dev/null +++ b/x/stake/types/test_common.go @@ -0,0 +1,201 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" +) + +var ( + // dummy pubkeys/addresses + pk1 = crypto.GenPrivKeyEd25519().PubKey() + pk2 = crypto.GenPrivKeyEd25519().PubKey() + pk3 = crypto.GenPrivKeyEd25519().PubKey() + addr1 = pk1.Address() + addr2 = pk2.Address() + addr3 = pk3.Address() + + emptyAddr sdk.Address + emptyPubkey crypto.PubKey +) + +//______________________________________________________________ + +// any operation that transforms staking state +// takes in RNG instance, pool, validator +// returns updated pool, updated validator, delta tokens, descriptive message +type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string) + +// operation: bond or unbond a validator depending on current status +func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var msg string + var newStatus sdk.BondStatus + if val.Status() == sdk.Bonded { + msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Unbonded + + } else if val.Status() == sdk.Unbonded { + msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newStatus = sdk.Bonded + } + val, pool = val.UpdateStatus(pool, newStatus) + return pool, val, 0, msg +} + +// operation: add a random number of tokens to a validator +func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + tokens := int64(r.Int31n(1000)) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + val, pool, _ = val.AddTokensFromDel(pool, tokens) + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative +} + +// operation: remove a random number of shares from a validator +func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) { + var shares sdk.Rat + for { + shares = sdk.NewRat(int64(r.Int31n(1000))) + if shares.LT(val.DelegatorShares) { + break + } + } + + msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) + + val, pool, tokens := val.RemoveDelShares(pool, shares) + return pool, val, tokens, msg +} + +// pick a random staking operation +func RandomOperation(r *rand.Rand) Operation { + operations := []Operation{ + OpBondOrUnbond, + OpAddTokens, + OpRemoveShares, + } + r.Shuffle(len(operations), func(i, j int) { + operations[i], operations[j] = operations[j], operations[i] + }) + return operations[0] +} + +// ensure invariants that should always be true are true +func AssertInvariants(t *testing.T, msg string, + pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) { + + // total tokens conserved + require.Equal(t, + pOrig.UnbondedTokens+pOrig.BondedTokens, + pMod.UnbondedTokens+pMod.BondedTokens+tokens, + "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", + msg, + pOrig.BondedShares, pOrig.UnbondedShares, + pMod.BondedShares, pMod.UnbondedShares, + pOrig.UnbondedTokens, pOrig.BondedTokens, + pMod.UnbondedTokens, pMod.BondedTokens, tokens) + + // nonnegative bonded shares + require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), + "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // nonnegative unbonded shares + require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), + "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", + msg, pOrig, pMod, tokens) + + // nonnegative bonded ex rate + require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative BondedShareExRate: %d", + msg, pMod.BondedShareExRate().Evaluate()) + + // nonnegative unbonded ex rate + require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d", + msg, pMod.UnbondedShareExRate().Evaluate()) + + for _, vMod := range vMods { + + // nonnegative ex rate + require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", + msg, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // nonnegative poolShares + require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.PoolShares.Bonded(), + vMod.DelegatorShares, + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + // nonnegative delShares + require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", + msg, + vMod.DelegatorShares, + vMod.PoolShares.Bonded(), + vMod.DelegatorShareExRate(pMod), + vMod.Owner, + ) + + } + +} + +//________________________________________________________________________________ +// TODO refactor this random setup + +// generate a random validator +func randomValidator(r *rand.Rand, i int) Validator { + + poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) + delShares := sdk.NewRat(int64(r.Int31n(10000))) + + var pShares PoolShares + if r.Float64() < float64(0.5) { + pShares = NewBondedShares(poolSharesAmt) + } else { + pShares = NewUnbondedShares(poolSharesAmt) + } + return Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: pShares, + DelegatorShares: delShares, + } +} + +// generate a random staking state +func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { + pool := InitialPool() + pool.LooseTokens = 100000 + + validators := make([]Validator, numValidators) + for i := 0; i < numValidators; i++ { + validator := randomValidator(r, i) + if validator.Status() == sdk.Bonded { + pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) + pool.BondedTokens += validator.PoolShares.Bonded().Evaluate() + } else if validator.Status() == sdk.Unbonded { + pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) + pool.UnbondedTokens += validator.PoolShares.Unbonded().Evaluate() + } + validators[i] = validator + } + return pool, validators +} diff --git a/x/stake/validator.go b/x/stake/types/validator.go similarity index 86% rename from x/stake/validator.go rename to x/stake/types/validator.go index ce6e7a7ebcb8..a9561a05fbe2 100644 --- a/x/stake/validator.go +++ b/x/stake/types/validator.go @@ -1,14 +1,15 @@ -package stake +package types import ( "bytes" "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" tmtypes "github.com/tendermint/tendermint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" ) // Validator defines the total amount of bond shares and their exchange rate to @@ -40,9 +41,6 @@ type Validator struct { PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools } -// Validators - list of Validators -type Validators []Validator - // NewValidator - initialize a new validator func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Description) Validator { return Validator{ @@ -64,7 +62,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti } // only the vitals - does not check bond height of IntraTxCounter -func (v Validator) equal(c2 Validator) bool { +func (v Validator) Equal(c2 Validator) bool { return v.PubKey.Equals(c2.PubKey) && bytes.Equal(v.Owner, c2.Owner) && v.PoolShares.Equal(c2.PoolShares) && @@ -97,10 +95,47 @@ func NewDescription(moniker, identity, website, details string) Description { } } -//XXX updateDescription function which enforce limit to number of description characters +// update the description based on input +func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) { + if d.Moniker == "[do-not-modify]" { + d2.Moniker = d.Moniker + } + if d.Identity == "[do-not-modify]" { + d2.Identity = d.Identity + } + if d.Website == "[do-not-modify]" { + d2.Website = d.Website + } + if d.Details == "[do-not-modify]" { + d2.Details = d.Details + } + return Description{ + Moniker: d2.Moniker, + Identity: d2.Identity, + Website: d2.Website, + Details: d2.Details, + }.EnsureLength() +} + +// ensure the length of the description +func (d Description) EnsureLength() (Description, sdk.Error) { + if len(d.Moniker) > 70 { + return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70) + } + if len(d.Identity) > 3000 { + return d, ErrDescriptionLength(DefaultCodespace, "identity", len(d.Identity), 3000) + } + if len(d.Website) > 140 { + return d, ErrDescriptionLength(DefaultCodespace, "website", len(d.Website), 140) + } + if len(d.Details) > 280 { + return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280) + } + return d, nil +} // abci validator from stake validator type -func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { +func (v Validator) ABCIValidator(cdc *wire.Codec) abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: v.PoolShares.Bonded().Evaluate(), @@ -109,7 +144,7 @@ func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { // abci validator from stake validator type // with zero power used for validator updates -func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { +func (v Validator) ABCIValidatorZero(cdc *wire.Codec) abci.Validator { return abci.Validator{ PubKey: tmtypes.TM2PB.PubKey(v.PubKey), Power: 0, @@ -123,7 +158,7 @@ func (v Validator) Status() sdk.BondStatus { // update the location of the shares within a validator if its bond status has changed func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { - var tokens sdk.Int + var tokens int64 switch v.Status() { case sdk.Unbonded: @@ -159,8 +194,8 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, // Remove pool shares // Returns corresponding tokens, which could be burned (e.g. when slashing // a validator) or redistributed elsewhere -func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, sdk.Int) { - var tokens sdk.Int +func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) { + var tokens int64 switch v.Status() { case sdk.Unbonded: pool, tokens = pool.removeSharesUnbonded(poolShares) @@ -173,7 +208,7 @@ func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, P return v, pool, tokens } -// XXX TEST +// TODO remove should only be tokens // get the power or potential power for a validator // if bonded, the power is the BondedShares // if not bonded, the power is the amount of bonded shares which the @@ -184,10 +219,9 @@ func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { //_________________________________________________________________________________________________________ -// XXX Audit this function further to make sure it's correct // add tokens to a validator -func (v Validator) addTokensFromDel(pool Pool, - amount sdk.Int) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { +func (v Validator) AddTokensFromDel(pool Pool, + amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { exRate := v.DelegatorShareExRate(pool) // bshr/delshr @@ -212,8 +246,8 @@ func (v Validator) addTokensFromDel(pool Pool, // remove delegator shares from a validator // NOTE this function assumes the shares have already been updated for the validator status -func (v Validator) removeDelShares(pool Pool, - delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins sdk.Int) { +func (v Validator) RemoveDelShares(pool Pool, + delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { amount := v.DelegatorShareExRate(pool).Mul(delShares) eqBondedSharesToRemove := NewBondedShares(amount) diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go new file mode 100644 index 000000000000..2af3ace407fe --- /dev/null +++ b/x/stake/types/validator_test.go @@ -0,0 +1,232 @@ +package types + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestAddTokensValidatorBonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Bonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) +} + +func TestAddTokensValidatorUnbonding(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) +} + +func TestAddTokensValidatorUnbonded(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 10 + val := NewValidator(addr1, pk1, Description{}) + val, pool = val.UpdateStatus(pool, sdk.Unbonded) + val, pool, delShares := val.AddTokensFromDel(pool, 10) + + assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) + assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate()) + assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate()) + + assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) + assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) +} + +// TODO refactor to make simpler like the AddToken tests above +func TestRemoveDelShares(t *testing.T) { + poolA := InitialPool() + poolA.LooseTokens = 10 + valA := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(sdk.NewRat(100)), + DelegatorShares: sdk.NewRat(100), + } + poolA.BondedTokens = valA.PoolShares.Bonded().Evaluate() + poolA.BondedShares = valA.PoolShares.Bonded() + assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) + assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat()) + assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat()) + valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10)) + + // coins were created + assert.Equal(t, coinsB, int64(10)) + // pool shares were removed + assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) + // conservation of tokens + assert.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens) + + // specific case from random tests + poolShares := sdk.NewRat(5102) + delShares := sdk.NewRat(115) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + BondedShares: sdk.NewRat(248305), + UnbondedShares: sdk.NewRat(232147), + BondedTokens: 248305, + UnbondedTokens: 232147, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + shares := sdk.NewRat(29) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) + _, newPool, tokens := val.RemoveDelShares(pool, shares) + require.Equal(t, + tokens+newPool.UnbondedTokens+newPool.BondedTokens, + pool.BondedTokens+pool.UnbondedTokens, + "Tokens were not conserved: %s", msg) +} + +func TestUpdateStatus(t *testing.T) { + pool := InitialPool() + pool.LooseTokens = 100 + + val := NewValidator(addr1, pk1, Description{}) + val, pool, _ = val.AddTokensFromDel(pool, 100) + assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(0), pool.BondedTokens) + assert.Equal(t, int64(0), pool.UnbondingTokens) + assert.Equal(t, int64(100), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Unbonding) + assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(0), pool.BondedTokens) + assert.Equal(t, int64(100), pool.UnbondingTokens) + assert.Equal(t, int64(0), pool.UnbondedTokens) + + val, pool = val.UpdateStatus(pool, sdk.Bonded) + assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) + assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) + assert.Equal(t, int64(100), pool.BondedTokens) + assert.Equal(t, int64(0), pool.UnbondingTokens) + assert.Equal(t, int64(0), pool.UnbondedTokens) +} + +func TestPossibleOverflow(t *testing.T) { + poolShares := sdk.NewRat(2159) + delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) + val := Validator{ + Owner: addr1, + PubKey: pk1, + PoolShares: NewBondedShares(poolShares), + DelegatorShares: delShares, + } + pool := Pool{ + LooseTokens: 100, + BondedShares: poolShares, + UnbondedShares: sdk.ZeroRat(), + BondedTokens: poolShares.Evaluate(), + UnbondedTokens: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + tokens := int64(71) + msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", + val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) + newValidator, _, _ := val.AddTokensFromDel(pool, tokens) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), + "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", + msg, newValidator.DelegatorShareExRate(pool)) +} + +// run random operations in a random order on a random single-validator state, assert invariants hold +func TestSingleValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(41)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 1) + require.Equal(t, 1, len(validatorsOrig)) + + // sanity check + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0]) + + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + require.Equal(t, 1, len(validatorsOrig), "j %v", j) + require.Equal(t, 1, len(validatorsMod), "j %v", j) + validatorsMod[0] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + } + } +} + +// run random operations in a random order on a random multi-validator state, assert invariants hold +func TestMultiValidatorIntegrationInvariants(t *testing.T) { + r := rand.New(rand.NewSource(42)) + + for i := 0; i < 10; i++ { + poolOrig, validatorsOrig := RandomSetup(r, 100) + + AssertInvariants(t, "no operation", + poolOrig, validatorsOrig, + poolOrig, validatorsOrig, 0) + + for j := 0; j < 5; j++ { + index := int(r.Int31n(int32(len(validatorsOrig)))) + poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index]) + validatorsMod := make([]Validator, len(validatorsOrig)) + copy(validatorsMod[:], validatorsOrig[:]) + validatorsMod[index] = validatorMod + + AssertInvariants(t, msg, + poolOrig, validatorsOrig, + poolMod, validatorsMod, tokens) + + poolOrig = poolMod + validatorsOrig = validatorsMod + + } + } +} diff --git a/x/stake/types/wire.go b/x/stake/types/wire.go new file mode 100644 index 000000000000..86f9f5f09d0f --- /dev/null +++ b/x/stake/types/wire.go @@ -0,0 +1,27 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/wire" +) + +// Register concrete types on wire codec +func RegisterWire(cdc *wire.Codec) { + cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) + cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) + cdc.RegisterConcrete(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil) + cdc.RegisterConcrete(MsgCompleteUnbonding{}, "cosmos-sdk/CompleteUnbonding", nil) + cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil) + cdc.RegisterConcrete(MsgCompleteRedelegate{}, "cosmos-sdk/CompleteRedelegate", nil) +} + +// generic sealed codec to be used throughout sdk +var MsgCdc *wire.Codec + +func init() { + cdc := wire.NewCodec() + RegisterWire(cdc) + wire.RegisterCrypto(cdc) + MsgCdc = cdc + //MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10 +} diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go deleted file mode 100644 index b9540a2feff3..000000000000 --- a/x/stake/validator_test.go +++ /dev/null @@ -1,408 +0,0 @@ -package stake - -import ( - "fmt" - "math/rand" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAddTokensValidatorBonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Bonded) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded())) -} - -func TestAddTokensValidatorUnbonding(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding())) -} - -func TestAddTokensValidatorUnbonded(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - pool := keeper.GetPool(ctx) - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool = val.UpdateStatus(pool, sdk.Unbonded) - val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10)) - - assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool)) - assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate()) - assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate()) - - assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares)) - assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded())) -} - -// TODO refactor to make simpler like the AddToken tests above -func TestRemoveShares(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - poolA := keeper.GetPool(ctx) - valA := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(sdk.NewRat(9)), - DelegatorShares: sdk.NewRat(9), - } - poolA.BondedTokens = valA.PoolShares.Bonded().EvaluateInt() - poolA.BondedShares = valA.PoolShares.Bonded() - assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat()) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - valB, poolB, coinsB := valA.removeDelShares(poolA, sdk.NewRat(10)) - - // coins were created - assert.Equal(t, coinsB.Int64(), int64(10)) - // pool shares were removed - assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA)))) - // conservation of tokens - assert.Equal(t, poolB.UnbondedTokens.Add(poolB.BondedTokens).Add(coinsB), poolA.UnbondedTokens.Add(poolA.BondedTokens)) - - // specific case from random tests - poolShares := sdk.NewRat(5102) - delShares := sdk.NewRat(115) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: sdk.NewRat(248305), - UnbondedShares: sdk.NewRat(232147), - BondedTokens: sdk.NewInt(248305), - UnbondedTokens: sdk.NewInt(232147), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - shares := sdk.NewRat(29) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) - _, newPool, tokens := val.removeDelShares(pool, shares) - require.Equal(t, - tokens.Add(newPool.UnbondedTokens).Add(newPool.BondedTokens), - pool.BondedTokens.Add(pool.UnbondedTokens), - "Tokens were not conserved: %s", msg) -} - -func TestUpdateStatus(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - pool := keeper.GetPool(ctx) - - val := NewValidator(addrs[0], pks[0], Description{}) - val, pool, _ = val.addTokensFromDel(pool, sdk.NewInt(100)) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(100), pool.UnbondedTokens.Int64()) - - val, pool = val.UpdateStatus(pool, sdk.Unbonding) - assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(0), pool.BondedTokens.Int64()) - assert.Equal(t, int64(100), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondedTokens.Int64()) - - val, pool = val.UpdateStatus(pool, sdk.Bonded) - assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate()) - assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate()) - assert.Equal(t, int64(100), pool.BondedTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondingTokens.Int64()) - assert.Equal(t, int64(0), pool.UnbondedTokens.Int64()) -} - -//________________________________________________________________________________ -// TODO refactor this random setup - -// generate a random validator -func randomValidator(r *rand.Rand, i int) Validator { - - poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000))) - delShares := sdk.NewRat(int64(r.Int31n(10000))) - - var pShares PoolShares - if r.Float64() < float64(0.5) { - pShares = NewBondedShares(poolSharesAmt) - } else { - pShares = NewUnbondedShares(poolSharesAmt) - } - return Validator{ - Owner: addrs[i], - PubKey: pks[i], - PoolShares: pShares, - DelegatorShares: delShares, - } -} - -// generate a random staking state -func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) { - pool := InitialPool() - - validators := make([]Validator, numValidators) - for i := 0; i < numValidators; i++ { - validator := randomValidator(r, i) - if validator.Status() == sdk.Bonded { - pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded()) - pool.BondedTokens = pool.BondedTokens.Add(validator.PoolShares.Bonded().EvaluateInt()) - } else if validator.Status() == sdk.Unbonded { - pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded()) - pool.UnbondedTokens = pool.UnbondedTokens.Add(validator.PoolShares.Unbonded().EvaluateInt()) - } - validators[i] = validator - } - return pool, validators -} - -// any operation that transforms staking state -// takes in RNG instance, pool, validator -// returns updated pool, updated validator, delta tokens, descriptive message -type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, sdk.Int, string) - -// operation: bond or unbond a validator depending on current status -func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - var msg string - var newStatus sdk.BondStatus - if val.Status() == sdk.Bonded { - msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Unbonded - - } else if val.Status() == sdk.Unbonded { - msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newStatus = sdk.Bonded - } - val, pool = val.UpdateStatus(pool, newStatus) - return pool, val, sdk.ZeroInt(), msg -} - -// operation: add a random number of tokens to a validator -func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - tokens := sdk.NewInt(int64(r.Int31n(1000))) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - val, pool, _ = val.addTokensFromDel(pool, tokens) - msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg) - return pool, val, tokens.Neg(), msg // tokens are removed so for accounting must be negative -} - -// operation: remove a random number of shares from a validator -func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) { - var shares sdk.Rat - for { - shares = sdk.NewRat(int64(r.Int31n(1000))) - if shares.LT(val.DelegatorShares) { - break - } - } - - msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool)) - - val, pool, tokens := val.removeDelShares(pool, shares) - return pool, val, tokens, msg -} - -// pick a random staking operation -func randomOperation(r *rand.Rand) Operation { - operations := []Operation{ - OpBondOrUnbond, - OpAddTokens, - OpRemoveShares, - } - r.Shuffle(len(operations), func(i, j int) { - operations[i], operations[j] = operations[j], operations[i] - }) - return operations[0] -} - -// ensure invariants that should always be true are true -func assertInvariants(t *testing.T, msg string, - pOrig Pool, cOrig Validators, pMod Pool, vMods Validators, tokens sdk.Int) { - - // total tokens conserved - require.Equal(t, - pOrig.UnbondedTokens.Add(pOrig.BondedTokens), - pMod.UnbondedTokens.Add(pMod.BondedTokens).Add(tokens), - "Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n", - msg, - pOrig.BondedShares, pOrig.UnbondedShares, - pMod.BondedShares, pMod.UnbondedShares, - pOrig.UnbondedTokens, pOrig.BondedTokens, - pMod.UnbondedTokens, pMod.BondedTokens, tokens) - - // nonnegative bonded shares - require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()), - "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative unbonded shares - require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()), - "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n", - msg, pOrig, pMod, tokens) - - // nonnegative bonded ex rate - require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative bondedShareExRate: %d", - msg, pMod.bondedShareExRate().Evaluate()) - - // nonnegative unbonded ex rate - require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative unbondedShareExRate: %d", - msg, pMod.unbondedShareExRate().Evaluate()) - - for _, vMod := range vMods { - - // nonnegative ex rate - require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", - msg, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative poolShares - require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.PoolShares.Bonded(), - vMod.DelegatorShares, - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - // nonnegative delShares - require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)", - msg, - vMod.DelegatorShares, - vMod.PoolShares.Bonded(), - vMod.DelegatorShareExRate(pMod), - vMod.Owner, - ) - - } - -} - -func TestPossibleOverflow(t *testing.T) { - poolShares := sdk.NewRat(2159) - delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) - val := Validator{ - Owner: addrs[0], - PubKey: pks[0], - PoolShares: NewBondedShares(poolShares), - DelegatorShares: delShares, - } - pool := Pool{ - BondedShares: poolShares, - UnbondedShares: sdk.ZeroRat(), - BondedTokens: poolShares.EvaluateInt(), - UnbondedTokens: sdk.ZeroInt(), - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - tokens := sdk.NewInt(71) - msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)", - val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool)) - newValidator, _, _ := val.addTokensFromDel(pool, tokens) - - msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg) - require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v", - msg, newValidator.DelegatorShareExRate(pool)) -} - -// run random operations in a random order on a random single-validator state, assert invariants hold -func TestSingleValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(41)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 1) - require.Equal(t, 1, len(validatorsOrig)) - - // sanity check - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, sdk.ZeroInt()) - - for j := 0; j < 5; j++ { - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0]) - - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - require.Equal(t, 1, len(validatorsOrig), "j %v", j) - require.Equal(t, 1, len(validatorsMod), "j %v", j) - validatorsMod[0] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - } - } -} - -// run random operations in a random order on a random multi-validator state, assert invariants hold -func TestMultiValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(42)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := randomSetup(r, 100) - - assertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig, sdk.ZeroInt()) - - for j := 0; j < 5; j++ { - index := int(r.Int31n(int32(len(validatorsOrig)))) - poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[index]) - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - validatorsMod[index] = validatorMod - - assertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod, tokens) - - poolOrig = poolMod - validatorsOrig = validatorsMod - - } - } -} diff --git a/x/stake/view_slash_keeper.go b/x/stake/view_slash_keeper.go deleted file mode 100644 index cb7d16ce9480..000000000000 --- a/x/stake/view_slash_keeper.go +++ /dev/null @@ -1,29 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// keeper to view information & slash validators -// will be used by governance module -type ViewSlashKeeper struct { - keeper Keeper -} - -// NewViewSlashKeeper creates a keeper restricted to -// viewing information & slashing validators -func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { - return ViewSlashKeeper{k} -} - -// load a delegator bond -func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context, - delegatorAddr sdk.Address, validatorAddr sdk.Address) (bond Delegation, found bool) { - return v.keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) -} - -// load n delegator bonds -func (v ViewSlashKeeper) GetDelegations(ctx sdk.Context, - delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { - return v.keeper.GetDelegations(ctx, delegator, maxRetrieve) -} diff --git a/x/stake/view_slash_keeper_test.go b/x/stake/view_slash_keeper_test.go deleted file mode 100644 index a65c3fb386a3..000000000000 --- a/x/stake/view_slash_keeper_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package stake - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// tests GetDelegation, GetDelegations -func TestViewSlashBond(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0)) - - //construct the validators - amts := []int64{9, 8, 7} - var validators [3]Validator - for i, amt := range amts { - validators[i] = Validator{ - Owner: addrVals[i], - PubKey: pks[i], - PoolShares: NewUnbondedShares(sdk.NewRat(amt)), - DelegatorShares: sdk.NewRat(amt), - } - } - - // first add a validators[0] to delegate too - keeper.updateValidator(ctx, validators[0]) - - bond1to1 := Delegation{ - DelegatorAddr: addrDels[0], - ValidatorAddr: addrVals[0], - Shares: sdk.NewRat(9), - } - - viewSlashKeeper := NewViewSlashKeeper(keeper) - - // check the empty keeper first - _, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.False(t, found) - - // set and retrieve a record - keeper.setDelegation(ctx, bond1to1) - resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // modify a records, save, and retrieve - bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegation(ctx, bond1to1) - resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) - assert.True(t, found) - assert.True(t, bond1to1.equal(resBond)) - - // add some more records - keeper.updateValidator(ctx, validators[1]) - keeper.updateValidator(ctx, validators[2]) - bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(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 := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond1to1.equal(resBonds[0])) - assert.True(t, bond1to2.equal(resBonds[1])) - assert.True(t, bond1to3.equal(resBonds[2])) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 3) - require.Equal(t, 3, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2) - require.Equal(t, 2, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5) - require.Equal(t, 3, len(resBonds)) - assert.True(t, bond2to1.equal(resBonds[0])) - assert.True(t, bond2to2.equal(resBonds[1])) - assert.True(t, bond2to3.equal(resBonds[2])) - -} diff --git a/x/stake/wire.go b/x/stake/wire.go deleted file mode 100644 index c0b0be71fa43..000000000000 --- a/x/stake/wire.go +++ /dev/null @@ -1,20 +0,0 @@ -package stake - -import ( - "github.com/cosmos/cosmos-sdk/wire" -) - -// Register concrete types on wire codec -func RegisterWire(cdc *wire.Codec) { - cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) - cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil) - cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) - cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) -} - -var msgCdc = wire.NewCodec() - -func init() { - RegisterWire(msgCdc) - wire.RegisterCrypto(msgCdc) -}