Merge pull request #982 from cosmos/rigel/stake-refactor

Stake refactor // fee seperation
This commit is contained in:
Rigel 2018-05-23 17:43:35 -04:00 committed by GitHub
commit 34f64752e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 3221 additions and 2181 deletions

View File

@ -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:

View File

@ -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())

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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(

View File

@ -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
}

View File

@ -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))
}
}

65
types/stake.go Normal file
View File

@ -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))
}

View File

@ -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) {}

View File

@ -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)
//}

View File

@ -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)
//}

View File

@ -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
//}

107
x/fee_distribution/types.go Normal file
View File

@ -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...)
//}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

33
x/stake/delegation.go Normal file
View File

@ -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 }

View File

@ -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 {

View File

@ -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) {
}

54
x/stake/genesis.go Normal file
View File

@ -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,
}
}

View File

@ -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,
}

View File

@ -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")

File diff suppressed because it is too large Load Diff

View File

@ -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...)
}

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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 {

35
x/stake/params.go Normal file
View File

@ -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",
}
}

View File

@ -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
}

View File

@ -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)))
}

133
x/stake/shares.go Normal file
View File

@ -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")
}
}

45
x/stake/store.md Normal file
View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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())
}

View File

@ -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
}

View File

@ -1,3 +0,0 @@
package stake
// XXX test global state functions, candidate exchange rate functions etc.

238
x/stake/validator.go Normal file
View File

@ -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 }

408
x/stake/validator_test.go Normal file
View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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]))
}

View File

@ -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()