diff --git a/CHANGELOG.md b/CHANGELOG.md index 5467d0b748..73f5e5f880 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,24 @@ BUG FIXES *TBD* +BREAKING CHANGES + +* [stake] candidate -> validator throughout (details in refactor comment) +* [stake] delegate-bond -> delegation throughout +* [stake] `gaiacli query validator` takes and argument instead of using the `--address-candidate` flag +* [stake] introduce `gaiacli query delegations` +* [stake] staking refactor + * ValidatorsBonded store now take sorted pubKey-address instead of validator owner-address, + is sorted like Tendermint by pk's address + * store names more understandable + * removed temporary ToKick store, just needs a local map! + * removed distinction between candidates and validators + * everything is now a validator + * only validators with a status == bonded are actively validating/receiving rewards + * Introduction of Unbonding fields, lowlevel logic throughout (not fully implemented with queue) + * Introduction of PoolShares type within validators, + replaces three rational fields (BondedShares, UnbondingShares, UnbondedShares + FEATURES * [x/auth] Added ability to change pubkey to auth module @@ -18,6 +36,17 @@ FEATURES * Transactions which run out of gas stop execution and revert state changes * A "simulate" query has been added to determine how much gas a transaction will need * Modules can include their own gas costs for execution of particular message types +* [stake] Seperation of fee distribution to a new module +* [stake] Creation of a validator/delegation generics in `/types` +* [stake] Helper Description of the store in x/stake/store.md +* [stake] removed use of caches in the stake keeper + +BUG FIXES + +* Auto-sequencing now works correctly +* [stake] staking delegator shares exchange rate now relative to equivalent-bonded-tokens the validator has instead of bonded tokens + ^ this is important for unbonded validators in the power store! + ## 0.17.2 @@ -34,6 +63,7 @@ Update to Tendermint v0.19.4 (fixes a consensus bug and improves logging) BREAKING CHANGES * [stake] MarshalJSON -> MarshalBinary +* Queries against the store must be prefixed with the path "/store" FEATURES @@ -71,7 +101,6 @@ BREAKING CHANGES * gaiad init now requires use of `--name` flag * Removed Get from Msg interface * types/rational now extends big.Rat -* Queries against the store must be prefixed with the path "/store" FEATURES: diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 5ff532bffa..42869c9558 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + feed "github.com/cosmos/cosmos-sdk/x/fee_distribution" "github.com/cosmos/cosmos-sdk/x/ibc" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -81,7 +82,7 @@ func NewGaiaApp(logger log.Logger, db dbm.DB) *GaiaApp { app.SetInitChainer(app.initChainer) app.SetEndBlocker(stake.NewEndBlocker(app.stakeKeeper)) app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyIBC, app.keyStake) - app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, stake.FeeHandler)) + app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, feed.BurnFeeHandler)) err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index fd26ce27a3..42a56959ca 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -105,7 +105,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := GenesisState{ Accounts: genaccs, - StakeData: stake.GetDefaultGenesisState(), + StakeData: stake.DefaultGenesisState(), } stateBytes, err := wire.MarshalJSONIndent(gapp.cdc, genesisState) @@ -147,7 +147,7 @@ func setGenesisAccounts(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := GenesisState{ Accounts: genaccs, - StakeData: stake.GetDefaultGenesisState(), + StakeData: stake.DefaultGenesisState(), } stateBytes, err := json.MarshalIndent(genesisState, "", "\t") @@ -405,9 +405,15 @@ func TestStakeMsgs(t *testing.T) { ctxDeliver := gapp.BaseApp.NewContext(false, abci.Header{}) res1 = gapp.accountMapper.GetAccount(ctxDeliver, addr1) require.Equal(t, genCoins.Minus(sdk.Coins{bondCoin}), res1.GetCoins()) - candidate, found := gapp.stakeKeeper.GetCandidate(ctxDeliver, addr1) + validator, found := gapp.stakeKeeper.GetValidator(ctxDeliver, addr1) require.True(t, found) - require.Equal(t, candidate.Address, addr1) + require.Equal(t, addr1, validator.Owner) + require.Equal(t, sdk.Bonded, validator.Status()) + require.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded())) + + // check the bond that should have been created as well + bond, found := gapp.stakeKeeper.GetDelegation(ctxDeliver, addr1, addr1) + require.True(sdk.RatEq(t, sdk.NewRat(10), bond.Shares)) // Edit Candidacy @@ -417,9 +423,9 @@ func TestStakeMsgs(t *testing.T) { ) SignDeliver(t, gapp, editCandidacyMsg, []int64{1}, true, priv1) - candidate, found = gapp.stakeKeeper.GetCandidate(ctxDeliver, addr1) + validator, found = gapp.stakeKeeper.GetValidator(ctxDeliver, addr1) require.True(t, found) - require.Equal(t, candidate.Description, description) + require.Equal(t, description, validator.Description) // Delegate @@ -428,12 +434,13 @@ func TestStakeMsgs(t *testing.T) { ) SignDeliver(t, gapp, delegateMsg, []int64{0}, true, priv2) - ctxDeliver = gapp.BaseApp.NewContext(false, abci.Header{}) res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) require.Equal(t, genCoins.Minus(sdk.Coins{bondCoin}), res2.GetCoins()) - bond, found := gapp.stakeKeeper.GetDelegatorBond(ctxDeliver, addr2, addr1) + bond, found = gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) require.True(t, found) - require.Equal(t, bond.DelegatorAddr, addr2) + require.Equal(t, addr2, bond.DelegatorAddr) + require.Equal(t, addr1, bond.ValidatorAddr) + require.True(sdk.RatEq(t, sdk.NewRat(10), bond.Shares)) // Unbond @@ -442,10 +449,9 @@ func TestStakeMsgs(t *testing.T) { ) SignDeliver(t, gapp, unbondMsg, []int64{1}, true, priv2) - ctxDeliver = gapp.BaseApp.NewContext(false, abci.Header{}) res2 = gapp.accountMapper.GetAccount(ctxDeliver, addr2) require.Equal(t, genCoins, res2.GetCoins()) - _, found = gapp.stakeKeeper.GetDelegatorBond(ctxDeliver, addr2, addr1) + _, found = gapp.stakeKeeper.GetDelegation(ctxDeliver, addr2, addr1) require.False(t, found) } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 7cb7564dd3..525fe8ab07 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -135,7 +135,7 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso } // start with the default staking genesis state - stakeData := stake.GetDefaultGenesisState() + stakeData := stake.DefaultGenesisState() // get genesis flag account information genaccs := make([]GenesisAccount, len(appGenTxs)) @@ -155,19 +155,18 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState jso } acc := NewGenesisAccount(&accAuth) genaccs[i] = acc - stakeData.Pool.TotalSupply += freeFermionsAcc // increase the supply + stakeData.Pool.LooseUnbondedTokens += freeFermionsAcc // increase the supply // add the validator if len(genTx.Name) > 0 { desc := stake.NewDescription(genTx.Name, "", "", "") - candidate := stake.NewCandidate(genTx.Address, genTx.PubKey, desc) - candidate.Assets = sdk.NewRat(freeFermionVal) - stakeData.Candidates = append(stakeData.Candidates, candidate) + validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc) + validator.PoolShares = stake.NewBondedShares(sdk.NewRat(freeFermionVal)) + stakeData.Validators = append(stakeData.Validators, validator) // pool logic - stakeData.Pool.TotalSupply += freeFermionVal - stakeData.Pool.BondedPool += freeFermionVal - stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedPool) + stakeData.Pool.BondedTokens += freeFermionVal + stakeData.Pool.BondedShares = sdk.NewRat(stakeData.Pool.BondedTokens) } } diff --git a/cmd/gaia/cli_test/cli_test.go b/cmd/gaia/cli_test/cli_test.go index b4529efd89..2d8b9d6a34 100644 --- a/cmd/gaia/cli_test/cli_test.go +++ b/cmd/gaia/cli_test/cli_test.go @@ -101,7 +101,7 @@ func TestGaiaCLIDeclareCandidacy(t *testing.T) { assert.Equal(t, int64(7), barAcc.GetCoins().AmountOf("steak")) candidate := executeGetCandidate(t, fmt.Sprintf("gaiacli candidate %v --address-candidate=%v", flags, barAddr)) assert.Equal(t, candidate.Address.String(), barAddr) - assert.Equal(t, int64(3), candidate.Assets.Evaluate()) + assert.Equal(t, int64(3), candidate.BondedShares.Evaluate()) // TODO timeout issues if not connected to the internet // unbond a single share @@ -117,7 +117,7 @@ func TestGaiaCLIDeclareCandidacy(t *testing.T) { //barAcc = executeGetAccount(t, fmt.Sprintf("gaiacli account %v %v", barAddr, flags)) //assert.Equal(t, int64(99998), barAcc.GetCoins().AmountOf("steak")) //candidate = executeGetCandidate(t, fmt.Sprintf("gaiacli candidate %v --address-candidate=%v", flags, barAddr)) - //assert.Equal(t, int64(2), candidate.Assets.Evaluate()) + //assert.Equal(t, int64(2), candidate.BondedShares.Evaluate()) } func executeWrite(t *testing.T, cmdStr string, writes ...string) { diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 8de2e3acc2..2f36201b2f 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -45,10 +45,10 @@ func main() { rootCmd.AddCommand( client.GetCommands( authcmd.GetAccountCmd("acc", cdc, authcmd.GetAccountDecoder(cdc)), - stakecmd.GetCmdQueryCandidate("stake", cdc), - stakecmd.GetCmdQueryCandidates("stake", cdc), - stakecmd.GetCmdQueryDelegatorBond("stake", cdc), - //stakecmd.GetCmdQueryDelegatorBonds("stake", cdc), + stakecmd.GetCmdQueryValidator("stake", cdc), + stakecmd.GetCmdQueryValidators("stake", cdc), + stakecmd.GetCmdQueryDelegation("stake", cdc), + stakecmd.GetCmdQueryDelegations("stake", cdc), )...) rootCmd.AddCommand( client.PostCommands( diff --git a/types/rational.go b/types/rational.go index 7e0a091075..0709a350f4 100644 --- a/types/rational.go +++ b/types/rational.go @@ -5,6 +5,7 @@ import ( "math/big" "strconv" "strings" + "testing" ) // "that's one big rat!" @@ -80,17 +81,17 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { } //nolint -func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator -func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator -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 } // Equal - rationals are equal -func (r Rat) GT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 1 } // Equal - rationals are equal -func (r Rat) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // Equal - rationals are equal +func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator +func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator +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) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than 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 func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction -func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } // Sub - subtraction +func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } var ( zero = big.NewInt(0) @@ -168,3 +169,25 @@ func (r *Rat) UnmarshalAmino(text string) (err error) { r.Rat = *tempRat return nil } + +//___________________________________________________________________________________ +// helpers + +// test if two rat arrays are the equal +func RatsEqual(r1s, r2s []Rat) bool { + if len(r1s) != len(r2s) { + return false + } + + for i, r1 := range r1s { + if !r1.Equal(r2s[i]) { + return false + } + } + return true +} + +// intended to be used with require/assert: require.True(RatEq(...)) +func RatEq(t *testing.T, exp, got Rat) (*testing.T, bool, string, Rat, Rat) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +} diff --git a/types/rational_test.go b/types/rational_test.go index e59545bfc7..30abb1a518 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -276,3 +276,24 @@ func TestEmbeddedStructSerializationGoWire(t *testing.T) { assert.Equal(t, obj.Field2, obj2.Field2) assert.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2) } + +func TestRatsEqual(t *testing.T) { + tests := []struct { + r1s, r2s []Rat + eq bool + }{ + {[]Rat{NewRat(0)}, []Rat{NewRat(0)}, true}, + {[]Rat{NewRat(0)}, []Rat{NewRat(1)}, false}, + {[]Rat{NewRat(0)}, []Rat{}, false}, + {[]Rat{NewRat(0), NewRat(1)}, []Rat{NewRat(0), NewRat(1)}, true}, + {[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(1), NewRat(0)}, true}, + {[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(0), NewRat(1)}, false}, + {[]Rat{NewRat(1), NewRat(0)}, []Rat{NewRat(1)}, false}, + } + + for _, tc := range tests { + assert.Equal(t, tc.eq, RatsEqual(tc.r1s, tc.r2s)) + assert.Equal(t, tc.eq, RatsEqual(tc.r2s, tc.r1s)) + } + +} diff --git a/types/stake.go b/types/stake.go new file mode 100644 index 0000000000..df74a705b9 --- /dev/null +++ b/types/stake.go @@ -0,0 +1,65 @@ +package types + +import ( + abci "github.com/tendermint/abci/types" + "github.com/tendermint/go-crypto" +) + +// status of a validator +type BondStatus byte + +// nolint +const ( + Unbonded BondStatus = 0x00 + Unbonding BondStatus = 0x01 + Bonded BondStatus = 0x02 +) + +// validator for a delegated proof of stake system +type Validator interface { + GetStatus() BondStatus // status of the validator + GetOwner() Address // owner address to receive/return validators coins + GetPubKey() crypto.PubKey // validation pubkey + GetPower() Rat // validation power + GetBondHeight() int64 // height in which the validator became active +} + +// validator which fulfills abci validator interface for use in Tendermint +func ABCIValidator(v Validator) abci.Validator { + return abci.Validator{ + PubKey: v.GetPubKey().Bytes(), + Power: v.GetPower().Evaluate(), + } +} + +// properties for the set of all validators +type ValidatorSet interface { + // iterate through validator by owner-address, execute func for each validator + IterateValidators(Context, + func(index int64, validator Validator) (stop bool)) + + // iterate through bonded validator by pubkey-address, execute func for each validator + 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 +} + +//_______________________________________________________________________________ + +// delegation bond for a delegated proof of stake system +type Delegation interface { + GetDelegator() Address // delegator address for the bond + GetValidator() Address // validator owner address for the bond + GetBondShares() Rat // amount of validator's shares +} + +// properties for the set of all delegations for a particular +type DelegationSet interface { + + // iterate through all delegations from one delegator by validator-address, + // execute func for each validator + IterateDelegators(Context, delegator Address, + fn func(index int64, delegation Delegation) (stop bool)) +} diff --git a/x/auth/ante.go b/x/auth/ante.go index 248083206d..21d17fb9be 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -166,5 +166,4 @@ func deductFees(acc sdk.Account, fee sdk.StdFee) (sdk.Account, sdk.Result) { } // BurnFeeHandler burns all fees (decreasing total supply) -func BurnFeeHandler(ctx sdk.Context, tx sdk.Tx, fee sdk.Coins) { -} +func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {} diff --git a/x/fee_distribution/keeper.go b/x/fee_distribution/keeper.go new file mode 100644 index 0000000000..1450717193 --- /dev/null +++ b/x/fee_distribution/keeper.go @@ -0,0 +1,91 @@ +package stake + +//// 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 +//} + +////_________________________________________________________________________ + +//// cummulative power of the non-absent prevotes +//func (k Keeper) GetTotalPrecommitVotingPower(ctx sdk.Context) sdk.Rat { +//store := ctx.KVStore(k.storeKey) + +//// get absent prevote indexes +//absents := ctx.AbsentValidators() + +//TotalPower := sdk.ZeroRat() +//i := int32(0) +//iterator := store.SubspaceIterator(ValidatorsBondedKey) +//for ; iterator.Valid(); iterator.Next() { + +//skip := false +//for j, absentIndex := range absents { +//if absentIndex > i { +//break +//} + +//// if non-voting validator found, skip adding its power +//if absentIndex == i { +//absents = append(absents[:j], absents[j+1:]...) // won't need again +//skip = true +//break +//} +//} +//if skip { +//continue +//} + +//bz := iterator.Value() +//var validator Validator +//k.cdc.MustUnmarshalBinary(bz, &validator) +//TotalPower = TotalPower.Add(validator.Power) +//i++ +//} +//iterator.Close() +//return TotalPower +//} + +////_______________________________________________________________________ + +//// XXX TODO trim functionality + +//// retrieve all the power changes which occur after a height +//func (k Keeper) GetPowerChangesAfterHeight(ctx sdk.Context, earliestHeight int64) (pcs []PowerChange) { +//store := ctx.KVStore(k.storeKey) + +//iterator := store.SubspaceIterator(PowerChangeKey) //smallest to largest +//for ; iterator.Valid(); iterator.Next() { +//pcBytes := iterator.Value() +//var pc PowerChange +//k.cdc.MustUnmarshalBinary(pcBytes, &pc) +//if pc.Height < earliestHeight { +//break +//} +//pcs = append(pcs, pc) +//} +//iterator.Close() +//return +//} + +//// set a power change +//func (k Keeper) setPowerChange(ctx sdk.Context, pc PowerChange) { +//store := ctx.KVStore(k.storeKey) +//b := k.cdc.MustMarshalBinary(pc) +//store.Set(GetPowerChangeKey(pc.Height), b) +//} diff --git a/x/fee_distribution/keeper_test.go b/x/fee_distribution/keeper_test.go new file mode 100644 index 0000000000..4ad8180e17 --- /dev/null +++ b/x/fee_distribution/keeper_test.go @@ -0,0 +1,31 @@ +package stake + +//// test if is a gotValidator from the last update +//func TestGetTotalPrecommitVotingPower(t *testing.T) { +//ctx, _, keeper := createTestInput(t, false, 0) + +//amts := []int64{10000, 1000, 100, 10, 1} +//var candidatesIn [5]Candidate +//for i, amt := range amts { +//candidatesIn[i] = NewCandidate(addrVals[i], pks[i], Description{}) +//candidatesIn[i].BondedShares = sdk.NewRat(amt) +//candidatesIn[i].DelegatorShares = sdk.NewRat(amt) +//keeper.setCandidate(ctx, candidatesIn[i]) +//} + +//// test that an empty gotValidator set doesn't have any gotValidators +//gotValidators := keeper.GetValidators(ctx) +//assert.Equal(t, 5, len(gotValidators)) + +//totPow := keeper.GetTotalPrecommitVotingPower(ctx) +//exp := sdk.NewRat(11111) +//assert.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) + +//// set absent gotValidators to be the 1st and 3rd record sorted by pubKey address +//ctx = ctx.WithAbsentValidators([]int32{1, 3}) +//totPow = keeper.GetTotalPrecommitVotingPower(ctx) + +//// XXX verify that this order should infact exclude these two records +//exp = sdk.NewRat(11100) +//assert.True(t, exp.Equal(totPow), "exp %v, got %v", exp, totPow) +//} diff --git a/x/fee_distribution/movement.go b/x/fee_distribution/movement.go new file mode 100644 index 0000000000..03c4de72cb --- /dev/null +++ b/x/fee_distribution/movement.go @@ -0,0 +1,72 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// burn burn burn +func BurnFeeHandler(ctx sdk.Context, _ sdk.Tx, collectedFees sdk.Coins) {} + +//// Handle fee distribution to the validators and delegators +//func (k Keeper) FeeHandler(ctx sdk.Context, collectedFees sdk.Coins) { +//pool := k.GetPool(ctx) +//params := k.GetParams(ctx) + +//// XXX determine +//candidate := NewCandidate(addrs[0], pks[0], Description{}) + +//// calculate the proposer reward +//precommitPower := k.GetTotalPrecommitVotingPower(ctx) +//toProposer := coinsMulRat(collectedFees, (sdk.NewRat(1, 100).Add(sdk.NewRat(4, 100).Mul(precommitPower).Quo(pool.BondedShares)))) +//candidate.ProposerRewardPool = candidate.ProposerRewardPool.Plus(toProposer) + +//toReservePool := coinsMulRat(collectedFees, params.ReservePoolFee) +//pool.FeeReservePool = pool.FeeReservePool.Plus(toReservePool) + +//distributedReward := (collectedFees.Minus(toProposer)).Minus(toReservePool) +//pool.FeePool = pool.FeePool.Plus(distributedReward) +//pool.FeeSumReceived = pool.FeeSumReceived.Plus(distributedReward) +//pool.FeeRecent = distributedReward + +//// lastly update the FeeRecent term +//pool.FeeRecent = collectedFees + +//k.setPool(ctx, pool) +//} + +//func coinsMulRat(coins sdk.Coins, rat sdk.Rat) sdk.Coins { +//var res sdk.Coins +//for _, coin := range coins { +//coinMulAmt := rat.Mul(sdk.NewRat(coin.Amount)).Evaluate() +//coinMul := sdk.Coins{{coin.Denom, coinMulAmt}} +//res = res.Plus(coinMul) +//} +//return res +//} + +////____________________________________________________________________________- + +//// calculate adjustment changes for a candidate at a height +//func CalculateAdjustmentChange(candidate Candidate, pool Pool, denoms []string, height int64) (Candidate, Pool) { + +//heightRat := sdk.NewRat(height) +//lastHeightRat := sdk.NewRat(height - 1) +//candidateFeeCount := candidate.BondedShares.Mul(heightRat) +//poolFeeCount := pool.BondedShares.Mul(heightRat) + +//for i, denom := range denoms { +//poolFeeSumReceived := sdk.NewRat(pool.FeeSumReceived.AmountOf(denom)) +//poolFeeRecent := sdk.NewRat(pool.FeeRecent.AmountOf(denom)) +//// calculate simple and projected pools +//simplePool := candidateFeeCount.Quo(poolFeeCount).Mul(poolFeeSumReceived) +//calc1 := candidate.PrevBondedShares.Mul(lastHeightRat).Quo(pool.PrevBondedShares.Mul(lastHeightRat)).Mul(poolFeeRecent) +//calc2 := candidate.BondedShares.Quo(pool.BondedShares).Mul(poolFeeRecent) +//projectedPool := calc1.Add(calc2) + +//AdjustmentChange := simplePool.Sub(projectedPool) +//candidate.FeeAdjustments[i] = candidate.FeeAdjustments[i].Add(AdjustmentChange) +//pool.FeeAdjustments[i] = pool.FeeAdjustments[i].Add(AdjustmentChange) +//} + +//return candidate, pool +//} diff --git a/x/fee_distribution/types.go b/x/fee_distribution/types.go new file mode 100644 index 0000000000..f9d4f905f6 --- /dev/null +++ b/x/fee_distribution/types.go @@ -0,0 +1,107 @@ +package stake + +//// GenesisState - all staking state that must be provided at genesis +//type GenesisState struct { +//Pool Pool `json:"pool"` +//Params Params `json:"params"` +//} + +//func NewGenesisState(pool Pool, params Params, candidates []Candidate, bonds []Delegation) GenesisState { +//return GenesisState{ +//Pool: pool, +//Params: params, +//} +//} + +//// get raw genesis raw message for testing +//func DefaultGenesisState() GenesisState { +//return GenesisState{ +//Pool: initialPool(), +//Params: defaultParams(), +//} +//} + +//// fee information for a validator +//type Validator struct { +//Adjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms +//PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools +//} + +////_________________________________________________________________________ + +//// Params defines the high level settings for staking +//type Params struct { +//FeeDenoms []string `json:"fee_denoms"` // accepted fee denoms +//ReservePoolFee sdk.Rat `json:"reserve_pool_fee"` // percent of fees which go to reserve pool +//} + +//func (p Params) equal(p2 Params) bool { +//return p.BondDenom == p2.BondDenom && +//p.ReservePoolFee.Equal(p2.ReservePoolFee) +//} + +//func defaultParams() Params { +//return Params{ +//FeeDenoms: []string{"steak"}, +//ReservePoolFee: sdk.NewRat(5, 100), +//} +//} + +////_________________________________________________________________________ + +//// Pool - dynamic parameters of the current state +//type Pool struct { +//FeeReservePool sdk.Coins `json:"fee_reserve_pool"` // XXX reserve pool of collected fees for use by governance +//FeePool sdk.Coins `json:"fee_pool"` // XXX fee pool for all the fee shares which have already been distributed +//FeeSumReceived sdk.Coins `json:"fee_sum_received"` // XXX sum of all fees received, post reserve pool `json:"fee_sum_received"` +//FeeRecent sdk.Coins `json:"fee_recent"` // XXX most recent fee collected +//FeeAdjustments []sdk.Rat `json:"fee_adjustments"` // XXX Adjustment factors for lazy fee accounting, couples with Params.BondDenoms +//PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // XXX last recorded bonded shares +//} + +//func (p Pool) equal(p2 Pool) bool { +//return p.FeeReservePool.IsEqual(p2.FeeReservePool) && +//p.FeePool.IsEqual(p2.FeePool) && +//p.FeeSumReceived.IsEqual(p2.FeeSumReceived) && +//p.FeeRecent.IsEqual(p2.FeeRecent) && +//sdk.RatsEqual(p.FeeAdjustments, p2.FeeAdjustments) && +//p.PrevBondedShares.Equal(p2.PrevBondedShares) +//} + +//// initial pool for testing +//func initialPool() Pool { +//return Pool{ +//FeeReservePool: sdk.Coins(nil), +//FeePool: sdk.Coins(nil), +//FeeSumReceived: sdk.Coins(nil), +//FeeRecent: sdk.Coins(nil), +//FeeAdjustments: []sdk.Rat{sdk.ZeroRat()}, +//PrevBondedShares: sdk.ZeroRat(), +//} +//} + +////_________________________________________________________________________ + +//// Used in calculation of fee shares, added to a queue for each block where a power change occures +//type PowerChange struct { +//Height int64 `json:"height"` // block height at change +//Power sdk.Rat `json:"power"` // total power at change +//PrevPower sdk.Rat `json:"prev_power"` // total power at previous height-1 +//FeesIn sdk.Coins `json:"fees_in"` // fees in at block height +//PrevFeePool sdk.Coins `json:"prev_fee_pool"` // total fees in at previous block height +//} + +////_________________________________________________________________________ +//// KEY MANAGEMENT + +//var ( +//// Keys for store prefixes +//PowerChangeKey = []byte{0x09} // prefix for power change object +//) + +//// get the key for the accumulated update validators +//func GetPowerChangeKey(height int64) []byte { +//heightBytes := make([]byte, binary.MaxVarintLen64) +//binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) +//return append(PowerChangeKey, heightBytes...) +//} diff --git a/x/stake/client/cli/flags.go b/x/stake/client/cli/flags.go index 98ea3bcced..eea7e20319 100644 --- a/x/stake/client/cli/flags.go +++ b/x/stake/client/cli/flags.go @@ -7,7 +7,7 @@ import ( // nolint const ( FlagAddressDelegator = "address-delegator" - FlagAddressCandidate = "address-candidate" + FlagAddressValidator = "address-validator" FlagPubKey = "pubkey" FlagAmount = "amount" FlagShares = "shares" @@ -24,18 +24,18 @@ var ( fsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsDescription = flag.NewFlagSet("", flag.ContinueOnError) - fsCandidate = flag.NewFlagSet("", flag.ContinueOnError) + fsValidator = flag.NewFlagSet("", flag.ContinueOnError) fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { - fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator-candidate. For Ed25519 the go-amino prepend hex is 1624de6220") + 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-candidate name") + fsDescription.String(FlagMoniker, "", "validator name") fsDescription.String(FlagIdentity, "", "optional keybase signature") fsDescription.String(FlagWebsite, "", "optional website") fsDescription.String(FlagDetails, "", "optional details") - fsCandidate.String(FlagAddressCandidate, "", "hex address of the validator/candidate") + fsValidator.String(FlagAddressValidator, "", "hex address of the validator") fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") } diff --git a/x/stake/client/cli/query.go b/x/stake/client/cli/query.go index 8a5a06a709..079dd003d8 100644 --- a/x/stake/client/cli/query.go +++ b/x/stake/client/cli/query.go @@ -15,51 +15,47 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" ) -// get the command to query a candidate -func GetCmdQueryCandidate(storeName string, cdc *wire.Codec) *cobra.Command { +// get the command to query a validator +func GetCmdQueryValidator(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "candidate", - Short: "Query a validator-candidate account", + Use: "validator [owner-addr]", + Short: "Query a validator", + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + addr, err := sdk.GetAddress(args[0]) if err != nil { return err } - - key := stake.GetCandidateKey(addr) + key := stake.GetValidatorKey(addr) ctx := context.NewCoreContextFromViper() res, err := ctx.Query(key, storeName) if err != nil { return err } - // parse out the candidate - candidate := new(stake.Candidate) - cdc.MustUnmarshalBinary(res, candidate) - output, err := wire.MarshalJSONIndent(cdc, candidate) - if err != nil { - return err - } + // parse out the validator + validator := new(stake.Validator) + cdc.MustUnmarshalBinary(res, validator) + output, err := wire.MarshalJSONIndent(cdc, validator) fmt.Println(string(output)) - return nil // TODO output with proofs / machine parseable etc. + return nil }, } - cmd.Flags().AddFlagSet(fsCandidate) return cmd } -// get the command to query a candidate -func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { +// get the command to query a validator +func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "candidates", - Short: "Query for all validator-candidate accounts", + Use: "validators", + Short: "Query for all validators", RunE: func(cmd *cobra.Command, args []string) error { - key := stake.CandidatesKey + key := stake.ValidatorsKey ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -67,11 +63,11 @@ func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { } // parse out the candidates - var candidates []stake.Candidate + var candidates []stake.Validator for _, KV := range resKVs { - var candidate stake.Candidate - cdc.MustUnmarshalBinary(KV.Value, &candidate) - candidates = append(candidates, candidate) + var validator stake.Validator + cdc.MustUnmarshalBinary(KV.Value, &validator) + candidates = append(candidates, validator) } output, err := wire.MarshalJSONIndent(cdc, candidates) @@ -87,14 +83,14 @@ func GetCmdQueryCandidates(storeName string, cdc *wire.Codec) *cobra.Command { return cmd } -// get the command to query a single delegator bond -func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command { +// get the command to query a single delegation bond +func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "delegator-bond", - Short: "Query a delegators bond based on address and candidate pubkey", + Use: "delegation", + Short: "Query a delegations bond based on address and validator address", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + addr, err := sdk.GetAddress(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -103,9 +99,9 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command if err != nil { return err } - delegator := crypto.Address(bz) + delegation := crypto.Address(bz) - key := stake.GetDelegatorBondKey(delegator, addr, cdc) + key := stake.GetDelegationKey(delegation, addr, cdc) ctx := context.NewCoreContextFromViper() res, err := ctx.Query(key, storeName) if err != nil { @@ -113,7 +109,7 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command } // parse out the bond - bond := new(stake.DelegatorBond) + bond := new(stake.Delegation) cdc.MustUnmarshalBinary(res, bond) output, err := wire.MarshalJSONIndent(cdc, bond) if err != nil { @@ -126,23 +122,24 @@ func GetCmdQueryDelegatorBond(storeName string, cdc *wire.Codec) *cobra.Command }, } - cmd.Flags().AddFlagSet(fsCandidate) + cmd.Flags().AddFlagSet(fsValidator) cmd.Flags().AddFlagSet(fsDelegator) return cmd } -// get the command to query all the candidates bonded to a delegator -func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command { +// get the command to query all the candidates bonded to a delegation +func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "delegator-candidates", - Short: "Query all delegators bonds based on delegator-address", + Use: "delegations [delegator-addr]", + Short: "Query all delegations made from one delegator", + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) + delegatorAddr, err := sdk.GetAddress(args[0]) if err != nil { return err } - key := stake.GetDelegatorBondsKey(delegatorAddr, cdc) + key := stake.GetDelegationsKey(delegatorAddr, cdc) ctx := context.NewCoreContextFromViper() resKVs, err := ctx.QuerySubspace(cdc, key, storeName) if err != nil { @@ -150,14 +147,14 @@ func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command } // parse out the candidates - var delegators []stake.DelegatorBond + var delegations []stake.Delegation for _, KV := range resKVs { - var delegator stake.DelegatorBond - cdc.MustUnmarshalBinary(KV.Value, &delegator) - delegators = append(delegators, delegator) + var delegation stake.Delegation + cdc.MustUnmarshalBinary(KV.Value, &delegation) + delegations = append(delegations, delegation) } - output, err := wire.MarshalJSONIndent(cdc, delegators) + output, err := wire.MarshalJSONIndent(cdc, delegations) if err != nil { return err } @@ -167,6 +164,5 @@ func GetCmdQueryDelegatorBonds(storeName string, cdc *wire.Codec) *cobra.Command // TODO output with proofs / machine parseable etc. }, } - cmd.Flags().AddFlagSet(fsDelegator) return cmd } diff --git a/x/stake/client/cli/tx.go b/x/stake/client/cli/tx.go index d4f97fd526..dd9c97a72c 100644 --- a/x/stake/client/cli/tx.go +++ b/x/stake/client/cli/tx.go @@ -19,8 +19,8 @@ import ( // create declare candidacy command func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "declare-candidacy", - Short: "create new validator-candidate account and delegate some coins to it", + Use: "create-validator", + Short: "create new validator initialized with a self-delegation to it", RunE: func(cmd *cobra.Command, args []string) error { ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -28,7 +28,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + validatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -47,7 +47,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { } if viper.GetString(FlagMoniker) == "" { - return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") + return fmt.Errorf("please enter a moniker for the validator using --moniker") } description := stake.Description{ Moniker: viper.GetString(FlagMoniker), @@ -55,7 +55,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) + msg := stake.NewMsgDeclareCandidacy(validatorAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, msg, cdc) @@ -71,18 +71,18 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDescription) - cmd.Flags().AddFlagSet(fsCandidate) + cmd.Flags().AddFlagSet(fsValidator) return cmd } // create edit candidacy command func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "edit-candidacy", - Short: "edit and existing validator-candidate account", + Use: "edit-validator", + Short: "edit and existing validator account", RunE: func(cmd *cobra.Command, args []string) error { - candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + validatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressValidator)) if err != nil { return err } @@ -92,7 +92,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgEditCandidacy(candidateAddr, description) + msg := stake.NewMsgEditCandidacy(validatorAddr, description) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -108,7 +108,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { } cmd.Flags().AddFlagSet(fsDescription) - cmd.Flags().AddFlagSet(fsCandidate) + cmd.Flags().AddFlagSet(fsValidator) return cmd } @@ -116,7 +116,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "delegate", - Short: "delegate coins to an existing validator/candidate", + Short: "delegate coins to an existing validator", RunE: func(cmd *cobra.Command, args []string) error { amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { @@ -124,12 +124,12 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { } delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) - candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + validatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressValidator)) if err != nil { return err } - msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount) + msg := stake.NewMsgDelegate(delegatorAddr, validatorAddr, amount) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -146,7 +146,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsAmount) cmd.Flags().AddFlagSet(fsDelegator) - cmd.Flags().AddFlagSet(fsCandidate) + cmd.Flags().AddFlagSet(fsValidator) return cmd } @@ -154,7 +154,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "unbond", - Short: "unbond coins from a validator/candidate", + Short: "unbond shares from a validator", RunE: func(cmd *cobra.Command, args []string) error { // check the shares before broadcasting @@ -172,12 +172,12 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { } delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) - candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) + validatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressValidator)) if err != nil { return err } - msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr) + msg := stake.NewMsgUnbond(delegatorAddr, validatorAddr, sharesStr) // build and sign the transaction, then broadcast to Tendermint ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) @@ -194,6 +194,6 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsShares) cmd.Flags().AddFlagSet(fsDelegator) - cmd.Flags().AddFlagSet(fsCandidate) + cmd.Flags().AddFlagSet(fsValidator) return cmd } diff --git a/x/stake/client/rest/query.go b/x/stake/client/rest/query.go index 8eb0e03ceb..6093902934 100644 --- a/x/stake/client/rest/query.go +++ b/x/stake/client/rest/query.go @@ -16,7 +16,7 @@ import ( // RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { - r.HandleFunc("/stake/{delegator}/bonding_status/{candidate}", BondingStatusHandlerFn("stake", cdc, kb, ctx)).Methods("GET") + r.HandleFunc("/stake/{delegator}/bonding_status/{validator}", BondingStatusHandlerFn("stake", cdc, kb, ctx)).Methods("GET") } // BondingStatusHandlerFn - http request handler to query delegator bonding status @@ -41,9 +41,9 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, w.Write([]byte(err.Error())) return } - candidateAddr := sdk.Address(bz) + validatorAddr := sdk.Address(bz) - key := stake.GetDelegatorBondKey(delegatorAddr, candidateAddr, cdc) + key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc) res, err := ctx.Query(key, storeName) if err != nil { @@ -58,7 +58,7 @@ func BondingStatusHandlerFn(storeName string, cdc *wire.Codec, kb keys.Keybase, return } - var bond stake.DelegatorBond + var bond stake.Delegation err = cdc.UnmarshalBinary(res, &bond) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/x/stake/delegation.go b/x/stake/delegation.go new file mode 100644 index 0000000000..89afe8e907 --- /dev/null +++ b/x/stake/delegation.go @@ -0,0 +1,33 @@ +package stake + +import ( + "bytes" + + 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. +// TODO better way of managing space +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 } diff --git a/x/stake/errors.go b/x/stake/errors.go index 008d51bc9d..83c38d528d 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -14,9 +14,8 @@ const ( // Gaia errors reserve 200 ~ 299. CodeInvalidValidator CodeType = 201 - CodeInvalidCandidate CodeType = 202 - CodeInvalidBond CodeType = 203 - CodeInvalidInput CodeType = 204 + CodeInvalidBond CodeType = 202 + CodeInvalidInput CodeType = 203 CodeUnauthorized CodeType = sdk.CodeUnauthorized CodeInternal CodeType = sdk.CodeInternal CodeUnknownRequest CodeType = sdk.CodeUnknownRequest @@ -27,8 +26,6 @@ func codeToDefaultMsg(code CodeType) string { switch code { case CodeInvalidValidator: return "Invalid Validator" - case CodeInvalidCandidate: - return "Invalid Candidate" case CodeInvalidBond: return "Invalid Bond" case CodeInvalidInput: @@ -50,8 +47,8 @@ func codeToDefaultMsg(code CodeType) string { func ErrNotEnoughBondShares(codespace sdk.CodespaceType, shares string) sdk.Error { return newError(codespace, CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares)) } -func ErrCandidateEmpty(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Cannot bond to an empty candidate") +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") @@ -71,16 +68,13 @@ func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error { func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Validator does not exist for that address") } -func ErrBadCandidateAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Candidate 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 ErrCandidateExistsAddr(codespace sdk.CodespaceType) sdk.Error { - return newError(codespace, CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy") +func ErrValidatorExistsAddr(codespace sdk.CodespaceType) sdk.Error { + return newError(codespace, CodeInvalidValidator, "Validator already exist, cannot re-declare candidacy") } -func ErrCandidateRevoked(codespace sdk.CodespaceType) sdk.Error { +func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Candidacy for this address is currently revoked") } func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { @@ -89,7 +83,7 @@ func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error { func ErrBondNotNominated(codespace sdk.CodespaceType) sdk.Error { return newError(codespace, CodeInvalidValidator, "Cannot bond to non-nominated account") } -func ErrNoCandidateForAddress(codespace sdk.CodespaceType) sdk.Error { +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 { diff --git a/x/stake/fee_distribution.go b/x/stake/fee_distribution.go deleted file mode 100644 index 940a66e38c..0000000000 --- a/x/stake/fee_distribution.go +++ /dev/null @@ -1,10 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Handle fee distribution to the validators and delegators -func FeeHandler(ctx sdk.Context, tx sdk.Tx, fee sdk.Coins) { - -} diff --git a/x/stake/genesis.go b/x/stake/genesis.go new file mode 100644 index 0000000000..d45adc3d7f --- /dev/null +++ b/x/stake/genesis.go @@ -0,0 +1,54 @@ +package stake + +import sdk "github.com/cosmos/cosmos-sdk/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) { + k.setPool(ctx, data.Pool) + k.setNewParams(ctx, data.Params) + for _, validator := range data.Validators { + k.updateValidator(ctx, validator) + } + for _, bond := range data.Bonds { + k.setDelegation(ctx, bond) + } +} + +// 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{ + pool, + params, + validators, + bonds, + } +} diff --git a/x/stake/handler.go b/x/stake/handler.go index 8d3bbb8b80..53653557cc 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -7,16 +7,6 @@ import ( abci "github.com/tendermint/abci/types" ) -//nolint -const ( - GasDeclareCandidacy int64 = 20 - GasEditCandidacy int64 = 20 - GasDelegate int64 = 20 - GasUnbond int64 = 20 -) - -//_______________________________________________________________________ - func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // NOTE msg already has validate basic run @@ -35,8 +25,6 @@ func NewHandler(k Keeper) sdk.Handler { } } -//_____________________________________________________________________ - // NewEndBlocker generates sdk.EndBlocker // Performs tick functionality func NewEndBlocker(k Keeper) sdk.EndBlocker { @@ -48,60 +36,35 @@ func NewEndBlocker(k Keeper) sdk.EndBlocker { //_____________________________________________________________________ -// InitGenesis - store genesis parameters -func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - k.setPool(ctx, data.Pool) - k.setParams(ctx, data.Params) - for _, candidate := range data.Candidates { - k.setCandidate(ctx, candidate) - } - for _, bond := range data.Bonds { - k.setDelegatorBond(ctx, bond) - } -} - -// WriteGenesis - output genesis parameters -func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState { - pool := k.GetPool(ctx) - params := k.GetParams(ctx) - candidates := k.GetCandidates(ctx, 32767) - bonds := k.getBonds(ctx, 32767) - return GenesisState{ - pool, - params, - candidates, - bonds, - } -} - -//_____________________________________________________________________ - // These functions assume everything has been authenticated, // now we just perform action and save func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before - _, found := k.GetCandidate(ctx, msg.CandidateAddr) + _, found := k.GetValidator(ctx, msg.ValidatorAddr) if found { - return ErrCandidateExistsAddr(k.codespace).Result() + return ErrValidatorExistsAddr(k.codespace).Result() } if msg.Bond.Denom != k.GetParams(ctx).BondDenom { return ErrBadBondingDenom(k.codespace).Result() } if ctx.IsCheckTx() { - return sdk.Result{ - GasUsed: GasDeclareCandidacy, - } + return sdk.Result{} } - candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description) - k.setCandidate(ctx, candidate) - tags := sdk.NewTags("action", []byte("declareCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) + validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) + k.setValidator(ctx, validator) + tags := sdk.NewTags( + "action", []byte("declareCandidacy"), + "validator", msg.ValidatorAddr.Bytes(), + "moniker", []byte(msg.Description.Moniker), + "identity", []byte(msg.Description.Identity), + ) // move coins from the msg.Address account to a (self-bond) delegator account - // the candidate account and global shares are updated within here - delegateTags, err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate) + // the validator account and global shares are updated within here + delegateTags, err := delegate(ctx, k, msg.ValidatorAddr, msg.Bond, validator) if err != nil { return err.Result() } @@ -113,26 +76,29 @@ func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keepe func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result { - // candidate must already be registered - candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) + // validator must already be registered + validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadCandidateAddr(k.codespace).Result() + return ErrBadValidatorAddr(k.codespace).Result() } if ctx.IsCheckTx() { - return sdk.Result{ - GasUsed: GasEditCandidacy, - } + return sdk.Result{} } // XXX move to types // replace all editable fields (clients should autofill existing values) - candidate.Description.Moniker = msg.Description.Moniker - candidate.Description.Identity = msg.Description.Identity - candidate.Description.Website = msg.Description.Website - candidate.Description.Details = msg.Description.Details + validator.Description.Moniker = msg.Description.Moniker + validator.Description.Identity = msg.Description.Identity + validator.Description.Website = msg.Description.Website + validator.Description.Details = msg.Description.Details - k.setCandidate(ctx, candidate) - tags := sdk.NewTags("action", []byte("editCandidacy"), "candidate", msg.CandidateAddr.Bytes(), "moniker", []byte(msg.Description.Moniker), "identity", []byte(msg.Description.Identity)) + k.updateValidator(ctx, validator) + tags := sdk.NewTags( + "action", []byte("editCandidacy"), + "validator", msg.ValidatorAddr.Bytes(), + "moniker", []byte(msg.Description.Moniker), + "identity", []byte(msg.Description.Identity), + ) return sdk.Result{ Tags: tags, } @@ -140,22 +106,20 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { - candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) + validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrBadCandidateAddr(k.codespace).Result() + return ErrBadValidatorAddr(k.codespace).Result() } if msg.Bond.Denom != k.GetParams(ctx).BondDenom { return ErrBadBondingDenom(k.codespace).Result() } - if candidate.Status == Revoked { - return ErrCandidateRevoked(k.codespace).Result() + if validator.Revoked == true { + return ErrValidatorRevoked(k.codespace).Result() } if ctx.IsCheckTx() { - return sdk.Result{ - GasUsed: GasDelegate, - } + return sdk.Result{} } - tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate) + tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, validator) if err != nil { return err.Result() } @@ -166,14 +130,14 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { // common functionality between handlers func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, - bondAmt sdk.Coin, candidate Candidate) (sdk.Tags, sdk.Error) { + bondAmt sdk.Coin, validator Validator) (sdk.Tags, sdk.Error) { // Get or create the delegator bond - bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address) + bond, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner) if !found { - bond = DelegatorBond{ + bond = Delegation{ DelegatorAddr: delegatorAddr, - CandidateAddr: candidate.Address, + ValidatorAddr: validator.Owner, Shares: sdk.ZeroRat(), } } @@ -184,31 +148,28 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, if err != nil { return nil, err } - pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount) + validator, pool, newShares := validator.addTokensFromDel(pool, bondAmt.Amount) bond.Shares = bond.Shares.Add(newShares) // Update bond height bond.Height = ctx.BlockHeight() - k.setDelegatorBond(ctx, bond) - k.setCandidate(ctx, candidate) k.setPool(ctx, pool) - tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "candidate", candidate.Address.Bytes()) + k.setDelegation(ctx, bond) + k.updateValidator(ctx, validator) + tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "validator", validator.Owner.Bytes()) return tags, nil } func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // check if bond has any shares in it unbond - bond, found := k.GetDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr) + bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.ValidatorAddr) if !found { return ErrNoDelegatorForAddress(k.codespace).Result() } - if !bond.Shares.GT(sdk.ZeroRat()) { // bond shares < msg shares - return ErrInsufficientFunds(k.codespace).Result() - } - var shares sdk.Rat + var delShares sdk.Rat // test that there are enough shares to unbond if msg.Shares == "MAX" { @@ -217,81 +178,71 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } } else { var err sdk.Error - shares, err = sdk.NewRatFromDecimal(msg.Shares) + delShares, err = sdk.NewRatFromDecimal(msg.Shares) if err != nil { return err.Result() } - if bond.Shares.LT(shares) { + if bond.Shares.LT(delShares) { return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result() } } - // get candidate - candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) + // get validator + validator, found := k.GetValidator(ctx, msg.ValidatorAddr) if !found { - return ErrNoCandidateForAddress(k.codespace).Result() + return ErrNoValidatorForAddress(k.codespace).Result() } if ctx.IsCheckTx() { - return sdk.Result{ - GasUsed: GasUnbond, - } + return sdk.Result{} } // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) if msg.Shares == "MAX" { - shares = bond.Shares + delShares = bond.Shares } // subtract bond tokens from delegator bond - bond.Shares = bond.Shares.Sub(shares) + bond.Shares = bond.Shares.Sub(delShares) // remove the bond revokeCandidacy := false if bond.Shares.IsZero() { - // if the bond is the owner of the candidate then + // if the bond is the owner of the validator then // trigger a revoke candidacy - if bytes.Equal(bond.DelegatorAddr, candidate.Address) && - candidate.Status != Revoked { + if bytes.Equal(bond.DelegatorAddr, validator.Owner) && + validator.Revoked == false { revokeCandidacy = true } - k.removeDelegatorBond(ctx, bond) + k.removeDelegation(ctx, bond) } else { // Update bond height bond.Height = ctx.BlockHeight() - k.setDelegatorBond(ctx, bond) + k.setDelegation(ctx, bond) } // Add the coins - p := k.GetPool(ctx) - p, candidate, returnAmount := p.candidateRemoveShares(candidate, shares) + 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 candidate if necessary + // revoke validator if necessary if revokeCandidacy { - - // change the share types to unbonded if they were not already - if candidate.Status == Bonded { - p, candidate = p.bondedToUnbondedPool(candidate) - } - - // lastly update the status - candidate.Status = Revoked + validator.Revoked = true } - // deduct shares from the candidate - if candidate.Liabilities.IsZero() { - k.removeCandidate(ctx, candidate.Address) - } else { - k.setCandidate(ctx, candidate) + validator = k.updateValidator(ctx, validator) + + if validator.DelegatorShares.IsZero() { + k.removeValidator(ctx, validator.Owner) } - k.setPool(ctx, p) - tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "candidate", msg.CandidateAddr.Bytes()) + + tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes()) return sdk.Result{ Tags: tags, } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index b4db952f0e..19848e8e6d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -17,16 +17,16 @@ import ( func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { return MsgDeclareCandidacy{ Description: Description{}, - CandidateAddr: address, - Bond: sdk.Coin{"steak", amt}, + ValidatorAddr: address, PubKey: pubKey, + Bond: sdk.Coin{"steak", amt}, } } -func newTestMsgDelegate(delegatorAddr, candidateAddr sdk.Address, amt int64) MsgDelegate { +func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, - CandidateAddr: candidateAddr, + ValidatorAddr: validatorAddr, Bond: sdk.Coin{"steak", amt}, } } @@ -36,21 +36,21 @@ func newTestMsgDelegate(delegatorAddr, candidateAddr sdk.Address, amt int64) Msg func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) - candidateAddr := addrs[0] + validatorAddr := addrs[0] pk := pks[0] - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pk, 10) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pk, 10) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "%v", got) - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, Unbonded, candidate.Status) - assert.Equal(t, candidateAddr, candidate.Address) - assert.Equal(t, pk, candidate.PubKey) - assert.Equal(t, sdk.NewRat(10), candidate.Assets) - assert.Equal(t, sdk.NewRat(10), candidate.Liabilities) - assert.Equal(t, Description{}, candidate.Description) + assert.Equal(t, sdk.Bonded, validator.Status()) + assert.Equal(t, validatorAddr, validator.Owner) + assert.Equal(t, pk, validator.PubKey) + assert.Equal(t, sdk.NewRat(10), validator.PoolShares.Bonded()) + assert.Equal(t, sdk.NewRat(10), validator.DelegatorShares) + assert.Equal(t, Description{}, validator.Description) - // one candidate cannot bond twice + // one validator cannot bond twice msgDeclareCandidacy.PubKey = pks[1] got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.False(t, got.IsOK(), "%v", got) @@ -62,20 +62,34 @@ func TestIncrementsMsgDelegate(t *testing.T) { params := keeper.GetParams(ctx) bondAmount := int64(10) - candidateAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := addrs[0], addrs[1] // first declare candidacy - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], bondAmount) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], bondAmount) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "expected declare candidacy msg to be ok, got %v", got) - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, bondAmount, candidate.Liabilities.Evaluate()) - assert.Equal(t, bondAmount, candidate.Assets.Evaluate()) + require.Equal(t, sdk.Bonded, validator.Status()) + 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.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.Evaluate()) + assert.Equal(t, bondAmount, pool.BondedTokens) // just send the same msgbond multiple times - msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, bondAmount) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) for i := 0; i < 5; i++ { ctx = ctx.WithBlockHeight(int64(i)) @@ -84,30 +98,34 @@ func TestIncrementsMsgDelegate(t *testing.T) { 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 - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) + pool := keeper.GetPool(ctx) + exRate := validator.DelegatorShareExRate(pool) + require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i) + expBond := int64(i+1) * bondAmount - expLiabilities := int64(i+2) * bondAmount // (1 self delegation) + expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation) expDelegatorAcc := initBond - expBond require.Equal(t, bond.Height, int64(i), "Incorrect bond height") gotBond := bond.Shares.Evaluate() - gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, - "i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n", - i, expBond, gotBond, candidate, bond) - require.Equal(t, expLiabilities, gotLiabilities, - "i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n", - i, expLiabilities, gotLiabilities, candidate, bond) + "i: %v\nexpBond: %v\ngotBond: %v\nvalidator: %v\nbond: %v\n", + i, expBond, gotBond, validator, bond) + require.Equal(t, expDelegatorShares, gotDelegatorShares, + "i: %v\nexpDelegatorShares: %v\ngotDelegatorShares: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorShares, gotDelegatorShares, validator, bond) require.Equal(t, expDelegatorAcc, gotDelegatorAcc, - "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n", - i, expDelegatorAcc, gotDelegatorAcc, candidate, bond) + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, validator, bond) } } @@ -117,53 +135,53 @@ func TestIncrementsMsgUnbond(t *testing.T) { params := keeper.GetParams(ctx) // declare candidacy, delegate - candidateAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := addrs[0], addrs[1] - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], initBond) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], initBond) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "expected declare-candidacy to be ok, got %v", got) - msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, initBond) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) assert.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - assert.Equal(t, initBond*2, candidate.Liabilities.Evaluate()) - assert.Equal(t, initBond*2, candidate.Assets.Evaluate()) + 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, candidateAddr, unbondSharesStr) + msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) numUnbonds := 5 for i := 0; i < numUnbonds; i++ { got := handleMsgUnbond(ctx, msgUnbond, 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 - candidate, found = keeper.GetCandidate(ctx, candidateAddr) + validator, found = keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) expBond := initBond - int64(i+1)*unbondShares - expLiabilities := 2*initBond - int64(i+1)*unbondShares + expDelegatorShares := 2*initBond - int64(i+1)*unbondShares expDelegatorAcc := initBond - expBond gotBond := bond.Shares.Evaluate() - gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorShares := validator.DelegatorShares.Evaluate() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) require.Equal(t, expBond, gotBond, - "i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n", - i, expBond, gotBond, candidate, bond) - require.Equal(t, expLiabilities, gotLiabilities, - "i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n", - i, expLiabilities, gotLiabilities, candidate, bond) + "i: %v\nexpBond: %v\ngotBond: %v\nvalidator: %v\nbond: %v\n", + i, expBond, gotBond, validator, bond) + require.Equal(t, expDelegatorShares, gotDelegatorShares, + "i: %v\nexpDelegatorShares: %v\ngotDelegatorShares: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorShares, gotDelegatorShares, validator, bond) require.Equal(t, expDelegatorAcc, gotDelegatorAcc, - "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n", - i, expDelegatorAcc, gotDelegatorAcc, candidate, bond) + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\nvalidator: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, validator, bond) } // these are more than we have bonded now @@ -176,7 +194,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { } for _, c := range errorCases { unbondShares := strconv.Itoa(int(c)) - msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, unbondShares) + msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondShares) got = handleMsgUnbond(ctx, msgUnbond, keeper) require.False(t, got.IsOK(), "expected unbond msg to fail") } @@ -185,14 +203,14 @@ func TestIncrementsMsgUnbond(t *testing.T) { // should be unable to unbond one more than we have unbondSharesStr = strconv.Itoa(int(leftBonded) + 1) - msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr) + msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) got = handleMsgUnbond(ctx, msgUnbond, keeper) assert.False(t, got.IsOK(), "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) // should be able to unbond just what we have unbondSharesStr = strconv.Itoa(int(leftBonded)) - msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr) + msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr) got = handleMsgUnbond(ctx, msgUnbond, keeper) assert.True(t, got.IsOK(), "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) @@ -202,108 +220,108 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { initBond := int64(1000) ctx, accMapper, keeper := createTestInput(t, false, initBond) params := keeper.GetParams(ctx) - candidateAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + validatorAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} // bond them all - for i, candidateAddr := range candidateAddrs { - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[i], 10) + for i, validatorAddr := range validatorAddrs { + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[i], 10) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded - candidates := keeper.GetCandidates(ctx, 100) - require.Equal(t, (i + 1), len(candidates)) - val := candidates[i] + validators := keeper.GetValidators(ctx, 100) + require.Equal(t, (i + 1), len(validators)) + val := validators[i] balanceExpd := initBond - 10 - balanceGot := accMapper.GetAccount(ctx, val.Address).GetCoins().AmountOf(params.BondDenom) - require.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) - require.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) + 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) require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) } // unbond them all - for i, candidateAddr := range candidateAddrs { - candidatePre, found := keeper.GetCandidate(ctx, candidateAddr) + for i, validatorAddr := range validatorAddrs { + validatorPre, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - msgUnbond := NewMsgUnbond(candidateAddr, candidateAddr, "10") // self-delegation + msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "10") // self-delegation got := handleMsgUnbond(ctx, msgUnbond, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded - candidates := keeper.GetCandidates(ctx, 100) - require.Equal(t, len(candidateAddrs)-(i+1), len(candidates), - "expected %d candidates got %d", len(candidateAddrs)-(i+1), len(candidates)) + validators := keeper.GetValidators(ctx, 100) + require.Equal(t, len(validatorAddrs)-(i+1), len(validators), + "expected %d validators got %d", len(validatorAddrs)-(i+1), len(validators)) - _, found = keeper.GetCandidate(ctx, candidateAddr) + _, found = keeper.GetValidator(ctx, validatorAddr) require.False(t, found) expBalance := initBond - gotBalance := accMapper.GetAccount(ctx, candidatePre.Address).GetCoins().AmountOf(params.BondDenom) + 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, 1000) - candidateAddr, delegatorAddrs := addrs[0], addrs[1:] + validatorAddr, delegatorAddrs := addrs[0], addrs[1:] - //first make a candidate - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], 10) + //first make a validator + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], 10) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, 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, candidateAddr, 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) //Check that the account is bonded - bond, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.True(t, found) require.NotNil(t, bond, "expected delegatee bond %d to exist", bond) } // unbond them all for i, delegatorAddr := range delegatorAddrs { - msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, "10") + msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, "10") got := handleMsgUnbond(ctx, msgUnbond, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded - _, found := keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) + _, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) require.False(t, found) } } func TestVoidCandidacy(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) - candidateAddr, delegatorAddr := addrs[0], addrs[1] + validatorAddr, delegatorAddr := addrs[0], addrs[1] - // create the candidate - msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], 10) + // create the validator + msgDeclareCandidacy := newTestMsgDeclareCandidacy(validatorAddr, pks[0], 10) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, 10) + msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) - // unbond the candidates bond portion - msgUnbondCandidate := NewMsgUnbond(candidateAddr, candidateAddr, "10") - got = handleMsgUnbond(ctx, msgUnbondCandidate, keeper) + // unbond the validators bond portion + msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10") + got = handleMsgUnbond(ctx, msgUnbondValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + validator, found := keeper.GetValidator(ctx, validatorAddr) require.True(t, found) - require.Equal(t, Revoked, candidate.Status) + require.True(t, validator.Revoked) // 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, candidateAddr, "10") + msgUnbondDelegator := NewMsgUnbond(delegatorAddr, validatorAddr, "10") got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 605f675db0..ada19df9f0 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -2,6 +2,7 @@ package stake import ( "bytes" + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -15,10 +16,6 @@ type Keeper struct { cdc *wire.Codec coinKeeper bank.Keeper - // caches - pool Pool - params Params - // codespace codespace sdk.CodespaceType } @@ -33,53 +30,57 @@ func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk. return keeper } -// get the current in-block validator operation counter -func (k Keeper) getCounter(ctx sdk.Context) int16 { - store := ctx.KVStore(k.storeKey) - b := store.Get(CounterKey) - if b == nil { - return 0 - } - var counter int16 - err := k.cdc.UnmarshalBinary(b, &counter) - if err != nil { - panic(err) - } - return counter -} - -// set the current in-block validator operation counter -func (k Keeper) setCounter(ctx sdk.Context, counter int16) { - store := ctx.KVStore(k.storeKey) - bz, err := k.cdc.MarshalBinary(counter) - if err != nil { - panic(err) - } - store.Set(CounterKey, bz) -} - //_________________________________________________________________________ -// get a single candidate -func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { +// get a single validator +func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Validator, found bool) { store := ctx.KVStore(k.storeKey) - b := store.Get(GetCandidateKey(addr)) - if b == nil { - return candidate, false - } - err := k.cdc.UnmarshalBinary(b, &candidate) - if err != nil { - panic(err) - } - return candidate, true + return k.getValidator(store, addr) } -// Get the set of all candidates, retrieve a maxRetrieve number of records -func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { - store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(CandidatesKey)) +// 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 +} - candidates = make([]Candidate, maxRetrieve) +// set the main record holding validator details +func (k Keeper) setValidator(ctx sdk.Context, validator Validator) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshalBinary(validator) + store.Set(GetValidatorKey(validator.Owner), bz) +} + +// 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 := store.SubspaceIterator(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 := store.SubspaceIterator(ValidatorsKey) + + validators = make([]Validator, maxRetrieve) i := 0 for ; ; i++ { if !iterator.Valid() || i > int(maxRetrieve-1) { @@ -87,321 +88,444 @@ func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Ca break } bz := iterator.Value() - var candidate Candidate - err := k.cdc.UnmarshalBinary(bz, &candidate) - if err != nil { - panic(err) - } - candidates[i] = candidate + var validator Validator + k.cdc.MustUnmarshalBinary(bz, &validator) + validators[i] = validator iterator.Next() } - return candidates[:i] // trim -} - -func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { - store := ctx.KVStore(k.storeKey) - address := candidate.Address - - // retreive the old candidate record - oldCandidate, oldFound := k.GetCandidate(ctx, address) - - // if found, copy the old block height and counter - if oldFound { - candidate.ValidatorBondHeight = oldCandidate.ValidatorBondHeight - candidate.ValidatorBondCounter = oldCandidate.ValidatorBondCounter - } - - // marshal the candidate record and add to the state - bz, err := k.cdc.MarshalBinary(candidate) - if err != nil { - panic(err) - } - store.Set(GetCandidateKey(candidate.Address), bz) - - // if the voting power is the same no need to update any of the other indexes - if oldFound && oldCandidate.Assets.Equal(candidate.Assets) { - return - } - - updateHeight := false - - // update the list ordered by voting power - if oldFound { - if !k.isNewValidator(ctx, store, candidate.Address) { - updateHeight = true - } - // else already in the validator set - retain the old validator height and counter - store.Delete(GetValidatorKey(address, oldCandidate.Assets, oldCandidate.ValidatorBondHeight, oldCandidate.ValidatorBondCounter, k.cdc)) - } else { - updateHeight = true - } - - if updateHeight { - // wasn't a candidate or wasn't in the validator set, update the validator block height and counter - candidate.ValidatorBondHeight = ctx.BlockHeight() - counter := k.getCounter(ctx) - candidate.ValidatorBondCounter = counter - k.setCounter(ctx, counter+1) - } - - // update the candidate record - bz, err = k.cdc.MarshalBinary(candidate) - if err != nil { - panic(err) - } - store.Set(GetCandidateKey(candidate.Address), bz) - - // marshal the new validator record - validator := candidate.validator() - bz, err = k.cdc.MarshalBinary(validator) - if err != nil { - panic(err) - } - - store.Set(GetValidatorKey(address, validator.Power, validator.Height, validator.Counter, k.cdc), bz) - - // add to the validators to update list if is already a validator - // or is a new validator - setAcc := false - if store.Get(GetRecentValidatorKey(address)) != nil { - setAcc = true - - // want to check in the else statement because inefficient - } else if k.isNewValidator(ctx, store, address) { - setAcc = true - } - if setAcc { - bz, err = k.cdc.MarshalBinary(validator.abciValidator(k.cdc)) - if err != nil { - panic(err) - } - store.Set(GetAccUpdateValidatorKey(validator.Address), bz) - - } - - return -} - -func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { - - // first retreive the old candidate record - candidate, found := k.GetCandidate(ctx, address) - if !found { - return - } - - // delete the old candidate record - store := ctx.KVStore(k.storeKey) - store.Delete(GetCandidateKey(address)) - store.Delete(GetValidatorKey(address, candidate.Assets, candidate.ValidatorBondHeight, candidate.ValidatorBondCounter, k.cdc)) - - // delete from recent and power weighted validator groups if the validator - // exists and add validator with zero power to the validator updates - if store.Get(GetRecentValidatorKey(address)) == nil { - return - } - bz, err := k.cdc.MarshalBinary(candidate.validator().abciValidatorZero(k.cdc)) - if err != nil { - panic(err) - } - store.Set(GetAccUpdateValidatorKey(address), bz) - store.Delete(GetRecentValidatorKey(address)) + return validators[:i] // trim } //___________________________________________________________________________ -// Get the validator set from the candidates. The correct subset is retrieved -// by iterating through an index of the candidates sorted by power, stored -// using the ValidatorsKey. Simultaniously the most recent the validator -// records are updated in store with the RecentValidatorsKey. This store is -// used to determine if a candidate is a validator without needing to iterate -// over the subspace as we do in GetValidators -func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { +// get the group of the bonded validators +func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) - // clear the recent validators store, add to the ToKickOut Temp store - iterator := store.Iterator(subspace(RecentValidatorsKey)) - for ; iterator.Valid(); iterator.Next() { - addr := AddrFromKey(iterator.Key()) - - // iterator.Value is the validator object - store.Set(GetToKickOutValidatorKey(addr), iterator.Value()) - store.Delete(iterator.Key()) - } - iterator.Close() - // add the actual validator power sorted store maxValidators := k.GetParams(ctx).MaxValidators - iterator = store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest validators = make([]Validator, maxValidators) + + iterator := store.SubspaceIterator(ValidatorsBondedKey) i := 0 - for ; ; i++ { + 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 := store.ReverseSubspaceIterator(ValidatorsByPowerKey) // largest to smallest + i := 0 + for { if !iterator.Valid() || i > int(maxValidators-1) { iterator.Close() break } - bz := iterator.Value() - var validator Validator - err := k.cdc.UnmarshalBinary(bz, &validator) - if err != nil { - panic(err) + address := iterator.Value() + validator, found := k.getValidator(store, address) + if !found { + panic(fmt.Sprintf("validator record not found for address: %v\n", address)) + } + if validator.Status() == sdk.Bonded { + validators[i] = validator + i++ } - validators[i] = validator - - // remove from ToKickOut group - store.Delete(GetToKickOutValidatorKey(validator.Address)) - - // also add to the recent validators group - store.Set(GetRecentValidatorKey(validator.Address), bz) - iterator.Next() } - - // add any kicked out validators to the acc change - iterator = store.Iterator(subspace(ToKickOutValidatorsKey)) - for ; iterator.Valid(); iterator.Next() { - key := iterator.Key() - addr := AddrFromKey(key) - - // get the zero abci validator from the ToKickOut iterator value - bz := iterator.Value() - var validator Validator - err := k.cdc.UnmarshalBinary(bz, &validator) - if err != nil { - panic(err) - } - bz, err = k.cdc.MarshalBinary(validator.abciValidatorZero(k.cdc)) - if err != nil { - panic(err) - } - - store.Set(GetAccUpdateValidatorKey(addr), bz) - store.Delete(key) - } - iterator.Close() - return validators[:i] // trim } -// TODO this is madly inefficient because need to call every time we set a candidate -// Should use something better than an iterator maybe? -// Used to determine if something has just been added to the actual validator set -func (k Keeper) isNewValidator(ctx sdk.Context, store sdk.KVStore, address sdk.Address) bool { - // add the actual validator power sorted store - maxVal := k.GetParams(ctx).MaxValidators - iterator := store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest - for i := 0; ; i++ { - if !iterator.Valid() || i > int(maxVal-1) { - iterator.Close() - break - } - bz := iterator.Value() - var val Validator - err := k.cdc.UnmarshalBinary(bz, &val) - if err != nil { - panic(err) - } - if bytes.Equal(val.Address, address) { - return true - } - iterator.Next() - } - - return false -} - -// Is the address provided a part of the most recently saved validator group? -func (k Keeper) IsRecentValidator(ctx sdk.Context, address sdk.Address) bool { - store := ctx.KVStore(k.storeKey) - if store.Get(GetRecentValidatorKey(address)) == nil { - return false - } - return true -} - //_________________________________________________________________________ -// Accumulated updates to the validator set +// Accumulated updates to the active/bonded validator set for tendermint // get the most recently updated validators -func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []abci.Validator) { +func (k Keeper) getTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) { store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(AccUpdateValidatorsKey)) //smallest to largest + iterator := store.SubspaceIterator(TendermintUpdatesKey) //smallest to largest for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val abci.Validator - err := k.cdc.UnmarshalBinary(valBytes, &val) - if err != nil { - panic(err) - } + k.cdc.MustUnmarshalBinary(valBytes, &val) updates = append(updates, val) } iterator.Close() return } -// remove all validator update entries -func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) { +// remove all validator update entries after applied to Tendermint +func (k Keeper) clearTendermintUpdates(ctx sdk.Context) { store := ctx.KVStore(k.storeKey) // delete subspace - iterator := store.Iterator(subspace(AccUpdateValidatorsKey)) + iterator := store.SubspaceIterator(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) + }() + + // retreive the old validator record + oldValidator, oldFound := k.GetValidator(ctx, ownerAddr) + + if validator.Revoked && oldValidator.Status() == sdk.Bonded { + validator, pool = validator.UpdateStatus(pool, sdk.Unbonded) + k.setPool(ctx, pool) + } + + 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 occured to be updated + validator = updatedVal + } + return validator +} + +// XXX TODO build in consideration for revoked +// +// 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. Simultaniously +// 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 := store.ReverseSubspaceIterator(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 := store.SubspaceIterator(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 = store.ReverseSubspaceIterator(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) { + 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)) +} + +// 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 retreive 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(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) GetDelegatorBond(ctx sdk.Context, - delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { +func (k Keeper) GetDelegation(ctx sdk.Context, + delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) { store := ctx.KVStore(k.storeKey) - delegatorBytes := store.Get(GetDelegatorBondKey(delegatorAddr, candidateAddr, k.cdc)) + delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc)) if delegatorBytes == nil { return bond, false } - err := k.cdc.UnmarshalBinary(delegatorBytes, &bond) - if err != nil { - panic(err) - } + k.cdc.MustUnmarshalBinary(delegatorBytes, &bond) return bond, true } -// load all bonds -func (k Keeper) getBonds(ctx sdk.Context, maxRetrieve int16) (bonds []DelegatorBond) { +// load all delegations used during genesis dump +func (k Keeper) getAllDelegations(ctx sdk.Context) (delegations []Delegation) { store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(DelegatorBondKeyPrefix)) + iterator := store.SubspaceIterator(DelegationKey) - bonds = make([]DelegatorBond, maxRetrieve) i := 0 for ; ; i++ { - if !iterator.Valid() || i > int(maxRetrieve-1) { + if !iterator.Valid() { iterator.Close() break } bondBytes := iterator.Value() - var bond DelegatorBond - err := k.cdc.UnmarshalBinary(bondBytes, &bond) - if err != nil { - panic(err) - } - bonds[i] = bond + var delegation Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &delegation) + delegations = append(delegations, delegation) iterator.Next() } - return bonds[:i] // trim + return delegations[:i] // trim } // load all bonds of a delegator -func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { +func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) { store := ctx.KVStore(k.storeKey) - delegatorPrefixKey := GetDelegatorBondsKey(delegator, k.cdc) - iterator := store.Iterator(subspace(delegatorPrefixKey)) //smallest to largest + delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc) + iterator := store.SubspaceIterator(delegatorPrefixKey) //smallest to largest - bonds = make([]DelegatorBond, maxRetrieve) + bonds = make([]Delegation, maxRetrieve) i := 0 for ; ; i++ { if !iterator.Valid() || i > int(maxRetrieve-1) { @@ -409,87 +533,219 @@ func (k Keeper) GetDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet break } bondBytes := iterator.Value() - var bond DelegatorBond - err := k.cdc.UnmarshalBinary(bondBytes, &bond) - if err != nil { - panic(err) - } + var bond Delegation + k.cdc.MustUnmarshalBinary(bondBytes, &bond) bonds[i] = bond iterator.Next() } return bonds[:i] // trim } -func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { +func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(bond) - if err != nil { - panic(err) - } - store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) + b := k.cdc.MustMarshalBinary(bond) + store.Set(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc), b) } -func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { +func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) { store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) + store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc)) } //_______________________________________________________________________ // load/save the global staking params -func (k Keeper) GetParams(ctx sdk.Context) (params Params) { - // check if cached before anything - if !k.params.equal(Params{}) { - return k.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") } - err := k.cdc.UnmarshalBinary(b, ¶ms) - if err != nil { - panic(err) - } + 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) +} + func (k Keeper) setParams(ctx sdk.Context, params Params) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(params) - if err != nil { - panic(err) + 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) - k.params = Params{} // clear the cache } //_______________________________________________________________________ // load/save the pool func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) { - // check if cached before anything - if !k.pool.equal(Pool{}) { - return k.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") } - err := k.cdc.UnmarshalBinary(b, &pool) - if err != nil { - panic(err) // This error should never occur big problem if does - } + k.cdc.MustUnmarshalBinary(b, &pool) return } func (k Keeper) setPool(ctx sdk.Context, p Pool) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(p) - if err != nil { - panic(err) - } + b := k.cdc.MustMarshalBinary(p) store.Set(PoolKey, b) - k.pool = Pool{} //clear the cache +} + +//__________________________________________________________________________ + +// 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) +} + +//__________________________________________________________________________ + +// 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 := store.SubspaceIterator(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 := store.SubspaceIterator(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 +} + +// iterate through the active validator set and perform the provided function +func (k Keeper) IterateDelegators(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 := store.SubspaceIterator(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() } diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index eb641b883b..5a84d08f29 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -5,6 +5,7 @@ import ( 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 @@ -12,67 +13,64 @@ import ( //nolint var ( // Keys for store prefixes - ParamKey = []byte{0x00} // key for global parameters relating to staking - PoolKey = []byte{0x01} // key for global parameters relating to staking - CandidatesKey = []byte{0x02} // prefix for each key to a candidate - ValidatorsKey = []byte{0x03} // prefix for each key to a validator - AccUpdateValidatorsKey = []byte{0x04} // prefix for each key to a validator which is being updated - RecentValidatorsKey = []byte{0x05} // prefix for each key to the last updated validator group - - ToKickOutValidatorsKey = []byte{0x06} // prefix for each key to the last updated validator group - - DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond - - CounterKey = []byte{0x08} // key for block-local tx index + 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 + ValidatorsBondedKey = []byte{0x03} // prefix for each key to bonded/actively validating validators + ValidatorsByPowerKey = []byte{0x04} // prefix for each key to a validator sorted by power + ValidatorCliffKey = []byte{0x05} // key for block-local tx index + ValidatorPowerCliffKey = []byte{0x06} // key for block-local tx index + TendermintUpdatesKey = []byte{0x07} // prefix for each key to a validator which is being updated + DelegationKey = []byte{0x08} // prefix for each key to a delegator's bond + IntraTxCounterKey = []byte{0x09} // key for block-local tx index ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch -// get the key for the candidate with address -func GetCandidateKey(addr sdk.Address) []byte { - return append(CandidatesKey, addr.Bytes()...) +// get the key for the validator with address +func GetValidatorKey(ownerAddr sdk.Address) []byte { + return append(ValidatorsKey, ownerAddr.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 GetValidatorKey(addr sdk.Address, power sdk.Rat, height int64, counter int16, cdc *wire.Codec) []byte { +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 + // heightBytes and counterBytes represent strings like powerBytes does heightBytes := make([]byte, binary.MaxVarintLen64) - binary.BigEndian.PutUint64(heightBytes, ^uint64(height)) // invert height (older validators first) + binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first) counterBytes := make([]byte, 2) - binary.BigEndian.PutUint16(counterBytes, ^uint16(counter)) // invert counter (first txns have priority) - return append(ValidatorsKey, append(powerBytes, append(heightBytes, append(counterBytes, addr.Bytes()...)...)...)...) + binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority) + return append(ValidatorsByPowerKey, + 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 GetAccUpdateValidatorKey(addr sdk.Address) []byte { - return append(AccUpdateValidatorsKey, addr.Bytes()...) +func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte { + return append(TendermintUpdatesKey, ownerAddr.Bytes()...) } -// get the key for the accumulated update validators -func GetRecentValidatorKey(addr sdk.Address) []byte { - return append(RecentValidatorsKey, addr.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()...) } -// reverse operation of GetRecentValidatorKey -func AddrFromKey(key []byte) sdk.Address { - return key[1:] -} - -// get the key for the accumulated update validators -func GetToKickOutValidatorKey(addr sdk.Address) []byte { - return append(ToKickOutValidatorsKey, addr.Bytes()...) -} - -// get the key for delegator bond with candidate -func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...) -} - -// get the prefix for a delegator for all candidates -func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { +// 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(DelegatorBondKeyPrefix, res...) + return append(DelegationKey, res...) } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 8156071f10..f28a2cf684 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -23,652 +23,715 @@ var ( } ) -// This function tests GetCandidate, GetCandidates, setCandidate, removeCandidate -func TestCandidate(t *testing.T) { +func TestSetValidator(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) - //construct the candidates - var candidates [3]Candidate + // test how the validator is set from a purely unbonbed pool + validator := NewValidator(addrVals[0], pks[0], 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]) + +} + +// This function tests updateValidator, GetValidator, GetValidatorsBonded, removeValidator +func TestValidatorBasics(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + //construct the validators + var validators [3]Validator amts := []int64{9, 8, 7} for i, amt := range amts { - candidates[i] = Candidate{ - Address: addrVals[i], - PubKey: pks[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } + validators[i] = NewValidator(addrVals[i], pks[i], Description{}) + validators[i].PoolShares = NewUnbondedShares(sdk.ZeroRat()) + validators[i].addTokensFromDel(pool, amt) } // check the empty keeper first - _, found := keeper.GetCandidate(ctx, addrVals[0]) + _, found := keeper.GetValidator(ctx, addrVals[0]) assert.False(t, found) - resCands := keeper.GetCandidates(ctx, 100) - assert.Zero(t, len(resCands)) + resVals := keeper.GetValidatorsBonded(ctx) + assert.Zero(t, len(resVals)) // set and retrieve a record - keeper.setCandidate(ctx, candidates[0]) - resCand, found := keeper.GetCandidate(ctx, addrVals[0]) + validators[0] = keeper.updateValidator(ctx, validators[0]) + resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) - assert.True(t, candidatesEqual(candidates[0], resCand), "%v \n %v", resCand, candidates[0]) + 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 - candidates[0].Liabilities = sdk.NewRat(99) - keeper.setCandidate(ctx, candidates[0]) - resCand, found = keeper.GetCandidate(ctx, addrVals[0]) + 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(t, candidatesEqual(candidates[0], resCand)) + assert.True(ValEq(t, validators[0], resVal)) - // also test that the address has been added to address list - resCands = keeper.GetCandidates(ctx, 100) - require.Equal(t, 1, len(resCands)) - assert.Equal(t, addrVals[0], resCands[0].Address) + resVals = keeper.GetValidatorsBonded(ctx) + require.Equal(t, 1, len(resVals)) + assert.True(ValEq(t, validators[0], resVals[0])) - // add other candidates - keeper.setCandidate(ctx, candidates[1]) - keeper.setCandidate(ctx, candidates[2]) - resCand, found = keeper.GetCandidate(ctx, addrVals[1]) + // 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(t, candidatesEqual(candidates[1], resCand), "%v \n %v", resCand, candidates[1]) - resCand, found = keeper.GetCandidate(ctx, addrVals[2]) + assert.True(ValEq(t, validators[1], resVal)) + resVal, found = keeper.GetValidator(ctx, addrVals[2]) require.True(t, found) - assert.True(t, candidatesEqual(candidates[2], resCand), "%v \n %v", resCand, candidates[2]) - resCands = keeper.GetCandidates(ctx, 100) - require.Equal(t, 3, len(resCands)) - assert.True(t, candidatesEqual(candidates[0], resCands[0]), "%v \n %v", resCands[0], candidates[0]) - assert.True(t, candidatesEqual(candidates[1], resCands[1]), "%v \n %v", resCands[1], candidates[1]) - assert.True(t, candidatesEqual(candidates[2], resCands[2]), "%v \n %v", resCands[2], candidates[2]) + 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[0])) + assert.True(ValEq(t, validators[2], resVals[1])) // remove a record - keeper.removeCandidate(ctx, candidates[1].Address) - _, found = keeper.GetCandidate(ctx, addrVals[1]) + keeper.removeValidator(ctx, validators[1].Owner) + _, found = keeper.GetValidator(ctx, addrVals[1]) assert.False(t, found) } -// tests GetDelegatorBond, GetDelegatorBonds, SetDelegatorBond, removeDelegatorBond, GetBonds +// test how the validators are sorted, tests GetValidatorsByPower +func GetValidatorSortingUnmixed(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 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, 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 seperate out into multiple tests +func TestGetValidatorsEdgeCases(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 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, 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, 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, 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, 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, validators[0].PubKey.Bytes(), updates[0].PubKey) + assert.Equal(t, validators[1].PubKey.Bytes(), 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, 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, 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, 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, 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, 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, 0) - //construct the candidates + //construct the validators amts := []int64{9, 8, 7} - var candidates [3]Candidate + var validators [3]Validator for i, amt := range amts { - candidates[i] = Candidate{ - Address: addrVals[i], - PubKey: pks[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } + validators[i] = NewValidator(addrVals[i], pks[i], Description{}) + validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt)) + validators[i].DelegatorShares = sdk.NewRat(amt) } - // first add a candidates[0] to delegate too - keeper.setCandidate(ctx, candidates[0]) + // first add a validators[0] to delegate too + validators[0] = keeper.updateValidator(ctx, validators[0]) - bond1to1 := DelegatorBond{ + bond1to1 := Delegation{ DelegatorAddr: addrDels[0], - CandidateAddr: addrVals[0], + ValidatorAddr: addrVals[0], Shares: sdk.NewRat(9), } // check the empty keeper first - _, found := keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + _, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.False(t, found) // set and retrieve a record - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found := keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) - assert.True(t, bondsEqual(bond1to1, resBond)) + assert.True(t, bond1to1.equal(resBond)) // modify a records, save, and retrieve bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = keeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) - assert.True(t, bondsEqual(bond1to1, resBond)) + assert.True(t, bond1to1.equal(resBond)) // add some more records - keeper.setCandidate(ctx, candidates[1]) - keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegatorBond(ctx, bond1to2) - keeper.setDelegatorBond(ctx, bond1to3) - keeper.setDelegatorBond(ctx, bond2to1) - keeper.setDelegatorBond(ctx, bond2to2) - keeper.setDelegatorBond(ctx, bond2to3) + 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.GetDelegatorBonds(ctx, addrDels[0], 5) + resBonds := keeper.GetDelegations(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) - assert.True(t, bondsEqual(bond1to1, resBonds[0])) - assert.True(t, bondsEqual(bond1to2, resBonds[1])) - assert.True(t, bondsEqual(bond1to3, resBonds[2])) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[0], 3) + 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.GetDelegatorBonds(ctx, addrDels[0], 2) + resBonds = keeper.GetDelegations(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) - assert.True(t, bondsEqual(bond2to1, resBonds[0])) - assert.True(t, bondsEqual(bond2to2, resBonds[1])) - assert.True(t, bondsEqual(bond2to3, resBonds[2])) - allBonds := keeper.getBonds(ctx, 1000) + 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, bondsEqual(bond1to1, allBonds[0])) - assert.True(t, bondsEqual(bond1to2, allBonds[1])) - assert.True(t, bondsEqual(bond1to3, allBonds[2])) - assert.True(t, bondsEqual(bond2to1, allBonds[3])) - assert.True(t, bondsEqual(bond2to2, allBonds[4])) - assert.True(t, bondsEqual(bond2to3, allBonds[5])) + 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.removeDelegatorBond(ctx, bond2to3) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[2]) + keeper.removeDelegation(ctx, bond2to3) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2]) assert.False(t, found) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 2, len(resBonds)) - assert.True(t, bondsEqual(bond2to1, resBonds[0])) - assert.True(t, bondsEqual(bond2to2, resBonds[1])) + assert.True(t, bond2to1.equal(resBonds[0])) + assert.True(t, bond2to2.equal(resBonds[1])) // delete all the records from delegator 2 - keeper.removeDelegatorBond(ctx, bond2to1) - keeper.removeDelegatorBond(ctx, bond2to2) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[0]) + keeper.removeDelegation(ctx, bond2to1) + keeper.removeDelegation(ctx, bond2to2) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0]) assert.False(t, found) - _, found = keeper.GetDelegatorBond(ctx, addrDels[1], addrVals[1]) + _, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1]) assert.False(t, found) - resBonds = keeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = keeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 0, len(resBonds)) } -// TODO integrate in testing for equal validators, whichever one was a validator -// first remains the validator https://github.com/cosmos/cosmos-sdk/issues/582 -func TestGetValidators(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - // initialize some candidates into the state - amts := []int64{0, 100, 1, 400, 200} - n := len(amts) - var candidates [5]Candidate - for i, amt := range amts { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } - keeper.setCandidate(ctx, c) - candidates[i] = c - } - - // first make sure everything as normal is ordered - validators := keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(400), validators[0].Power, "%v", validators) - assert.Equal(t, sdk.NewRat(200), validators[1].Power, "%v", validators) - assert.Equal(t, sdk.NewRat(100), validators[2].Power, "%v", validators) - assert.Equal(t, sdk.NewRat(1), validators[3].Power, "%v", validators) - assert.Equal(t, sdk.NewRat(0), validators[4].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) - assert.Equal(t, candidates[1].Address, validators[2].Address, "%v", validators) - assert.Equal(t, candidates[2].Address, validators[3].Address, "%v", validators) - assert.Equal(t, candidates[0].Address, validators[4].Address, "%v", validators) - - // test a basic increase in voting power - candidates[3].Assets = sdk.NewRat(500) - keeper.setCandidate(ctx, candidates[3]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(500), validators[0].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - - // test a decrease in voting power - candidates[3].Assets = sdk.NewRat(300) - keeper.setCandidate(ctx, candidates[3]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(300), validators[0].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - - // test equal voting power, different age - candidates[3].Assets = sdk.NewRat(200) - ctx = ctx.WithBlockHeight(10) - keeper.setCandidate(ctx, candidates[3]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(200), validators[0].Power, "%v", validators) - assert.Equal(t, sdk.NewRat(200), validators[1].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) - assert.Equal(t, int64(0), validators[0].Height, "%v", validators) - assert.Equal(t, int64(0), validators[1].Height, "%v", validators) - - // no change in voting power - no change in sort - ctx = ctx.WithBlockHeight(20) - keeper.setCandidate(ctx, candidates[4]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) - - // change in voting power of both candidates, both still in v-set, no age change - candidates[3].Assets = sdk.NewRat(300) - candidates[4].Assets = sdk.NewRat(300) - keeper.setCandidate(ctx, candidates[3]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - ctx = ctx.WithBlockHeight(30) - keeper.setCandidate(ctx, candidates[4]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) - assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) - - // now 2 max validators - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - candidates[0].Assets = sdk.NewRat(500) - keeper.setCandidate(ctx, candidates[0]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - // candidate 3 was set before candidate 4 - require.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) - - /* - A candidate which leaves the validator 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. - - ref https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-380757108 - */ - candidates[4].Assets = sdk.NewRat(301) - keeper.setCandidate(ctx, candidates[4]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) - ctx = ctx.WithBlockHeight(40) - // candidate 4 kicked out temporarily - candidates[4].Assets = sdk.NewRat(200) - keeper.setCandidate(ctx, candidates[4]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) - // candidate 4 does not get spot back - candidates[4].Assets = sdk.NewRat(300) - keeper.setCandidate(ctx, candidates[4]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) - candidate, exists := keeper.GetCandidate(ctx, candidates[4].Address) - require.Equal(t, exists, true) - require.Equal(t, candidate.ValidatorBondHeight, int64(40)) - - /* - If two candidates both increase to the same voting power in the same block, - the one with the first transaction should take precedence (become a validator). - - ref https://github.com/cosmos/cosmos-sdk/issues/582#issuecomment-381250392 - */ - candidates[0].Assets = sdk.NewRat(2000) - keeper.setCandidate(ctx, candidates[0]) - candidates[1].Assets = sdk.NewRat(1000) - candidates[2].Assets = sdk.NewRat(1000) - keeper.setCandidate(ctx, candidates[1]) - keeper.setCandidate(ctx, candidates[2]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[1].Address, validators[1].Address, "%v", validators) - candidates[1].Assets = sdk.NewRat(1100) - candidates[2].Assets = sdk.NewRat(1100) - keeper.setCandidate(ctx, candidates[2]) - keeper.setCandidate(ctx, candidates[1]) - validators = keeper.GetValidators(ctx) - require.Equal(t, uint16(len(validators)), params.MaxValidators) - require.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - require.Equal(t, candidates[2].Address, validators[1].Address, "%v", validators) - - // reset assets / heights - params.MaxValidators = 100 - keeper.setParams(ctx, params) - candidates[0].Assets = sdk.NewRat(0) - candidates[1].Assets = sdk.NewRat(100) - candidates[2].Assets = sdk.NewRat(1) - candidates[3].Assets = sdk.NewRat(300) - candidates[4].Assets = sdk.NewRat(200) - ctx = ctx.WithBlockHeight(0) - keeper.setCandidate(ctx, candidates[0]) - keeper.setCandidate(ctx, candidates[1]) - keeper.setCandidate(ctx, candidates[2]) - keeper.setCandidate(ctx, candidates[3]) - keeper.setCandidate(ctx, candidates[4]) - - // test a swap in voting power - candidates[0].Assets = sdk.NewRat(600) - keeper.setCandidate(ctx, candidates[0]) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(600), validators[0].Power, "%v", validators) - assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - assert.Equal(t, sdk.NewRat(300), validators[1].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) - - // test the max validators term - params = keeper.GetParams(ctx) - n = 2 - params.MaxValidators = uint16(n) - keeper.setParams(ctx, params) - validators = keeper.GetValidators(ctx) - require.Equal(t, len(validators), n) - assert.Equal(t, sdk.NewRat(600), validators[0].Power, "%v", validators) - assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) - assert.Equal(t, sdk.NewRat(300), validators[1].Power, "%v", validators) - assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) -} - -// clear the tracked changes to the validator set -func TestClearAccUpdateValidators(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{100, 400, 200} - candidates := make([]Candidate, len(amts)) - for i, amt := range amts { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } - candidates[i] = c - keeper.setCandidate(ctx, c) - } - - acc := keeper.getAccUpdateValidators(ctx) - assert.Equal(t, len(amts), len(acc)) - keeper.clearAccUpdateValidators(ctx) - acc = keeper.getAccUpdateValidators(ctx) - assert.Equal(t, 0, len(acc)) -} - -// test the mechanism which keeps track of a validator set change -func TestGetAccUpdateValidators(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - params := defaultParams() - params.MaxValidators = 4 - keeper.setParams(ctx, params) - - // TODO eliminate use of candidatesIn here - // tests could be clearer if they just - // created the candidate at time of use - // and were labelled by power in the comments - // outlining in each test - amts := []int64{10, 11, 12, 13, 1} - var candidatesIn [5]Candidate - for i, amt := range amts { - candidatesIn[i] = Candidate{ - Address: addrs[i], - PubKey: pks[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } - } - - // test from nothing to something - // candidate set: {} -> {c1, c3} - // validator set: {} -> {c1, c3} - // accUpdate set: {} -> {c1, c3} - assert.Equal(t, 0, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.GetValidators(ctx))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.setCandidate(ctx, candidatesIn[1]) - keeper.setCandidate(ctx, candidatesIn[3]) - - vals := keeper.GetValidators(ctx) // to init recent validator set - require.Equal(t, 2, len(vals)) - acc := keeper.getAccUpdateValidators(ctx) - require.Equal(t, 2, len(acc)) - candidates := keeper.GetCandidates(ctx, 5) - require.Equal(t, 2, len(candidates)) - assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0]) - assert.Equal(t, candidates[1].validator().abciValidator(keeper.cdc), acc[1]) - assert.True(t, validatorsEqual(candidates[0].validator(), vals[1])) - assert.True(t, validatorsEqual(candidates[1].validator(), vals[0])) - - // test identical, - // candidate set: {c1, c3} -> {c1, c3} - // accUpdate set: {} -> {} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.setCandidate(ctx, candidates[0]) - keeper.setCandidate(ctx, candidates[1]) - - require.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - // test single value change - // candidate set: {c1, c3} -> {c1', c3} - // accUpdate set: {} -> {c1'} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - candidates[0].Assets = sdk.NewRat(600) - keeper.setCandidate(ctx, candidates[0]) - - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 2, len(candidates)) - assert.True(t, candidates[0].Assets.Equal(sdk.NewRat(600))) - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 1, len(acc)) - assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0]) - - // test multiple value change - // candidate set: {c1, c3} -> {c1', c3'} - // accUpdate set: {c1, c3} -> {c1', c3'} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - candidates[0].Assets = sdk.NewRat(200) - candidates[1].Assets = sdk.NewRat(100) - keeper.setCandidate(ctx, candidates[0]) - keeper.setCandidate(ctx, candidates[1]) - - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 2, len(acc)) - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 2, len(candidates)) - require.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0]) - require.Equal(t, candidates[1].validator().abciValidator(keeper.cdc), acc[1]) - - // test validtor added at the beginning - // candidate set: {c1, c3} -> {c0, c1, c3} - // accUpdate set: {} -> {c0} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.setCandidate(ctx, candidatesIn[0]) - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 1, len(acc)) - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 3, len(candidates)) - assert.Equal(t, candidates[0].validator().abciValidator(keeper.cdc), acc[0]) - - // test validator added at the middle - // candidate set: {c0, c1, c3} -> {c0, c1, c2, c3] - // accUpdate set: {} -> {c2} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 3, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.setCandidate(ctx, candidatesIn[2]) - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 1, len(acc)) - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 4, len(candidates)) - assert.Equal(t, candidates[2].validator().abciValidator(keeper.cdc), acc[0]) - - // test candidate added at the end but not inserted in the valset - // candidate set: {c0, c1, c2, c3} -> {c0, c1, c2, c3, c4} - // validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3} - // accUpdate set: {} -> {} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 4, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.setCandidate(ctx, candidatesIn[4]) - - assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4 - - // test candidate change its power but still not in the valset - // candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4} - // validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3} - // accUpdate set: {} -> {} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - candidatesIn[4].Assets = sdk.NewRat(1) - keeper.setCandidate(ctx, candidatesIn[4]) - - assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4 - - // test candidate change its power and become a validator (pushing out an existing) - // candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4} - // validator set: {c0, c1, c2, c3} -> {c1, c2, c3, c4} - // accUpdate set: {} -> {c0, c4} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - candidatesIn[4].Assets = sdk.NewRat(1000) - keeper.setCandidate(ctx, candidatesIn[4]) - - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 5, len(candidates)) - vals = keeper.GetValidators(ctx) - require.Equal(t, 4, len(vals)) - assert.Equal(t, candidatesIn[1].Address, vals[1].Address) - assert.Equal(t, candidatesIn[2].Address, vals[3].Address) - assert.Equal(t, candidatesIn[3].Address, vals[2].Address) - assert.Equal(t, candidatesIn[4].Address, vals[0].Address) - - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 2, len(acc), "%v", acc) - - assert.Equal(t, candidatesIn[0].PubKey.Bytes(), acc[0].PubKey) - assert.Equal(t, int64(0), acc[0].Power) - assert.Equal(t, vals[0].abciValidator(keeper.cdc), acc[1]) - - // test from something to nothing - // candidate set: {c0, c1, c2, c3, c4} -> {} - // validator set: {c1, c2, c3, c4} -> {} - // accUpdate set: {} -> {c1, c2, c3, c4} - keeper.clearAccUpdateValidators(ctx) - assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) - assert.Equal(t, 4, len(keeper.GetValidators(ctx))) - assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) - - keeper.removeCandidate(ctx, candidatesIn[0].Address) - keeper.removeCandidate(ctx, candidatesIn[1].Address) - keeper.removeCandidate(ctx, candidatesIn[2].Address) - keeper.removeCandidate(ctx, candidatesIn[3].Address) - keeper.removeCandidate(ctx, candidatesIn[4].Address) - - vals = keeper.GetValidators(ctx) - assert.Equal(t, 0, len(vals), "%v", vals) - candidates = keeper.GetCandidates(ctx, 5) - require.Equal(t, 0, len(candidates)) - acc = keeper.getAccUpdateValidators(ctx) - require.Equal(t, 4, len(acc)) - assert.Equal(t, candidatesIn[1].PubKey.Bytes(), acc[0].PubKey) - assert.Equal(t, candidatesIn[2].PubKey.Bytes(), acc[1].PubKey) - assert.Equal(t, candidatesIn[3].PubKey.Bytes(), acc[2].PubKey) - assert.Equal(t, candidatesIn[4].PubKey.Bytes(), acc[3].PubKey) - assert.Equal(t, int64(0), acc[0].Power) - assert.Equal(t, int64(0), acc[1].Power) - assert.Equal(t, int64(0), acc[2].Power) - assert.Equal(t, int64(0), acc[3].Power) -} - -// test if is a validator from the last update -func TestIsRecentValidator(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - amts := []int64{9, 8, 7, 10, 6} - var candidatesIn [5]Candidate - for i, amt := range amts { - candidatesIn[i] = Candidate{ - Address: addrVals[i], - PubKey: pks[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), - } - } - - // test that an empty validator set doesn't have any validators - validators := keeper.GetValidators(ctx) - assert.Equal(t, 0, len(validators)) - - // get the validators for the first time - keeper.setCandidate(ctx, candidatesIn[0]) - keeper.setCandidate(ctx, candidatesIn[1]) - validators = keeper.GetValidators(ctx) - require.Equal(t, 2, len(validators)) - assert.True(t, validatorsEqual(candidatesIn[0].validator(), validators[0])) - c1ValWithCounter := candidatesIn[1].validator() - c1ValWithCounter.Counter = int16(1) - assert.True(t, validatorsEqual(c1ValWithCounter, validators[1])) - - // test a basic retrieve of something that should be a recent validator - assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address)) - assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[1].Address)) - - // test a basic retrieve of something that should not be a recent validator - assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[2].Address)) - - // remove that validator, but don't retrieve the recent validator group - keeper.removeCandidate(ctx, candidatesIn[0].Address) - - // test that removed validator is not considered a recent validator - assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address)) -} - func TestParams(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) expParams := defaultParams() //check that the empty keeper loads the default resParams := keeper.GetParams(ctx) - assert.Equal(t, expParams, resParams) + assert.True(t, expParams.equal(resParams)) //modify a params, save, and retrieve expParams.MaxValidators = 777 keeper.setParams(ctx, expParams) resParams = keeper.GetParams(ctx) - assert.Equal(t, expParams, resParams) + assert.True(t, expParams.equal(resParams)) } func TestPool(t *testing.T) { @@ -677,11 +740,11 @@ func TestPool(t *testing.T) { //check that the empty keeper loads the default resPool := keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + assert.True(t, expPool.equal(resPool)) //modify a params, save, and retrieve - expPool.TotalSupply = 777 + expPool.BondedTokens = 777 keeper.setPool(ctx, expPool) resPool = keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + assert.True(t, expPool.equal(resPool)) } diff --git a/x/stake/msg.go b/x/stake/msg.go index 4e322e6402..0adff84d9b 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -31,16 +31,16 @@ func init() { // MsgDeclareCandidacy - struct for unbonding transactions type MsgDeclareCandidacy struct { Description - CandidateAddr sdk.Address `json:"address"` + ValidatorAddr sdk.Address `json:"address"` PubKey crypto.PubKey `json:"pubkey"` Bond sdk.Coin `json:"bond"` } -func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey, +func NewMsgDeclareCandidacy(validatorAddr sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy { return MsgDeclareCandidacy{ Description: description, - CandidateAddr: candidateAddr, + ValidatorAddr: validatorAddr, PubKey: pubkey, Bond: bond, } @@ -48,7 +48,7 @@ func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey, //nolint func (msg MsgDeclareCandidacy) Type() string { return MsgType } //TODO update "stake/declarecandidacy" -func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } +func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } // get the bytes for the message signer to sign on func (msg MsgDeclareCandidacy) GetSignBytes() []byte { @@ -57,8 +57,8 @@ func (msg MsgDeclareCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { - if msg.CandidateAddr == nil { - return ErrCandidateEmpty(DefaultCodespace) + if msg.ValidatorAddr == nil { + return ErrValidatorEmpty(DefaultCodespace) } if msg.Bond.Denom != StakingToken { return ErrBadBondingDenom(DefaultCodespace) @@ -75,22 +75,22 @@ func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { //______________________________________________________________________ -// MsgEditCandidacy - struct for editing a candidate +// MsgEditCandidacy - struct for editing a validator type MsgEditCandidacy struct { Description - CandidateAddr sdk.Address `json:"address"` + ValidatorAddr sdk.Address `json:"address"` } -func NewMsgEditCandidacy(candidateAddr sdk.Address, description Description) MsgEditCandidacy { +func NewMsgEditCandidacy(validatorAddr sdk.Address, description Description) MsgEditCandidacy { return MsgEditCandidacy{ Description: description, - CandidateAddr: candidateAddr, + ValidatorAddr: validatorAddr, } } //nolint func (msg MsgEditCandidacy) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" -func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } +func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.ValidatorAddr} } // get the bytes for the message signer to sign on func (msg MsgEditCandidacy) GetSignBytes() []byte { @@ -103,8 +103,8 @@ func (msg MsgEditCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { - if msg.CandidateAddr == nil { - return ErrCandidateEmpty(DefaultCodespace) + if msg.ValidatorAddr == nil { + return ErrValidatorEmpty(DefaultCodespace) } empty := Description{} if msg.Description == empty { @@ -118,14 +118,14 @@ func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { // MsgDelegate - struct for bonding transactions type MsgDelegate struct { DelegatorAddr sdk.Address `json:"address"` - CandidateAddr sdk.Address `json:"address"` + ValidatorAddr sdk.Address `json:"address"` Bond sdk.Coin `json:"bond"` } -func NewMsgDelegate(delegatorAddr, candidateAddr sdk.Address, bond sdk.Coin) MsgDelegate { +func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, - CandidateAddr: candidateAddr, + ValidatorAddr: validatorAddr, Bond: bond, } } @@ -148,8 +148,8 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { if msg.DelegatorAddr == nil { return ErrBadDelegatorAddr(DefaultCodespace) } - if msg.CandidateAddr == nil { - return ErrBadCandidateAddr(DefaultCodespace) + if msg.ValidatorAddr == nil { + return ErrBadValidatorAddr(DefaultCodespace) } if msg.Bond.Denom != StakingToken { return ErrBadBondingDenom(DefaultCodespace) @@ -165,14 +165,14 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { // MsgUnbond - struct for unbonding transactions type MsgUnbond struct { DelegatorAddr sdk.Address `json:"address"` - CandidateAddr sdk.Address `json:"address"` + ValidatorAddr sdk.Address `json:"address"` Shares string `json:"shares"` } -func NewMsgUnbond(delegatorAddr, candidateAddr sdk.Address, shares string) MsgUnbond { +func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUnbond { return MsgUnbond{ DelegatorAddr: delegatorAddr, - CandidateAddr: candidateAddr, + ValidatorAddr: validatorAddr, Shares: shares, } } @@ -195,8 +195,8 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { if msg.DelegatorAddr == nil { return ErrBadDelegatorAddr(DefaultCodespace) } - if msg.CandidateAddr == nil { - return ErrBadCandidateAddr(DefaultCodespace) + if msg.ValidatorAddr == nil { + return ErrBadValidatorAddr(DefaultCodespace) } if msg.Shares != "MAX" { rat, err := sdk.NewRatFromDecimal(msg.Shares) diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 19e8335bee..03a5fbf62f 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -22,7 +22,7 @@ var ( func TestMsgDeclareCandidacy(t *testing.T) { tests := []struct { name, moniker, identity, website, details string - candidateAddr sdk.Address + validatorAddr sdk.Address pubkey crypto.PubKey bond sdk.Coin expectPass bool @@ -40,7 +40,7 @@ func TestMsgDeclareCandidacy(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgDeclareCandidacy(tc.candidateAddr, tc.pubkey, tc.bond, description) + msg := NewMsgDeclareCandidacy(tc.validatorAddr, tc.pubkey, tc.bond, description) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -53,7 +53,7 @@ func TestMsgDeclareCandidacy(t *testing.T) { func TestMsgEditCandidacy(t *testing.T) { tests := []struct { name, moniker, identity, website, details string - candidateAddr sdk.Address + validatorAddr sdk.Address expectPass bool }{ {"basic good", "a", "b", "c", "d", addrs[0], true}, @@ -64,7 +64,7 @@ func TestMsgEditCandidacy(t *testing.T) { for _, tc := range tests { description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) - msg := NewMsgEditCandidacy(tc.candidateAddr, description) + msg := NewMsgEditCandidacy(tc.validatorAddr, description) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -78,21 +78,21 @@ func TestMsgDelegate(t *testing.T) { tests := []struct { name string delegatorAddr sdk.Address - candidateAddr 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 candidate", addrs[0], emptyAddr, 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.candidateAddr, tc.bond) + msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { @@ -106,7 +106,7 @@ func TestMsgUnbond(t *testing.T) { tests := []struct { name string delegatorAddr sdk.Address - candidateAddr sdk.Address + validatorAddr sdk.Address shares string expectPass bool }{ @@ -116,11 +116,11 @@ func TestMsgUnbond(t *testing.T) { {"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 candidate", addrs[0], emptyAddr, "0.1", false}, + {"empty validator", addrs[0], emptyAddr, "0.1", false}, } for _, tc := range tests { - msg := NewMsgUnbond(tc.delegatorAddr, tc.candidateAddr, tc.shares) + msg := NewMsgUnbond(tc.delegatorAddr, tc.validatorAddr, tc.shares) if tc.expectPass { assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) } else { diff --git a/x/stake/params.go b/x/stake/params.go new file mode 100644 index 0000000000..32b8c0ae83 --- /dev/null +++ b/x/stake/params.go @@ -0,0 +1,35 @@ +package stake + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Params defines the high level settings for staking +type Params struct { + InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + + 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 := cdcEmpty.MustMarshalBinary(&p) + bz2 := cdcEmpty.MustMarshalBinary(&p2) + return bytes.Equal(bz1, bz2) +} + +func defaultParams() Params { + return Params{ + InflationRateChange: sdk.NewRat(13, 100), + InflationMax: sdk.NewRat(20, 100), + InflationMin: sdk.NewRat(7, 100), + GoalBonded: sdk.NewRat(67, 100), + MaxValidators: 100, + BondDenom: "steak", + } +} diff --git a/x/stake/pool.go b/x/stake/pool.go index f0c7bfae5c..e2547b0503 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -1,13 +1,65 @@ package stake import ( + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" ) +// Pool - dynamic parameters of the current state +type Pool struct { + LooseUnbondedTokens int64 `json:"loose_unbonded_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 calcualtions +} + +func (p Pool) equal(p2 Pool) bool { + bz1 := cdcEmpty.MustMarshalBinary(&p) + bz2 := cdcEmpty.MustMarshalBinary(&p2) + return bytes.Equal(bz1, bz2) +} + +// initial pool for testing +func initialPool() Pool { + return Pool{ + LooseUnbondedTokens: 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.LooseUnbondedTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens +} + +//____________________________________________________________________ + // get the bond ratio of the global state func (p Pool) bondedRatio() sdk.Rat { - if p.TotalSupply > 0 { - return sdk.NewRat(p.BondedPool, p.TotalSupply) + if p.TokenSupply() > 0 { + return sdk.NewRat(p.BondedTokens, p.TokenSupply()) } return sdk.ZeroRat() } @@ -17,102 +69,65 @@ func (p Pool) bondedShareExRate() sdk.Rat { if p.BondedShares.IsZero() { return sdk.OneRat() } - return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) + return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares) } -// get the exchange rate of unbonded tokens held in candidates per issued share +// 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.UnbondedPool).Quo(p.UnbondedShares) -} - -// move a candidates asset pool from bonded to unbonded pool -func (p Pool) bondedToUnbondedPool(candidate Candidate) (Pool, Candidate) { - - // replace bonded shares with unbonded shares - p, tokens := p.removeSharesBonded(candidate.Assets) - p, candidate.Assets = p.addTokensUnbonded(tokens) - candidate.Status = Unbonded - return p, candidate -} - -// move a candidates asset pool from unbonded to bonded pool -func (p Pool) unbondedToBondedPool(candidate Candidate) (Pool, Candidate) { - - // replace unbonded shares with bonded shares - p, tokens := p.removeSharesUnbonded(candidate.Assets) - p, candidate.Assets = p.addTokensBonded(tokens) - candidate.Status = Bonded - return p, candidate + return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares) } //_______________________________________________________________________ -func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { - issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens) - p.BondedPool += amount - p.BondedShares = p.BondedShares.Add(issuedShares) - return p, issuedShares -} - -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.BondedPool = p.BondedPool - removedTokens - return p, removedTokens -} - -func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { - issuedShares = sdk.NewRat(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens) - p.UnbondedShares = p.UnbondedShares.Add(issuedShares) - p.UnbondedPool += amount - return p, issuedShares +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 + 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.UnbondedPool -= removedTokens + p.UnbondedTokens -= removedTokens return p, removedTokens } -//_______________________________________________________________________ - -// add tokens to a candidate -func (p Pool) candidateAddTokens(candidate Candidate, - amount int64) (p2 Pool, candidate2 Candidate, issuedDelegatorShares sdk.Rat) { - - exRate := candidate.delegatorShareExRate() - - var receivedGlobalShares sdk.Rat - if candidate.Status == Bonded { - p, receivedGlobalShares = p.addTokensBonded(amount) - } else { - p, receivedGlobalShares = p.addTokensUnbonded(amount) - } - candidate.Assets = candidate.Assets.Add(receivedGlobalShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - - return p, candidate, issuedDelegatorShares +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 + return p, NewUnbondingShares(issuedSharesAmount) } -// remove shares from a candidate -func (p Pool) candidateRemoveShares(candidate Candidate, - shares sdk.Rat) (p2 Pool, candidate2 Candidate, createdCoins int64) { - - //exRate := candidate.delegatorShareExRate() //XXX make sure not used - - globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) - if candidate.Status == Bonded { - p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove) - } else { - p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove) - } - candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) - candidate.Liabilities = candidate.Liabilities.Sub(shares) - return p, candidate, createdCoins +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 + 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 + 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 + return p, removedTokens } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index 6d70a85af3..99cfa5a127 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -1,8 +1,6 @@ package stake import ( - "fmt" - "math/rand" "testing" "github.com/stretchr/testify/assert" @@ -14,21 +12,22 @@ import ( func TestBondedRatio(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.TotalSupply = 3 - pool.BondedPool = 2 + pool.LooseUnbondedTokens = 1 + pool.BondedTokens = 2 // bonded pool / total supply require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3))) - pool.TotalSupply = 0 // avoids divide-by-zero + pool.LooseUnbondedTokens = 0 + pool.BondedTokens = 0 require.Equal(t, pool.bondedRatio(), sdk.ZeroRat()) } func TestBondedShareExRate(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.BondedPool = 3 + pool.BondedTokens = 3 pool.BondedShares = sdk.NewRat(10) // bonded pool / bonded shares @@ -39,10 +38,24 @@ func TestBondedShareExRate(t *testing.T) { require.Equal(t, pool.bondedShareExRate(), sdk.OneRat()) } +func TestUnbondingShareExRate(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + 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) { ctx, _, keeper := createTestInput(t, false, 0) pool := keeper.GetPool(ctx) - pool.UnbondedPool = 3 + pool.UnbondedTokens = 3 pool.UnbondedShares = sdk.NewRat(10) // unbonded pool / unbonded shares @@ -53,61 +66,6 @@ func TestUnbondedShareExRate(t *testing.T) { require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat()) } -func TestBondedToUnbondedPool(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - candA := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: sdk.OneRat(), - Liabilities: sdk.OneRat(), - } - poolB, candB := poolA.bondedToUnbondedPool(candA) - - // status unbonded - assert.Equal(t, candB.Status, Unbonded) - // same exchange rate, assets unchanged - assert.Equal(t, candB.Assets, candA.Assets) - // bonded pool decreased - assert.Equal(t, poolB.BondedPool, poolA.BondedPool-candA.Assets.Evaluate()) - // unbonded pool increased - assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+candA.Assets.Evaluate()) - // conservation of tokens - assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) -} - -func TestUnbonbedtoBondedPool(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - candA := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: sdk.OneRat(), - Liabilities: sdk.OneRat(), - } - candA.Status = Unbonded - poolB, candB := poolA.unbondedToBondedPool(candA) - - // status bonded - assert.Equal(t, candB.Status, Bonded) - // same exchange rate, assets unchanged - assert.Equal(t, candB.Assets, candA.Assets) - // bonded pool increased - assert.Equal(t, poolB.BondedPool, poolA.BondedPool+candA.Assets.Evaluate()) - // unbonded pool decreased - assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-candA.Assets.Evaluate()) - // conservation of tokens - assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) -} - func TestAddTokensBonded(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) @@ -117,11 +75,11 @@ func TestAddTokensBonded(t *testing.T) { assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat()) // correct changes to bonded shares and bonded pool - assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB)) - assert.Equal(t, poolB.BondedPool, poolA.BondedPool+10) + 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.BondedPool))) + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) } func TestRemoveSharesBonded(t *testing.T) { @@ -134,10 +92,10 @@ func TestRemoveSharesBonded(t *testing.T) { // correct changes to bonded shares and bonded pool assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.BondedPool, poolA.BondedPool-tokensB) + 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.BondedPool))) + assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens))) } func TestAddTokensUnbonded(t *testing.T) { @@ -149,11 +107,11 @@ func TestAddTokensUnbonded(t *testing.T) { assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat()) // correct changes to unbonded shares and unbonded pool - assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB)) - assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+10) + 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.UnbondedPool))) + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) } func TestRemoveSharesUnbonded(t *testing.T) { @@ -166,359 +124,8 @@ func TestRemoveSharesUnbonded(t *testing.T) { // correct changes to unbonded shares and bonded pool assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) - assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-tokensB) + 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.UnbondedPool))) -} - -func TestCandidateAddTokens(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - candA := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), - } - poolA.BondedPool = candA.Assets.Evaluate() - poolA.BondedShares = candA.Assets - assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, candB, sharesB := poolA.candidateAddTokens(candA, 10) - - // shares were issued - assert.Equal(t, sdk.NewRat(10).Mul(candA.delegatorShareExRate()), sharesB) - // pool shares were added - assert.Equal(t, candB.Assets, candA.Assets.Add(sdk.NewRat(10))) - // conservation of tokens - assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool) -} - -func TestCandidateRemoveShares(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) - - poolA := keeper.GetPool(ctx) - candA := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), - } - poolA.BondedPool = candA.Assets.Evaluate() - poolA.BondedShares = candA.Assets - assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat()) - assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat()) - poolB, candB, coinsB := poolA.candidateRemoveShares(candA, sdk.NewRat(10)) - - // coins were created - assert.Equal(t, coinsB, int64(10)) - // pool shares were removed - assert.Equal(t, candB.Assets, candA.Assets.Sub(sdk.NewRat(10).Mul(candA.delegatorShareExRate()))) - // conservation of tokens - assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool) - - // specific case from random tests - assets := sdk.NewRat(5102) - liabilities := sdk.NewRat(115) - cand := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: assets, - Liabilities: liabilities, - } - pool := Pool{ - TotalSupply: 0, - BondedShares: sdk.NewRat(248305), - UnbondedShares: sdk.NewRat(232147), - BondedPool: 248305, - UnbondedPool: 232147, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - shares := sdk.NewRat(29) - msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)", - cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - msg = fmt.Sprintf("Removed %v shares from %s", shares, msg) - newPool, _, tokens := pool.candidateRemoveShares(cand, shares) - require.Equal(t, - tokens+newPool.UnbondedPool+newPool.BondedPool, - pool.BondedPool+pool.UnbondedPool, - "Tokens were not conserved: %s", msg) -} - -///////////////////////////////////// -// TODO Make all random tests less obfuscated! - -// generate a random candidate -func randomCandidate(r *rand.Rand) Candidate { - var status CandidateStatus - if r.Float64() < float64(0.5) { - status = Bonded - } else { - status = Unbonded - } - assets := sdk.NewRat(int64(r.Int31n(10000))) - liabilities := sdk.NewRat(int64(r.Int31n(10000))) - return Candidate{ - Status: status, - Address: addrs[0], - PubKey: pks[0], - Assets: assets, - Liabilities: liabilities, - } -} - -// generate a random staking state -func randomSetup(r *rand.Rand, numCandidates int) (Pool, Candidates) { - pool := Pool{ - TotalSupply: 0, - BondedShares: sdk.ZeroRat(), - UnbondedShares: sdk.ZeroRat(), - BondedPool: 0, - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - - candidates := make([]Candidate, numCandidates) - for i := 0; i < numCandidates; i++ { - candidate := randomCandidate(r) - if candidate.Status == Bonded { - pool.BondedShares = pool.BondedShares.Add(candidate.Assets) - pool.BondedPool += candidate.Assets.Evaluate() - } else if candidate.Status == Unbonded { - pool.UnbondedShares = pool.UnbondedShares.Add(candidate.Assets) - pool.UnbondedPool += candidate.Assets.Evaluate() - } - candidates[i] = candidate - } - return pool, candidates -} - -// any operation that transforms staking state -// takes in RNG instance, pool, candidate -// returns updated pool, updated candidate, delta tokens, descriptive message -type Operation func(r *rand.Rand, p Pool, c Candidate) (Pool, Candidate, int64, string) - -// operation: bond or unbond a candidate depending on current status -func OpBondOrUnbond(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) { - var msg string - if cand.Status == Bonded { - msg = fmt.Sprintf("Unbonded previously bonded candidate %s (assets: %v, liabilities: %v, delegatorShareExRate: %v)", - cand.Address, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - p, cand = p.bondedToUnbondedPool(cand) - - } else if cand.Status == Unbonded { - msg = fmt.Sprintf("Bonded previously unbonded candidate %s (assets: %v, liabilities: %v, delegatorShareExRate: %v)", - cand.Address, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - p, cand = p.unbondedToBondedPool(cand) - } - return p, cand, 0, msg -} - -// operation: add a random number of tokens to a candidate -func OpAddTokens(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) { - tokens := int64(r.Int31n(1000)) - msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)", - cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - p, cand, _ = p.candidateAddTokens(cand, tokens) - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - return p, cand, -1 * tokens, msg // tokens are removed so for accounting must be negative -} - -// operation: remove a random number of shares from a candidate -func OpRemoveShares(r *rand.Rand, p Pool, cand Candidate) (Pool, Candidate, int64, string) { - var shares sdk.Rat - for { - shares = sdk.NewRat(int64(r.Int31n(1000))) - if shares.LT(cand.Liabilities) { - break - } - } - - msg := fmt.Sprintf("Removed %v shares from candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)", - shares, cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - - p, cand, tokens := p.candidateRemoveShares(cand, shares) - return p, cand, 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 Candidates, pMod Pool, cMods Candidates, tokens int64) { - - // total tokens conserved - require.Equal(t, - pOrig.UnbondedPool+pOrig.BondedPool, - pMod.UnbondedPool+pMod.BondedPool+tokens, - "Tokens not conserved - msg: %v\n, pOrig.BondedShares: %v, pOrig.UnbondedShares: %v, pMod.BondedShares: %v, pMod.UnbondedShares: %v, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n", - msg, - pOrig.BondedShares, pOrig.UnbondedShares, - pMod.BondedShares, pMod.UnbondedShares, - pOrig.UnbondedPool, pOrig.BondedPool, - pMod.UnbondedPool, pMod.BondedPool, 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 _, cMod := range cMods { - - // nonnegative ex rate - require.False(t, cMod.delegatorShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative candidate.delegatorShareExRate(): %v (candidate.Address: %s)", - msg, - cMod.delegatorShareExRate(), - cMod.Address, - ) - - // nonnegative assets - require.False(t, cMod.Assets.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative candidate.Assets: %v (candidate.Liabilities: %v, candidate.delegatorShareExRate: %v, candidate.Address: %s)", - msg, - cMod.Assets, - cMod.Liabilities, - cMod.delegatorShareExRate(), - cMod.Address, - ) - - // nonnegative liabilities - require.False(t, cMod.Liabilities.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative candidate.Liabilities: %v (candidate.Assets: %v, candidate.delegatorShareExRate: %v, candidate.Address: %s)", - msg, - cMod.Liabilities, - cMod.Assets, - cMod.delegatorShareExRate(), - cMod.Address, - ) - - } - -} - -func TestPossibleOverflow(t *testing.T) { - assets := sdk.NewRat(2159) - liabilities := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664)) - cand := Candidate{ - Status: Bonded, - Address: addrs[0], - PubKey: pks[0], - Assets: assets, - Liabilities: liabilities, - } - pool := Pool{ - TotalSupply: 0, - BondedShares: assets, - UnbondedShares: sdk.ZeroRat(), - BondedPool: assets.Evaluate(), - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } - tokens := int64(71) - msg := fmt.Sprintf("candidate %s (status: %d, assets: %v, liabilities: %v, delegatorShareExRate: %v)", - cand.Address, cand.Status, cand.Assets, cand.Liabilities, cand.delegatorShareExRate()) - _, newCandidate, _ := pool.candidateAddTokens(cand, tokens) - - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - require.False(t, newCandidate.delegatorShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative delegatorShareExRate(): %v", - msg, newCandidate.delegatorShareExRate()) -} - -// run random operations in a random order on a random single-candidate state, assert invariants hold -func TestSingleCandidateIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(41)) - - for i := 0; i < 10; i++ { - poolOrig, candidatesOrig := randomSetup(r, 1) - require.Equal(t, 1, len(candidatesOrig)) - - // sanity check - assertInvariants(t, "no operation", - poolOrig, candidatesOrig, - poolOrig, candidatesOrig, 0) - - for j := 0; j < 5; j++ { - poolMod, candidateMod, tokens, msg := randomOperation(r)(r, poolOrig, candidatesOrig[0]) - - candidatesMod := make([]Candidate, len(candidatesOrig)) - copy(candidatesMod[:], candidatesOrig[:]) - require.Equal(t, 1, len(candidatesOrig), "j %v", j) - require.Equal(t, 1, len(candidatesMod), "j %v", j) - candidatesMod[0] = candidateMod - - assertInvariants(t, msg, - poolOrig, candidatesOrig, - poolMod, candidatesMod, tokens) - - poolOrig = poolMod - candidatesOrig = candidatesMod - } - } -} - -// run random operations in a random order on a random multi-candidate state, assert invariants hold -func TestMultiCandidateIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(42)) - - for i := 0; i < 10; i++ { - poolOrig, candidatesOrig := randomSetup(r, 100) - - assertInvariants(t, "no operation", - poolOrig, candidatesOrig, - poolOrig, candidatesOrig, 0) - - for j := 0; j < 5; j++ { - index := int(r.Int31n(int32(len(candidatesOrig)))) - poolMod, candidateMod, tokens, msg := randomOperation(r)(r, poolOrig, candidatesOrig[index]) - candidatesMod := make([]Candidate, len(candidatesOrig)) - copy(candidatesMod[:], candidatesOrig[:]) - candidatesMod[index] = candidateMod - - assertInvariants(t, msg, - poolOrig, candidatesOrig, - poolMod, candidatesMod, tokens) - - poolOrig = poolMod - candidatesOrig = candidatesMod - - } - } + assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens))) } diff --git a/x/stake/shares.go b/x/stake/shares.go new file mode 100644 index 0000000000..d5fe93844d --- /dev/null +++ b/x/stake/shares.go @@ -0,0 +1,133 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// kind of shares +type PoolShareKind byte + +// pool shares held by a validator +type PoolShares struct { + Status sdk.BondStatus `json:"status"` + Amount sdk.Rat `json:"amount"` // total shares of type ShareKind +} + +// only the vitals - does not check bond height of IntraTxCounter +func (s PoolShares) Equal(s2 PoolShares) bool { + return s.Status == s2.Status && + s.Amount.Equal(s2.Amount) +} + +func NewUnbondedShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Unbonded, + Amount: amount, + } +} + +func NewUnbondingShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Unbonding, + Amount: amount, + } +} + +func NewBondedShares(amount sdk.Rat) PoolShares { + return PoolShares{ + Status: sdk.Bonded, + Amount: amount, + } +} + +//_________________________________________________________________________________________________________ + +// amount of unbonded shares +func (s PoolShares) Unbonded() sdk.Rat { + if s.Status == sdk.Unbonded { + return s.Amount + } + return sdk.ZeroRat() +} + +// amount of unbonding shares +func (s PoolShares) Unbonding() sdk.Rat { + if s.Status == sdk.Unbonding { + return s.Amount + } + return sdk.ZeroRat() +} + +// amount of bonded shares +func (s PoolShares) Bonded() sdk.Rat { + if s.Status == sdk.Bonded { + return s.Amount + } + return sdk.ZeroRat() +} + +//_________________________________________________________________________________________________________ + +// equivalent amount of shares if the shares were unbonded +func (s PoolShares) ToUnbonded(p Pool) PoolShares { + var amount sdk.Rat + switch s.Status { + case sdk.Bonded: + exRate := p.bondedShareExRate().Quo(p.unbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr + amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr + case sdk.Unbonding: + 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 + } + return NewUnbondedShares(amount) +} + +// equivalent amount of shares if the shares were unbonding +func (s PoolShares) ToUnbonding(p Pool) PoolShares { + var amount sdk.Rat + switch s.Status { + case sdk.Bonded: + exRate := p.bondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr + amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr + case sdk.Unbonding: + amount = s.Amount + case sdk.Unbonded: + exRate := p.unbondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr + amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr + } + return NewUnbondingShares(amount) +} + +// equivalent amount of shares if the shares were bonded +func (s PoolShares) ToBonded(p Pool) PoolShares { + var amount sdk.Rat + switch s.Status { + case sdk.Bonded: + amount = s.Amount + case sdk.Unbonding: + exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + case sdk.Unbonded: + exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr + amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr + } + return NewUnbondedShares(amount) +} + +//_________________________________________________________________________________________________________ + +// get the equivalent amount of tokens contained by the shares +func (s PoolShares) Tokens(p Pool) sdk.Rat { + switch s.Status { + case sdk.Bonded: + return p.unbondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares + case sdk.Unbonding: + return p.unbondedShareExRate().Mul(s.Amount) + case sdk.Unbonded: + return p.unbondedShareExRate().Mul(s.Amount) + default: + panic("unknown share kind") + } +} diff --git a/x/stake/store.md b/x/stake/store.md new file mode 100644 index 0000000000..1c95ffe876 --- /dev/null +++ b/x/stake/store.md @@ -0,0 +1,45 @@ +# Stores + +This document provided a bit more insight as to the purpose of several related +prefixed areas of the staking store which are accessed in `x/stake/keeper.go`. + + +## Validators + - Prefix Key Space: ValidatorsKey + - Key/Sort: Validator Owner Address + - Value: Validator Object + - Contains: All Validator records independent of being bonded or not + - Used For: Retrieve validator from owner address, general validator retrieval + +## Validators By Power + - Prefix Key Space: ValidatorsByPowerKey + - Key/Sort: Validator Power (equivalent bonded shares) then Block + Height then Transaction Order + - Value: Validator Owner Address + - Contains: All Validator records independent of being bonded or not + - Used For: Determining who the top validators are whom should be bonded + +## Validators Cliff Power + - Prefix Key Space: ValidatorCliffKey + - Key/Sort: single-record + - Value: Validator Power Key (as above store) + - Contains: The cliff validator (ex. 100th validator) power + - Used For: Efficient updates to validator status + +## Validators Bonded + - Prefix Key Space: ValidatorsBondedKey + - Key/Sort: Validator PubKey Address (NOTE same as Tendermint) + - Value: Validator Owner Address + - Contains: Only currently bonded Validators + - Used For: Retrieving the list of all currently bonded validators when updating + for a new validator entering the validator set we may want to loop + through this set to determine who we've kicked out. + retrieving validator by tendermint index + +## Tendermint Updates + - Prefix Key Space: TendermintUpdatesKey + - Key/Sort: Validator Owner Address + - Value: Tendermint ABCI Validator + - Contains: Validators are queued to affect the consensus validation set in Tendermint + - Used For: Informing Tendermint of the validator set updates, is used only intra-block, as the + updates are applied then cleared on endblock diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 27acebe086..2dac36069e 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -1,7 +1,6 @@ package stake import ( - "bytes" "encoding/hex" "testing" @@ -52,70 +51,14 @@ var ( emptyPubkey crypto.PubKey ) -func validatorsEqual(b1, b2 Validator) bool { - return bytes.Equal(b1.Address, b2.Address) && - b1.PubKey.Equals(b2.PubKey) && - b1.Power.Equal(b2.Power) && - b1.Height == b2.Height && - b1.Counter == b2.Counter +//_______________________________________________________________________________________ + +// 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 candidatesEqual(c1, c2 Candidate) bool { - return c1.Status == c2.Status && - c1.PubKey.Equals(c2.PubKey) && - bytes.Equal(c1.Address, c2.Address) && - c1.Assets.Equal(c2.Assets) && - c1.Liabilities.Equal(c2.Liabilities) && - c1.Description == c2.Description -} - -func bondsEqual(b1, b2 DelegatorBond) bool { - return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) && - bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) && - b1.Height == b2.Height && - b1.Shares.Equal(b2.Shares) -} - -// default params for testing -func defaultParams() Params { - return Params{ - InflationRateChange: sdk.NewRat(13, 100), - InflationMax: sdk.NewRat(20, 100), - InflationMin: sdk.NewRat(7, 100), - GoalBonded: sdk.NewRat(67, 100), - MaxValidators: 100, - BondDenom: "steak", - } -} - -// initial pool for testing -func initialPool() Pool { - return Pool{ - TotalSupply: 0, - BondedShares: sdk.ZeroRat(), - UnbondedShares: sdk.ZeroRat(), - BondedPool: 0, - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: sdk.NewRat(7, 100), - } -} - -// get raw genesis raw message for testing -func GetDefaultGenesisState() GenesisState { - return GenesisState{ - Pool: initialPool(), - Params: defaultParams(), - } -} - -// XXX reference the common declaration of this function -func subspace(prefix []byte) (start, end []byte) { - end = make([]byte, len(prefix)) - copy(end, prefix) - end[len(end)-1]++ - return prefix, end -} +//_______________________________________________________________________________________ func makeTestCodec() *wire.Codec { var cdc = wire.NewCodec() @@ -149,12 +92,13 @@ func paramsNoInflation() Params { // hogpodge of all sorts of input required for testing func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Keeper) { - db := dbm.NewMemDB() keyStake := sdk.NewKVStoreKey("stake") - keyMain := keyStake //sdk.NewKVStoreKey("main") //TODO fix multistore + keyAcc := sdk.NewKVStoreKey("acc") + db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) @@ -162,13 +106,13 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context cdc := makeTestCodec() accountMapper := auth.NewAccountMapper( cdc, // amino codec - keyMain, // target store + keyAcc, // target store &auth.BaseAccount{}, // prototype ) ck := bank.NewKeeper(accountMapper) keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace) keeper.setPool(ctx, initialPool()) - keeper.setParams(ctx, defaultParams()) + keeper.setNewParams(ctx, defaultParams()) // fill all the addresses with some coins for _, addr := range addrs { diff --git a/x/stake/tick.go b/x/stake/tick.go index 9ca484061c..a8d9457347 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -26,12 +26,14 @@ func (k Keeper) Tick(ctx sdk.Context) (change []abci.Validator) { // save the params k.setPool(ctx, p) - // reset the counter - k.setCounter(ctx, 0) + // reset the intra-transaction counter + k.setIntraTxCounter(ctx, 0) - change = k.getAccUpdateValidators(ctx) + // calculate validator set changes + change = k.getTendermintUpdates(ctx) + k.clearTendermintUpdates(ctx) - return + return change } // process provisions for an hour period @@ -44,9 +46,8 @@ func (k Keeper) processProvisions(ctx sdk.Context) Pool { // 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.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate() - pool.BondedPool += provisions - pool.TotalSupply += provisions + provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate() + pool.BondedTokens += provisions return pool } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index f75cf65b5c..4f0f6dc061 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -15,39 +15,39 @@ func TestGetInflation(t *testing.T) { hrsPerYrRat := sdk.NewRat(hrsPerYr) // Governing Mechanism: - // bondedRatio = BondedPool / TotalSupply + // bondedRatio = BondedTokens / TotalSupply // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange tests := []struct { - name string - setBondedPool, setTotalSupply int64 - setInflation, expectedChange sdk.Rat + name string + setBondedTokens, setLooseTokens int64 + setInflation, expectedChange sdk.Rat }{ // with 0% bonded atom supply the inflation should increase by InflationRateChange {"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", 1, 1, 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", 1, 2, 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", 1, 1, sdk.NewRat(7, 100), sdk.ZeroRat()}, - {"test 5", 1, 1, 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", 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", 67, 100, sdk.NewRat(15, 100), sdk.ZeroRat()}, + {"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()}, } for _, tc := range tests { - pool.BondedPool, pool.TotalSupply = tc.setBondedPool, tc.setTotalSupply + pool.BondedTokens, pool.LooseUnbondedTokens = tc.setBondedTokens, tc.setLooseTokens pool.Inflation = tc.setInflation keeper.setPool(ctx, pool) @@ -62,72 +62,79 @@ func TestGetInflation(t *testing.T) { func TestProcessProvisions(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) params := defaultParams() + params.MaxValidators = 2 keeper.setParams(ctx, params) pool := keeper.GetPool(ctx) - // create some candidates some bonded, some unbonded - candidates := make([]Candidate, 10) - for i := 0; i < 10; i++ { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.NewRat(0), - Liabilities: sdk.NewRat(0), - } - if i < 5 { - c.Status = Bonded - } - mintedTokens := int64((i + 1) * 10000000) - pool.TotalSupply += mintedTokens - pool, c, _ = pool.candidateAddTokens(c, mintedTokens) - - keeper.setCandidate(ctx, c) - candidates[i] = c - } - keeper.setPool(ctx, pool) - var totalSupply int64 = 550000000 + var tokenSupply int64 = 550000000 var bondedShares int64 = 150000000 var unbondedShares int64 = 400000000 - assert.Equal(t, totalSupply, pool.TotalSupply) - assert.Equal(t, bondedShares, pool.BondedPool) - assert.Equal(t, unbondedShares, pool.UnbondedPool) + + // create some validators some bonded, some unbonded + var validators [5]Validator + validators[0] = NewValidator(addrs[0], pks[0], Description{}) + validators[0], pool, _ = validators[0].addTokensFromDel(pool, 150000000) + keeper.setPool(ctx, pool) + validators[0] = keeper.updateValidator(ctx, validators[0]) + pool = keeper.GetPool(ctx) + require.Equal(t, bondedShares, pool.BondedTokens, "%v", pool) + + validators[1] = NewValidator(addrs[1], pks[1], Description{}) + validators[1], pool, _ = validators[1].addTokensFromDel(pool, 100000000) + keeper.setPool(ctx, pool) + validators[1] = keeper.updateValidator(ctx, validators[1]) + validators[2] = NewValidator(addrs[2], pks[2], Description{}) + validators[2], pool, _ = validators[2].addTokensFromDel(pool, 100000000) + keeper.setPool(ctx, pool) + validators[2] = keeper.updateValidator(ctx, validators[2]) + validators[3] = NewValidator(addrs[3], pks[3], Description{}) + validators[3], pool, _ = validators[3].addTokensFromDel(pool, 100000000) + keeper.setPool(ctx, pool) + validators[3] = keeper.updateValidator(ctx, validators[3]) + validators[4] = NewValidator(addrs[4], pks[4], Description{}) + validators[4], pool, _ = validators[4].addTokensFromDel(pool, 100000000) + keeper.setPool(ctx, pool) + validators[4] = keeper.updateValidator(ctx, validators[4]) + + assert.Equal(t, tokenSupply, pool.TokenSupply()) + assert.Equal(t, bondedShares, pool.BondedTokens) + assert.Equal(t, unbondedShares, pool.UnbondedTokens) // initial bonded ratio ~ 27% - assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", pool.bondedRatio()) + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, tokenSupply)), "%v", pool.bondedRatio()) - // test the value of candidate shares + // test the value of validator shares assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate()) - initialSupply := pool.TotalSupply - initialUnbonded := pool.TotalSupply - pool.BondedPool + initialSupply := pool.TokenSupply() + initialUnbonded := pool.TokenSupply() - pool.BondedTokens // process the provisions a year for hr := 0; hr < 8766; hr++ { pool := keeper.GetPool(ctx) expInflation := keeper.nextInflation(ctx).Round(1000000000) - expProvisions := (expInflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat)).Evaluate() - startBondedPool := pool.BondedPool - startTotalSupply := pool.TotalSupply + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate() + startBondedTokens := pool.BondedTokens + startTotalSupply := pool.TokenSupply() pool = keeper.processProvisions(ctx) keeper.setPool(ctx, pool) - //fmt.Printf("hr %v, startBondedPool %v, expProvisions %v, pool.BondedPool %v\n", hr, startBondedPool, expProvisions, pool.BondedPool) - require.Equal(t, startBondedPool+expProvisions, pool.BondedPool, "hr %v", hr) - require.Equal(t, startTotalSupply+expProvisions, pool.TotalSupply) + //fmt.Printf("hr %v, startBondedTokens %v, expProvisions %v, pool.BondedTokens %v\n", hr, startBondedTokens, expProvisions, pool.BondedTokens) + require.Equal(t, startBondedTokens+expProvisions, pool.BondedTokens, "hr %v", hr) + require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply()) } pool = keeper.GetPool(ctx) - assert.NotEqual(t, initialSupply, pool.TotalSupply) - assert.Equal(t, initialUnbonded, pool.UnbondedPool) - //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, pool.TotalSupply-pool.BondedPool)) + assert.NotEqual(t, initialSupply, pool.TokenSupply()) + assert.Equal(t, initialUnbonded, pool.UnbondedTokens) + //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedTokens, pool.TokenSupply()-pool.BondedTokens)) // initial bonded ratio ~ from 27% to 40% increase for bonded holders ownership of total supply assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(211813022, 611813022)), "%v", pool.bondedRatio()) // global supply - assert.Equal(t, int64(611813022), pool.TotalSupply) - assert.Equal(t, int64(211813022), pool.BondedPool) - assert.Equal(t, unbondedShares, pool.UnbondedPool) + assert.Equal(t, int64(611813022), pool.TokenSupply()) + assert.Equal(t, int64(211813022), pool.BondedTokens) + assert.Equal(t, unbondedShares, pool.UnbondedTokens) - // test the value of candidate shares + // test the value of validator shares assert.True(t, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", pool.bondedShareExRate()) } diff --git a/x/stake/types.go b/x/stake/types.go deleted file mode 100644 index 741783fa1d..0000000000 --- a/x/stake/types.go +++ /dev/null @@ -1,189 +0,0 @@ -package stake - -import ( - 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" -) - -// GenesisState - all staking state that must be provided at genesis -type GenesisState struct { - Pool Pool `json:"pool"` - Params Params `json:"params"` - Candidates []Candidate `json:"candidates"` - Bonds []DelegatorBond `json:"bonds"` -} - -//_________________________________________________________________________ - -// Params defines the high level settings for staking -type Params struct { - InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms - - MaxValidators uint16 `json:"max_validators"` // maximum number of validators - BondDenom string `json:"bond_denom"` // bondable coin denomination -} - -func (p Params) equal(p2 Params) bool { - return p.InflationRateChange.Equal(p2.InflationRateChange) && - p.InflationMax.Equal(p2.InflationMax) && - p.InflationMin.Equal(p2.InflationMin) && - p.GoalBonded.Equal(p2.GoalBonded) && - p.MaxValidators == p2.MaxValidators && - p.BondDenom == p2.BondDenom -} - -//_________________________________________________________________________ - -// Pool - dynamic parameters of the current state -type Pool struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - 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 -} - -func (p Pool) equal(p2 Pool) bool { - return p.BondedShares.Equal(p2.BondedShares) && - p.UnbondedShares.Equal(p2.UnbondedShares) && - p.Inflation.Equal(p2.Inflation) && - p.TotalSupply == p2.TotalSupply && - p.BondedPool == p2.BondedPool && - p.UnbondedPool == p2.UnbondedPool && - p.InflationLastTime == p2.InflationLastTime -} - -//_________________________________________________________________________ - -// CandidateStatus - status of a validator-candidate -type CandidateStatus byte - -const ( - // nolint - Bonded CandidateStatus = 0x00 - Unbonded CandidateStatus = 0x01 - Revoked CandidateStatus = 0x02 -) - -// Candidate defines the total amount of bond shares and their exchange rate to -// coins. Accumulation of interest is modelled as an in increase in the -// exchange rate, and slashing as a decrease. When coins are delegated to this -// candidate, the candidate is credited with a DelegatorBond whose number of -// bond shares is based on the amount of coins delegated divided by the current -// exchange rate. Voting power can be calculated as total bonds multiplied by -// exchange rate. -type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Assets sdk.Rat `json:"assets"` // total shares of a global hold pools - Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators - Description Description `json:"description"` // Description terms for the candidate - ValidatorBondHeight int64 `json:"validator_bond_height"` // Earliest height as a bonded validator - ValidatorBondCounter int16 `json:"validator_bond_counter"` // Block-local tx index of validator change -} - -// Candidates - list of Candidates -type Candidates []Candidate - -// NewCandidate - initialize a new candidate -func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) Candidate { - return Candidate{ - Status: Unbonded, - Address: address, - PubKey: pubKey, - Assets: sdk.ZeroRat(), - Liabilities: sdk.ZeroRat(), - Description: description, - ValidatorBondHeight: int64(0), - ValidatorBondCounter: int16(0), - } -} - -// Description - description fields for a candidate -type Description struct { - Moniker string `json:"moniker"` - Identity string `json:"identity"` - Website string `json:"website"` - Details string `json:"details"` -} - -func NewDescription(moniker, identity, website, details string) Description { - return Description{ - Moniker: moniker, - Identity: identity, - Website: website, - Details: details, - } -} - -// get the exchange rate of global pool shares over delegator shares -func (c Candidate) delegatorShareExRate() sdk.Rat { - if c.Liabilities.IsZero() { - return sdk.OneRat() - } - return c.Assets.Quo(c.Liabilities) -} - -// Validator returns a copy of the Candidate as a Validator. -// Should only be called when the Candidate qualifies as a validator. -func (c Candidate) validator() Validator { - return Validator{ - Address: c.Address, - PubKey: c.PubKey, - Power: c.Assets, - Height: c.ValidatorBondHeight, - Counter: c.ValidatorBondCounter, - } -} - -//XXX updateDescription function -//XXX enforce limit to number of description characters - -//______________________________________________________________________ - -// Validator is one of the top Candidates -type Validator struct { - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pub_key"` - Power sdk.Rat `json:"voting_power"` - Height int64 `json:"height"` // Earliest height as a validator - Counter int16 `json:"counter"` // Block-local tx index for resolving equal voting power & height -} - -// abci validator from stake validator type -func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { - return abci.Validator{ - PubKey: v.PubKey.Bytes(), - Power: v.Power.Evaluate(), - } -} - -// abci validator from stake validator type -// with zero power used for validator updates -func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { - return abci.Validator{ - PubKey: v.PubKey.Bytes(), - Power: 0, - } -} - -//_________________________________________________________________________ - -// DelegatorBond 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. -// TODO better way of managing space -type DelegatorBond struct { - DelegatorAddr sdk.Address `json:"delegator_addr"` - CandidateAddr sdk.Address `json:"candidate_addr"` - Shares sdk.Rat `json:"shares"` - Height int64 `json:"height"` // Last height bond updated -} diff --git a/x/stake/types_test.go b/x/stake/types_test.go deleted file mode 100644 index ec16f32d96..0000000000 --- a/x/stake/types_test.go +++ /dev/null @@ -1,3 +0,0 @@ -package stake - -// XXX test global state functions, candidate exchange rate functions etc. diff --git a/x/stake/validator.go b/x/stake/validator.go new file mode 100644 index 0000000000..9aa8d3768b --- /dev/null +++ b/x/stake/validator.go @@ -0,0 +1,238 @@ +package stake + +import ( + "bytes" + + 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" +) + +// Validator defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// validator, the validator is credited with a Delegation whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Validator struct { + Owner sdk.Address `json:"owner"` // sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // pubkey of validator + Revoked bool `json:"pub_key"` // has the validator been revoked from bonded status? + + PoolShares PoolShares `json:"pool_shares"` // total shares for tokens held in the pool + DelegatorShares sdk.Rat `json:"delegator_shares"` // total shares issued to a validator's delegators + + Description Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change + ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer + + Commission sdk.Rat `json:"commission"` // XXX the commission rate of fees charged to any delegators + CommissionMax sdk.Rat `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge + CommissionChangeRate sdk.Rat `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission + CommissionChangeToday sdk.Rat `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time) + + // fee related + 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{ + Owner: owner, + PubKey: pubKey, + PoolShares: NewUnbondedShares(sdk.ZeroRat()), + DelegatorShares: sdk.ZeroRat(), + Description: description, + BondHeight: int64(0), + BondIntraTxCounter: int16(0), + ProposerRewardPool: sdk.Coins{}, + Commission: sdk.ZeroRat(), + CommissionMax: sdk.ZeroRat(), + CommissionChangeRate: sdk.ZeroRat(), + CommissionChangeToday: sdk.ZeroRat(), + PrevBondedShares: sdk.ZeroRat(), + } +} + +// only the vitals - does not check bond height of IntraTxCounter +func (v Validator) equal(c2 Validator) bool { + return v.PubKey.Equals(c2.PubKey) && + bytes.Equal(v.Owner, c2.Owner) && + v.PoolShares.Equal(c2.PoolShares) && + v.DelegatorShares.Equal(c2.DelegatorShares) && + v.Description == c2.Description && + //v.BondHeight == c2.BondHeight && + //v.BondIntraTxCounter == c2.BondIntraTxCounter && // counter is always changing + v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) && + v.Commission.Equal(c2.Commission) && + v.CommissionMax.Equal(c2.CommissionMax) && + v.CommissionChangeRate.Equal(c2.CommissionChangeRate) && + v.CommissionChangeToday.Equal(c2.CommissionChangeToday) && + v.PrevBondedShares.Equal(c2.PrevBondedShares) +} + +// Description - description fields for a validator +type Description struct { + Moniker string `json:"moniker"` + Identity string `json:"identity"` + Website string `json:"website"` + Details string `json:"details"` +} + +func NewDescription(moniker, identity, website, details string) Description { + return Description{ + Moniker: moniker, + Identity: identity, + Website: website, + Details: details, + } +} + +//XXX updateDescription function which enforce limit to number of description characters + +// abci validator from stake validator type +func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator { + return abci.Validator{ + PubKey: v.PubKey.Bytes(), + Power: v.PoolShares.Bonded().Evaluate(), + } +} + +// abci validator from stake validator type +// with zero power used for validator updates +func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator { + return abci.Validator{ + PubKey: v.PubKey.Bytes(), + Power: 0, + } +} + +// abci validator from stake validator type +func (v Validator) Status() sdk.BondStatus { + return v.PoolShares.Status +} + +// update the location of the shares within a validator if its bond status has changed +func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) { + var tokens int64 + + switch v.Status() { + case sdk.Unbonded: + if NewStatus == sdk.Unbonded { + return v, pool + } + pool, tokens = pool.removeSharesUnbonded(v.PoolShares.Amount) + + case sdk.Unbonding: + if NewStatus == sdk.Unbonding { + return v, pool + } + pool, tokens = pool.removeSharesUnbonding(v.PoolShares.Amount) + + case sdk.Bonded: + if NewStatus == sdk.Bonded { // return if nothing needs switching + return v, pool + } + pool, tokens = pool.removeSharesBonded(v.PoolShares.Amount) + } + + switch NewStatus { + case sdk.Unbonded: + pool, v.PoolShares = pool.addTokensUnbonded(tokens) + case sdk.Unbonding: + pool, v.PoolShares = pool.addTokensUnbonding(tokens) + case sdk.Bonded: + pool, v.PoolShares = pool.addTokensBonded(tokens) + } + return v, pool +} + +// XXX TEST +// get the power or potential power for a validator +// if bonded, the power is the BondedShares +// if not bonded, the power is the amount of bonded shares which the +// the validator would have it was bonded +func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) { + return v.PoolShares.ToBonded(pool).Amount +} + +//_________________________________________________________________________________________________________ + +// XXX Audit this function further to make sure it's correct +// add tokens to a validator +func (v Validator) addTokensFromDel(pool Pool, + amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) { + + exRate := v.DelegatorShareExRate(pool) // bshr/delshr + + var poolShares PoolShares + var equivalentBondedShares sdk.Rat + switch v.Status() { + case sdk.Unbonded: + pool, poolShares = pool.addTokensUnbonded(amount) + case sdk.Unbonding: + pool, poolShares = pool.addTokensUnbonding(amount) + case sdk.Bonded: + pool, poolShares = pool.addTokensBonded(amount) + } + v.PoolShares.Amount = v.PoolShares.Amount.Add(poolShares.Amount) + equivalentBondedShares = poolShares.ToBonded(pool).Amount + + issuedDelegatorShares = equivalentBondedShares.Quo(exRate) // bshr/(bshr/delshr) = delshr + v.DelegatorShares = v.DelegatorShares.Add(issuedDelegatorShares) + + return v, pool, issuedDelegatorShares +} + +// remove delegator shares from a validator +// NOTE this function assumes the shares have already been updated for the validator status +func (v Validator) removeDelShares(pool Pool, + delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) { + + amount := v.DelegatorShareExRate(pool).Mul(delShares) + eqBondedSharesToRemove := NewBondedShares(amount) + v.DelegatorShares = v.DelegatorShares.Sub(delShares) + + switch v.Status() { + case sdk.Unbonded: + unbondedShares := eqBondedSharesToRemove.ToUnbonded(pool).Amount + pool, createdCoins = pool.removeSharesUnbonded(unbondedShares) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondedShares) + case sdk.Unbonding: + unbondingShares := eqBondedSharesToRemove.ToUnbonding(pool).Amount + pool, createdCoins = pool.removeSharesUnbonding(unbondingShares) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(unbondingShares) + case sdk.Bonded: + pool, createdCoins = pool.removeSharesBonded(eqBondedSharesToRemove.Amount) + v.PoolShares.Amount = v.PoolShares.Amount.Sub(eqBondedSharesToRemove.Amount) + } + return v, pool, createdCoins +} + +// get the exchange rate of tokens over delegator shares +// UNITS: eq-val-bonded-shares/delegator-shares +func (v Validator) DelegatorShareExRate(pool Pool) sdk.Rat { + if v.DelegatorShares.IsZero() { + return sdk.OneRat() + } + eqBondedShares := v.PoolShares.ToBonded(pool).Amount + return eqBondedShares.Quo(v.DelegatorShares) +} + +//______________________________________________________________________ + +// ensure fulfills the sdk validator types +var _ sdk.Validator = Validator{} + +// nolint - for sdk.Validator +func (v Validator) GetStatus() sdk.BondStatus { return v.Status() } +func (v Validator) GetOwner() sdk.Address { return v.Owner } +func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } +func (v Validator) GetPower() sdk.Rat { return v.PoolShares.Bonded() } +func (v Validator) GetBondHeight() int64 { return v.BondHeight } diff --git a/x/stake/validator_test.go b/x/stake/validator_test.go new file mode 100644 index 0000000000..1ca5ba2f75 --- /dev/null +++ b/x/stake/validator_test.go @@ -0,0 +1,408 @@ +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, 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, 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, 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, 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, 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, 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, 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().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: addrs[0], + PubKey: pks[0], + 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) { + ctx, _, keeper := createTestInput(t, false, 0) + pool := keeper.GetPool(ctx) + + val := NewValidator(addrs[0], pks[0], 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) +} + +//________________________________________________________________________________ +// TODO refactor this random setup + +// generate a random validator +func randomValidator(r *rand.Rand) 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[0], + PubKey: pks[0], + 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) + 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 +} + +// 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 Validators, pMod Pool, vMods Validators, 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, + ) + + } + +} + +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.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/view_slash_keeper.go b/x/stake/view_slash_keeper.go index 375c7323b1..38ba227bde 100644 --- a/x/stake/view_slash_keeper.go +++ b/x/stake/view_slash_keeper.go @@ -17,13 +17,13 @@ func NewViewSlashKeeper(k Keeper) ViewSlashKeeper { } // load a delegator bond -func (v ViewSlashKeeper) GetDelegatorBond(ctx sdk.Context, - delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { - return v.keeper.GetDelegatorBond(ctx, delegatorAddr, candidateAddr) +func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context, + delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) { + return v.keeper.GetDelegation(ctx, delegatorAddr, validatorAddr) } // load n delegator bonds -func (v ViewSlashKeeper) GetDelegatorBonds(ctx sdk.Context, - delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { - return v.keeper.GetDelegatorBonds(ctx, delegator, maxRetrieve) +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 index 7380859105..7f481fc601 100644 --- a/x/stake/view_slash_keeper_test.go +++ b/x/stake/view_slash_keeper_test.go @@ -9,78 +9,78 @@ import ( "github.com/stretchr/testify/require" ) -// tests GetDelegatorBond, GetDelegatorBonds +// tests GetDelegation, GetDelegations func TestViewSlashBond(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 0) - //construct the candidates + //construct the validators amts := []int64{9, 8, 7} - var candidates [3]Candidate + var validators [3]Validator for i, amt := range amts { - candidates[i] = Candidate{ - Address: addrVals[i], - PubKey: pks[i], - Assets: sdk.NewRat(amt), - Liabilities: sdk.NewRat(amt), + validators[i] = Validator{ + Owner: addrVals[i], + PubKey: pks[i], + PoolShares: NewUnbondedShares(sdk.NewRat(amt)), + DelegatorShares: sdk.NewRat(amt), } } - // first add a candidates[0] to delegate too - keeper.setCandidate(ctx, candidates[0]) + // first add a validators[0] to delegate too + keeper.updateValidator(ctx, validators[0]) - bond1to1 := DelegatorBond{ + bond1to1 := Delegation{ DelegatorAddr: addrDels[0], - CandidateAddr: addrVals[0], + ValidatorAddr: addrVals[0], Shares: sdk.NewRat(9), } viewSlashKeeper := NewViewSlashKeeper(keeper) // check the empty keeper first - _, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + _, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.False(t, found) // set and retrieve a record - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found := viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) - assert.True(t, bondsEqual(bond1to1, resBond)) + assert.True(t, bond1to1.equal(resBond)) // modify a records, save, and retrieve bond1to1.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = viewSlashKeeper.GetDelegatorBond(ctx, addrDels[0], addrVals[0]) + keeper.setDelegation(ctx, bond1to1) + resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0]) assert.True(t, found) - assert.True(t, bondsEqual(bond1to1, resBond)) + assert.True(t, bond1to1.equal(resBond)) // add some more records - keeper.setCandidate(ctx, candidates[1]) - keeper.setCandidate(ctx, candidates[2]) - bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9), 0} - bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9), 1} - bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9), 2} - bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9), 3} - bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9), 4} - keeper.setDelegatorBond(ctx, bond1to2) - keeper.setDelegatorBond(ctx, bond1to3) - keeper.setDelegatorBond(ctx, bond2to1) - keeper.setDelegatorBond(ctx, bond2to2) - keeper.setDelegatorBond(ctx, bond2to3) + 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.GetDelegatorBonds(ctx, addrDels[0], 5) + resBonds := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) - assert.True(t, bondsEqual(bond1to1, resBonds[0])) - assert.True(t, bondsEqual(bond1to2, resBonds[1])) - assert.True(t, bondsEqual(bond1to3, resBonds[2])) - resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[0], 3) + 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.GetDelegatorBonds(ctx, addrDels[0], 2) + resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = viewSlashKeeper.GetDelegatorBonds(ctx, addrDels[1], 5) + resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) - assert.True(t, bondsEqual(bond2to1, resBonds[0])) - assert.True(t, bondsEqual(bond2to2, resBonds[1])) - assert.True(t, bondsEqual(bond2to3, resBonds[2])) + 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 index e99c621fae..6e6e382606 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -11,3 +11,5 @@ func RegisterWire(cdc *wire.Codec) { cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil) cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil) } + +var cdcEmpty = wire.NewCodec()