Merge PR #1119: Unbonding, Redelegation

* stake/fees spec updates
* staking overview.md revisions, moving files
* docs reorganization
* staking spec state revisions
* transaction stake updates
* complete staking spec update
* WIP adding unbonding/redelegation commands
* added msg types for unbonding, redelegation
* stake sub-package reorg
* working stake reorg
* modify lcd tests to not use hardcoded json strings
* add description update
* index keys
* key managment for unbonding redelegation complete
* update stake errors
* completed handleMsgCompleteUnbonding fn
* updated to use begin/complete unbonding/redelegation
* fix token shares bug
* develop docs into unbonding
* got non-tests compiling after merge develop
* working fixing tests
* PrivlegedKeeper -> PrivilegedKeeper
* tests compile
* fix some tests
* fixing tests
* remove PrivilegedKeeper
* get unbonding bug
* only rpc sig verification failed tests now
* move percent unbonding/redelegation to the CLI and out of handler logic
* remove min unbonding height
* add lcd txs
* add pool sanity checks, fix a buncha tests
* fix ante. set lcd log to debug (#1322)
* redelegation tests, adding query functionality for bonds
* add self-delegations at genesis ref #1165
* PR comments (mostly) addressed
* cleanup, added Query LCD functionality
* test cleanup/fixes
* fix governance test
* SlashValidatorSet -> ValidatorSet
* changelog
* stake lcd fix
* x/auth: fix chainID in ante
* fix lcd test
* fix lint, update lint make command for spelling
* lowercase error string
* don't expose coinkeeper in staking
* remove a few duplicate lines in changelog
* chain_id in stake lcd tests
* added transient redelegation
* 'transient' => 'transitive'
* Re-add nolint instruction
* Fix tiny linter error
This commit is contained in:
Rigel 2018-06-26 22:00:12 -04:00 committed by Christopher Goes
parent d6df6b07d1
commit 6f140d7296
71 changed files with 5678 additions and 3765 deletions

View File

@ -12,6 +12,14 @@ BREAKING CHANGES
* Removed MsgChangePubKey from auth
* Removed setPubKey from account mapper
* Removed GetMemo from Tx (it is still on StdTx)
* [cli] rearranged commands under subcommands
* [stake] remove Tick and add EndBlocker
* [stake] introduce concept of unbonding for delegations and validators
* `gaiacli stake unbond` replaced with `gaiacli stake begin-unbonding`
* introduced:
* `gaiacli stake complete-unbonding`
* `gaiacli stake begin-redelegation`
* `gaiacli stake complete-redelegation`
FEATURES
* [gaiacli] You can now attach a simple text-only memo to any transaction, with the `--memo` flag
@ -29,7 +37,11 @@ FEATURES
* [types] Switches internal representation of Int/Uint/Rat to use pointers
* [gaiad] unsafe_reset_all now resets addrbook.json
FIXES
FIXES
* [gaia] Added self delegation for validators in the genesis creation
* [lcd] tests now don't depend on raw json text
* [stake] error strings lower case
* [stake] pool loose tokens now accounts for unbonding and unbonding tokens not associated with any validator
* \#1259 - fix bug where certain tests that could have a nil pointer in defer
* \#1052 - Make all now works
* Retry on HTTP request failure in CLI tests, add option to retry tests in Makefile
@ -39,6 +51,16 @@ FIXES
* \#1353 - CLI: Show pool shares fractions in human-readable format
* \#1258 - printing big.rat's can no longer overflow int64
IMPROVEMENTS
* bank module uses go-wire codec instead of 'encoding/json'
* auth module uses go-wire codec instead of 'encoding/json'
* revised use of endblock and beginblock
* [stake] module reorganized to include `types` and `keeper` package
* [stake] keeper always loads the store (instead passing around which doesn't really boost efficiency)
* [stake] edit-validator changes now can use the keyword [do-not-modify] to not modify unspecified `--flag` (aka won't set them to `""` value)
* [types] added common tag constants
* [stake] offload more generic functionality from the handler into the keeper
## 0.19.0
*June 13, 2018*

View File

@ -108,7 +108,7 @@ test_cover:
@bash tests/test_cover.sh
test_lint:
gometalinter.v2 --disable-all --enable='golint' --vendor ./...
gometalinter.v2 --disable-all --enable='golint' --enable='misspell' --vendor ./...
benchmark:
@go test -bench=. $(PACKAGES_NOCLITEST)

View File

@ -598,6 +598,7 @@ func (app *BaseApp) Commit() (res abci.ResponseCommit) {
// Write the Deliver state and commit the MultiStore
app.deliverState.ms.Write()
commitID := app.cms.Commit()
// TODO: this is missing a module identifier and dumps byte array
app.Logger.Debug("Commit synced",
"commit", commitID,
)

View File

@ -32,7 +32,7 @@ import (
func TestKeys(t *testing.T) {
name, password := "test", "1234567890"
addr, seed := CreateAddr(t, "test", password, GetKB(t))
cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr})
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()
// get seed
@ -218,7 +218,7 @@ func TestValidators(t *testing.T) {
func TestCoinSend(t *testing.T) {
name, password := "test", "1234567890"
addr, seed := CreateAddr(t, "test", password, GetKB(t))
cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr})
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()
bz, err := hex.DecodeString("8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6")
@ -260,7 +260,7 @@ func TestCoinSend(t *testing.T) {
func TestIBCTransfer(t *testing.T) {
name, password := "test", "1234567890"
addr, seed := CreateAddr(t, "test", password, GetKB(t))
cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr})
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()
acc := getAccount(t, port, addr)
@ -289,7 +289,7 @@ func TestIBCTransfer(t *testing.T) {
func TestTxs(t *testing.T) {
name, password := "test", "1234567890"
addr, seed := CreateAddr(t, "test", password, GetKB(t))
cleanup, _, port := InitializeTestLCD(t, 2, []sdk.Address{addr})
cleanup, _, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()
// query wrong
@ -378,13 +378,13 @@ func TestValidatorsQuery(t *testing.T) {
func TestBonding(t *testing.T) {
name, password, denom := "test", "1234567890", "steak"
addr, seed := CreateAddr(t, "test", password, GetKB(t))
cleanup, pks, port := InitializeTestLCD(t, 2, []sdk.Address{addr})
cleanup, pks, port := InitializeTestLCD(t, 1, []sdk.Address{addr})
defer cleanup()
validator1Owner := pks[0].Address()
// create bond TX
resultTx := doBond(t, port, seed, name, password, addr, validator1Owner)
resultTx := doDelegate(t, port, seed, name, password, addr, validator1Owner)
tests.WaitForHeight(resultTx.Height+1, port)
// check if tx was committed
@ -405,7 +405,7 @@ func TestBonding(t *testing.T) {
// testing unbonding
// create unbond TX
resultTx = doUnbond(t, port, seed, name, password, addr, validator1Owner)
resultTx = doBeginUnbonding(t, port, seed, name, password, addr, validator1Owner)
tests.WaitForHeight(resultTx.Height+1, port)
// query validator
@ -416,12 +416,13 @@ func TestBonding(t *testing.T) {
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
// TODO fix shares fn in staking
// should the sender should have not received any coins as the unbonding has only just begun
// query sender
//acc := getAccount(t, sendAddr)
//coins := acc.GetCoins()
//assert.Equal(t, int64(98), coins.AmountOf(coinDenom))
acc = getAccount(t, port, addr)
coins = acc.GetCoins()
assert.Equal(t, int64(40), coins.AmountOf("steak").Int64())
// TODO add redelegation, need more complex capabilities such to mock context and
}
func TestSubmitProposal(t *testing.T) {
@ -572,6 +573,8 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add
receiveAddr := receiveInfo.PubKey.Address()
receiveAddrBech := sdk.MustBech32ifyAcc(receiveAddr)
chainID := viper.GetString(client.FlagChainID)
// get the account to get the sequence
acc := getAccount(t, port, addr)
accnum := acc.GetAccountNumber()
@ -584,13 +587,14 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Add
"account_number":%d,
"sequence": %d,
"gas": 100000,
"chain_id": "%s",
"amount":[
{
"denom": "%s",
"amount": 1
}
]
}`, name, password, accnum, sequence, "steak"))
}`, name, password, accnum, sequence, chainID, "steak"))
res, body := Request(t, port, "POST", "/ibc/testchain/"+receiveAddrBech+"/send", jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -606,7 +610,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A
validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr)
// get the account to get the sequence
res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/bonding_status/"+validatorAddrBech, nil)
res, body := Request(t, port, "GET", "/stake/"+delegatorAddrBech+"/delegation/"+validatorAddrBech, nil)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var bond stake.Delegation
err := cdc.UnmarshalJSON([]byte(body), &bond)
@ -614,7 +618,7 @@ func getDelegation(t *testing.T, port string, delegatorAddr, validatorAddr sdk.A
return bond
}
func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
func doDelegate(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
// get the account to get the sequence
acc := getAccount(t, port, delegatorAddr)
accnum := acc.GetAccountNumber()
@ -623,6 +627,8 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali
delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr)
validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr)
chainID := viper.GetString(client.FlagChainID)
// send
jsonStr := []byte(fmt.Sprintf(`{
"name": "%s",
@ -630,15 +636,19 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali
"account_number": %d,
"sequence": %d,
"gas": 10000,
"delegate": [
"chain_id": "%s",
"delegations": [
{
"delegator_addr": "%s",
"validator_addr": "%s",
"bond": { "denom": "%s", "amount": 60 }
}
],
"unbond": []
}`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech, "steak"))
"begin_unbondings": [],
"complete_unbondings": [],
"begin_redelegates": [],
"complete_redelegates": []
}`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech, "steak"))
res, body := Request(t, port, "POST", "/stake/delegations", jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
@ -649,7 +659,9 @@ func doBond(t *testing.T, port, seed, name, password string, delegatorAddr, vali
return results[0]
}
func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
func doBeginUnbonding(t *testing.T, port, seed, name, password string,
delegatorAddr, validatorAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
// get the account to get the sequence
acc := getAccount(t, port, delegatorAddr)
accnum := acc.GetAccountNumber()
@ -658,6 +670,8 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va
delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr)
validatorAddrBech := sdk.MustBech32ifyVal(validatorAddr)
chainID := viper.GetString(client.FlagChainID)
// send
jsonStr := []byte(fmt.Sprintf(`{
"name": "%s",
@ -665,15 +679,64 @@ func doUnbond(t *testing.T, port, seed, name, password string, delegatorAddr, va
"account_number": %d,
"sequence": %d,
"gas": 10000,
"delegate": [],
"unbond": [
"chain_id": "%s",
"delegations": [],
"begin_unbondings": [
{
"delegator_addr": "%s",
"validator_addr": "%s",
"shares": "30"
}
]
}`, name, password, accnum, sequence, delegatorAddrBech, validatorAddrBech))
],
"complete_unbondings": [],
"begin_redelegates": [],
"complete_redelegates": []
}`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorAddrBech))
res, body := Request(t, port, "POST", "/stake/delegations", jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)
var results []ctypes.ResultBroadcastTxCommit
err := cdc.UnmarshalJSON([]byte(body), &results)
require.Nil(t, err)
return results[0]
}
func doBeginRedelegation(t *testing.T, port, seed, name, password string,
delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) (resultTx ctypes.ResultBroadcastTxCommit) {
// get the account to get the sequence
acc := getAccount(t, port, delegatorAddr)
accnum := acc.GetAccountNumber()
sequence := acc.GetSequence()
delegatorAddrBech := sdk.MustBech32ifyAcc(delegatorAddr)
validatorSrcAddrBech := sdk.MustBech32ifyVal(validatorSrcAddr)
validatorDstAddrBech := sdk.MustBech32ifyVal(validatorDstAddr)
chainID := viper.GetString(client.FlagChainID)
// send
jsonStr := []byte(fmt.Sprintf(`{
"name": "%s",
"password": "%s",
"account_number": %d,
"sequence": %d,
"gas": 10000,
"chain_id": "%s",
"delegations": [],
"begin_unbondings": [],
"complete_unbondings": [],
"begin_redelegates": [
{
"delegator_addr": "%s",
"validator_src_addr": "%s",
"validator_dst_addr": "%s",
"shares": "30"
}
],
"complete_redelegates": []
}`, name, password, accnum, sequence, chainID, delegatorAddrBech, validatorSrcAddrBech, validatorDstAddrBech))
res, body := Request(t, port, "POST", "/stake/delegations", jsonStr)
require.Equal(t, http.StatusOK, res.StatusCode, body)

View File

@ -100,13 +100,13 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (
config.TxIndex.IndexAllTags = true
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
logger = log.NewFilter(logger, log.AllowError())
logger = log.NewFilter(logger, log.AllowDebug())
privValidatorFile := config.PrivValidatorFile()
privVal := pvm.LoadOrGenFilePV(privValidatorFile)
privVal.Reset()
db := dbm.NewMemDB()
app := gapp.NewGaiaApp(logger, db)
cdc = gapp.MakeCodec() // XXX
cdc = gapp.MakeCodec()
genesisFile := config.GenesisFile()
genDoc, err := tmtypes.GenesisDocFromFile(genesisFile)
@ -146,6 +146,7 @@ func InitializeTestLCD(t *testing.T, nValidators int, initAddrs []sdk.Address) (
accAuth.Coins = sdk.Coins{sdk.NewCoin("steak", 100)}
acc := gapp.NewGenesisAccount(&accAuth)
genesisState.Accounts = append(genesisState.Accounts, acc)
genesisState.StakeData.Pool.LooseTokens += 100
}
appState, err := wire.MarshalJSONIndent(cdc, genesisState)

View File

@ -3,6 +3,7 @@ package app
import (
"encoding/json"
"errors"
"github.com/spf13/pflag"
crypto "github.com/tendermint/go-crypto"
tmtypes "github.com/tendermint/tendermint/types"
@ -17,8 +18,8 @@ import (
var (
// bonded tokens given to genesis validators/accounts
freeFermionVal = sdk.NewInt(100)
freeFermionsAcc = sdk.NewInt(50)
freeFermionVal = int64(100)
freeFermionsAcc = int64(50)
)
// State to Unmarshal
@ -124,7 +125,7 @@ func GaiaAppGenTxNF(cdc *wire.Codec, pk crypto.PubKey, addr sdk.Address, name st
validator = tmtypes.GenesisValidator{
PubKey: pk,
Power: freeFermionVal.Int64(),
Power: freeFermionVal,
}
return
}
@ -155,22 +156,33 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState
accAuth := auth.NewBaseAccountWithAddress(genTx.Address)
accAuth.Coins = sdk.Coins{
{genTx.Name + "Token", sdk.NewInt(1000)},
{"steak", freeFermionsAcc},
{"steak", sdk.NewInt(freeFermionsAcc)},
}
acc := NewGenesisAccount(&accAuth)
genaccs[i] = acc
stakeData.Pool.LooseUnbondedTokens = stakeData.Pool.LooseUnbondedTokens.Add(freeFermionsAcc) // increase the supply
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionsAcc // increase the supply
// add the validator
if len(genTx.Name) > 0 {
desc := stake.NewDescription(genTx.Name, "", "", "")
validator := stake.NewValidator(genTx.Address, genTx.PubKey, desc)
validator.PoolShares = stake.NewBondedShares(sdk.NewRatFromInt(freeFermionVal))
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens + freeFermionVal // increase the supply
// add some new shares to the validator
var issuedDelShares sdk.Rat
validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, freeFermionVal)
stakeData.Validators = append(stakeData.Validators, validator)
// pool logic
stakeData.Pool.BondedTokens = stakeData.Pool.BondedTokens.Add(freeFermionVal)
stakeData.Pool.BondedShares = sdk.NewRatFromInt(stakeData.Pool.BondedTokens)
// create the self-delegation from the issuedDelShares
delegation := stake.Delegation{
DelegatorAddr: validator.Owner,
ValidatorAddr: validator.Owner,
Shares: issuedDelShares,
Height: 0,
}
stakeData.Bonds = append(stakeData.Bonds, delegation)
}
}

View File

@ -95,7 +95,8 @@ func main() {
stakecmd.GetCmdCreateValidator(cdc),
stakecmd.GetCmdEditValidator(cdc),
stakecmd.GetCmdDelegate(cdc),
stakecmd.GetCmdUnbond(cdc),
stakecmd.GetCmdUnbond("stake", cdc),
stakecmd.GetCmdRedelegate("stake", cdc),
slashingcmd.GetCmdUnrevoke(cdc),
)...)
rootCmd.AddCommand(

View File

@ -37,8 +37,8 @@ processProvisions():
provisions = pool.Inflation * (pool.TotalSupply / hrsPerYr)
pool.LooseUnbondedTokens += provisions
feePool += LooseUnbondedTokens
pool.LooseTokens += provisions
feePool += LooseTokens
setPool(pool)

View File

@ -12,7 +12,7 @@ information, etc.
```golang
type Pool struct {
LooseUnbondedTokens int64 // tokens not associated with any validator
LooseTokens int64 // tokens not associated with any validator
UnbondedTokens int64 // reserve of unbonded tokens held with validators
UnbondingTokens int64 // tokens moving from bonded to unbonded pool
BondedTokens int64 // reserve of bonded tokens

View File

@ -172,7 +172,8 @@ func (app *BasecoinApp) ExportAppStateAndValidators() (appState json.RawMessage,
app.accountMapper.IterateAccounts(ctx, appendAccount)
genState := types.GenesisState{
Accounts: accounts,
Accounts: accounts,
StakeData: stake.WriteGenesis(ctx, app.stakeKeeper),
}
appState, err = wire.MarshalJSONIndent(app.cdc, genState)
if err != nil {

View File

@ -51,6 +51,10 @@ func main() {
// add query/post commands (custom to binary)
rootCmd.AddCommand(
client.GetCommands(
stakecmd.GetCmdQueryValidator("stake", cdc),
stakecmd.GetCmdQueryValidators("stake", cdc),
stakecmd.GetCmdQueryDelegation("stake", cdc),
stakecmd.GetCmdQueryDelegations("stake", cdc),
authcmd.GetAccountCmd("acc", cdc, types.GetAccountDecoder(cdc)),
)...)
@ -62,7 +66,7 @@ func main() {
stakecmd.GetCmdCreateValidator(cdc),
stakecmd.GetCmdEditValidator(cdc),
stakecmd.GetCmdDelegate(cdc),
stakecmd.GetCmdUnbond(cdc),
stakecmd.GetCmdUnbond("stake", cdc),
)...)
// add proxy, version and key info

View File

@ -110,7 +110,9 @@ func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - r
func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero
func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 }
func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than
func (r Rat) GTE(r2 Rat) bool { return !r.LT(r2) } // greater than or equal
func (r Rat) LT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == -1 } // less than
func (r Rat) LTE(r2 Rat) bool { return !r.GT(r2) } // less than or equal
func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.Rat)} } // Mul - multiplication
func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.Rat)} } // Quo - quotient
func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.Rat)} } // Add - addition

View File

@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) {
bz, err := r.MarshalText()
require.NoError(t, err)
var r2 Rat = Rat{new(big.Rat)}
var r2 = Rat{new(big.Rat)}
err = r2.UnmarshalText(bz)
require.NoError(t, err)
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)

View File

@ -59,8 +59,9 @@ type ValidatorSet interface {
IterateValidatorsBonded(Context,
func(index int64, validator Validator) (stop bool))
Validator(Context, Address) Validator // get a particular validator by owner address
TotalPower(Context) Rat // total power of the validator set
Validator(Context, Address) Validator // get a particular validator by owner address
TotalPower(Context) Rat // total power of the validator set
Slash(Context, crypto.PubKey, int64, Rat) // slash the validator and delegators of the validator, specifying offence height & slash fraction
Revoke(Context, crypto.PubKey) // revoke a validator
Unrevoke(Context, crypto.PubKey) // unrevoke a validator

View File

@ -21,8 +21,8 @@ func (t Tags) AppendTag(k string, v []byte) Tags {
}
// Append two lists of tags
func (t Tags) AppendTags(a Tags) Tags {
return append(t, a...)
func (t Tags) AppendTags(tags Tags) Tags {
return append(t, tags...)
}
// Turn tags into KVPair list
@ -51,3 +51,13 @@ func NewTags(tags ...interface{}) Tags {
func MakeTag(k string, v []byte) Tag {
return Tag{Key: []byte(k), Value: v}
}
//__________________________________________________
// common tags
var (
TagAction = "action"
TagSrcValidator = "source-validator"
TagDstValidator = "destination-validator"
TagDelegator = "delegator"
)

View File

@ -35,3 +35,15 @@ func MarshalJSONIndent(cdc *Codec, obj interface{}) ([]byte, error) {
}
return out.Bytes(), nil
}
//__________________________________________________________________
// generic sealed codec to be used throughout sdk
var Cdc *Codec
func init() {
cdc := NewCodec()
RegisterCrypto(cdc)
Cdc = cdc
//Cdc = cdc.Seal() // TODO uncomment once amino upgraded to 0.9.10
}

View File

@ -57,7 +57,11 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker {
func getInitChainer(mapp *mock.App, keeper Keeper, stakeKeeper stake.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState())
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stake.InitGenesis(ctx, stakeKeeper, stakeGenesis)
InitGenesis(ctx, keeper, DefaultGenesisState())
return abci.ResponseInitChain{}
}

View File

@ -55,7 +55,9 @@ func getEndBlocker(keeper stake.Keeper) sdk.EndBlocker {
func getInitChainer(mapp *mock.App, keeper stake.Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
stake.InitGenesis(ctx, keeper, stake.DefaultGenesisState())
stakeGenesis := stake.DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
stake.InitGenesis(ctx, keeper, stakeGenesis)
return abci.ResponseInitChain{}
}
}

View File

@ -12,41 +12,16 @@ const (
// Default slashing codespace
DefaultCodespace sdk.CodespaceType = 10
// Invalid validator
CodeInvalidValidator CodeType = 201
// Validator jailed
CodeValidatorJailed CodeType = 202
CodeInvalidValidator CodeType = 101
CodeValidatorJailed CodeType = 102
)
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "that address is not associated with any known validator")
return sdk.NewError(codespace, CodeInvalidValidator, "that address is not associated with any known validator")
}
func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "validator does not exist for that address")
return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address")
}
func ErrValidatorJailed(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked")
}
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidValidator:
return "invalid Validator"
case CodeValidatorJailed:
return "validator Jailed"
default:
return sdk.CodeToDefaultMsg(code)
}
}
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
}
return codeToDefaultMsg(code)
}
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(codespace, code, msg)
return sdk.NewError(codespace, CodeValidatorJailed, "validator jailed, cannot yet be unrevoked")
}

View File

@ -80,7 +80,7 @@ func TestHandleAbsentValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens.Int64())
require.Equal(t, int64(100), pool.BondedTokens)
// 51st block missed
ctx = ctx.WithBlockHeight(height)
@ -109,7 +109,7 @@ func TestHandleAbsentValidator(t *testing.T) {
// validator should have been slashed
pool = sk.GetPool(ctx)
require.Equal(t, int64(99), pool.BondedTokens.Int64())
require.Equal(t, int64(99), pool.BondedTokens)
// validator start height should have been changed
info, found = keeper.getValidatorSigningInfo(ctx, val.Address())
@ -167,5 +167,5 @@ func TestHandleNewValidator(t *testing.T) {
validator, _ := sk.GetValidatorByPubKey(ctx, val)
require.Equal(t, sdk.Bonded, validator.GetStatus())
pool := sk.GetPool(ctx)
require.Equal(t, int64(100), pool.BondedTokens.Int64())
require.Equal(t, int64(100), pool.BondedTokens)
}

View File

@ -20,6 +20,8 @@ import (
"github.com/cosmos/cosmos-sdk/x/stake"
)
// TODO remove dependencies on staking (should only refer to validator set type from sdk)
var (
addrs = []sdk.Address{
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"),
@ -61,7 +63,7 @@ func createTestInput(t *testing.T) (sdk.Context, bank.Keeper, stake.Keeper, Keep
ck := bank.NewKeeper(accountMapper)
sk := stake.NewKeeper(cdc, keyStake, ck, stake.DefaultCodespace)
genesis := stake.DefaultGenesisState()
genesis.Pool.LooseUnbondedTokens = initCoins.MulRaw(int64(len(addrs)))
genesis.Pool.LooseTokens = initCoins.MulRaw(int64(len(addrs))).Int64()
stake.InitGenesis(ctx, sk, genesis)
for _, addr := range addrs {
ck.AddCoins(ctx, addr, sdk.Coins{
@ -89,9 +91,9 @@ func testAddr(addr string) sdk.Address {
func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator {
return stake.MsgCreateValidator{
Description: stake.Description{},
ValidatorAddr: address,
PubKey: pubKey,
Bond: sdk.Coin{"steak", amt},
Description: stake.Description{},
ValidatorAddr: address,
PubKey: pubKey,
SelfDelegation: sdk.Coin{"steak", amt},
}
}

View File

@ -22,9 +22,9 @@ var (
addr3 = crypto.GenPrivKeyEd25519().PubKey().Address()
priv4 = crypto.GenPrivKeyEd25519()
addr4 = priv4.PubKey().Address()
coins = sdk.Coins{sdk.NewCoin("foocoin", 10)}
coins = sdk.Coins{{"foocoin", sdk.NewInt(10)}}
fee = auth.StdFee{
sdk.Coins{sdk.NewCoin("foocoin", 0)},
sdk.Coins{{"foocoin", sdk.NewInt(0)}},
100000,
}
)
@ -60,7 +60,9 @@ func getEndBlocker(keeper Keeper) sdk.EndBlocker {
func getInitChainer(mapp *mock.App, keeper Keeper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
InitGenesis(ctx, keeper, DefaultGenesisState())
stakeGenesis := DefaultGenesisState()
stakeGenesis.Pool.LooseTokens = 100000
InitGenesis(ctx, keeper, stakeGenesis)
return abci.ResponseInitChain{}
}
@ -93,8 +95,8 @@ func checkDelegation(t *testing.T, mapp *mock.App, keeper Keeper, delegatorAddr,
func TestStakeMsgs(t *testing.T) {
mapp, keeper := getMockApp(t)
genCoin := sdk.NewCoin("steak", 42)
bondCoin := sdk.NewCoin("steak", 10)
genCoin := sdk.Coin{"steak", sdk.NewInt(42)}
bondCoin := sdk.Coin{"steak", sdk.NewInt(10)}
acc1 := &auth.BaseAccount{
Address: addr1,
@ -148,10 +150,14 @@ func TestStakeMsgs(t *testing.T) {
checkDelegation(t, mapp, keeper, addr2, addr1, true, sdk.NewRat(10))
////////////////////
// Unbond
// Begin Unbonding
unbondMsg := NewMsgUnbond(addr2, addr1, "MAX")
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{unbondMsg}, []int64{1}, []int64{1}, true, priv2)
mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin})
beginUnbondingMsg := NewMsgBeginUnbonding(addr2, addr1, sdk.NewRat(10))
mock.SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{beginUnbondingMsg}, []int64{1}, []int64{1}, true, priv2)
// delegation should exist anymore
checkDelegation(t, mapp, keeper, addr2, addr1, false, sdk.Rat{})
// balance should be the same because bonding not yet complete
mock.CheckBalance(t, mapp, addr2, sdk.Coins{genCoin.Minus(bondCoin)})
}

View File

@ -6,11 +6,14 @@ import (
// nolint
const (
FlagAddressDelegator = "address-delegator"
FlagAddressValidator = "address-validator"
FlagPubKey = "pubkey"
FlagAmount = "amount"
FlagShares = "shares"
FlagAddressDelegator = "address-delegator"
FlagAddressValidator = "address-validator"
FlagAddressValidatorSrc = "addr-validator-source"
FlagAddressValidatorDst = "addr-validator-dest"
FlagPubKey = "pubkey"
FlagAmount = "amount"
FlagSharesAmount = "shares-amount"
FlagSharesPercent = "shares-percent"
FlagMoniker = "moniker"
FlagIdentity = "keybase-sig"
@ -20,22 +23,26 @@ const (
// common flagsets to add to various functions
var (
fsPk = flag.NewFlagSet("", flag.ContinueOnError)
fsAmount = flag.NewFlagSet("", flag.ContinueOnError)
fsShares = flag.NewFlagSet("", flag.ContinueOnError)
fsDescription = flag.NewFlagSet("", flag.ContinueOnError)
fsValidator = flag.NewFlagSet("", flag.ContinueOnError)
fsDelegator = flag.NewFlagSet("", flag.ContinueOnError)
fsPk = flag.NewFlagSet("", flag.ContinueOnError)
fsAmount = flag.NewFlagSet("", flag.ContinueOnError)
fsShares = flag.NewFlagSet("", flag.ContinueOnError)
fsDescription = flag.NewFlagSet("", flag.ContinueOnError)
fsValidator = flag.NewFlagSet("", flag.ContinueOnError)
fsDelegator = flag.NewFlagSet("", flag.ContinueOnError)
fsRedelegation = flag.NewFlagSet("", flag.ContinueOnError)
)
func init() {
fsPk.String(FlagPubKey, "", "Go-Amino encoded hex PubKey of the validator. For Ed25519 the go-amino prepend hex is 1624de6220")
fsAmount.String(FlagAmount, "1steak", "Amount of coins to bond")
fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)")
fsDescription.String(FlagMoniker, "", "validator name")
fsDescription.String(FlagIdentity, "", "optional keybase signature")
fsDescription.String(FlagWebsite, "", "optional website")
fsDescription.String(FlagDetails, "", "optional details")
fsShares.String(FlagSharesAmount, "", "Amount of source-shares to either unbond or redelegate as a positive integer or decimal")
fsShares.String(FlagSharesPercent, "", "Percent of source-shares to either unbond or redelegate as a positive integer or decimal >0 and <=1")
fsDescription.String(FlagMoniker, "[do-not-modify]", "validator name")
fsDescription.String(FlagIdentity, "[do-not-modify]", "optional keybase signature")
fsDescription.String(FlagWebsite, "[do-not-modify]", "optional website")
fsDescription.String(FlagDetails, "[do-not-modify]", "optional details")
fsValidator.String(FlagAddressValidator, "", "hex address of the validator")
fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator")
fsRedelegation.String(FlagAddressValidatorSrc, "", "hex address of the source validator")
fsRedelegation.String(FlagAddressValidatorDst, "", "hex address of the destination validator")
}

View File

@ -9,7 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" // XXX fix
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/stake"
)
@ -105,14 +105,14 @@ func GetCmdQueryValidators(storeName string, cdc *wire.Codec) *cobra.Command {
return cmd
}
// get the command to query a single delegation bond
// get the command to query a single delegation
func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "delegation",
Short: "Query a delegations bond based on address and validator address",
Short: "Query a delegation based on address and validator address",
RunE: func(cmd *cobra.Command, args []string) error {
addr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator))
valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator))
if err != nil {
return err
}
@ -122,26 +122,26 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command {
return err
}
key := stake.GetDelegationKey(delAddr, addr, cdc)
key := stake.GetDelegationKey(delAddr, valAddr, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.QueryStore(key, storeName)
if err != nil {
return err
}
// parse out the bond
bond := new(stake.Delegation)
// parse out the delegation
delegation := new(stake.Delegation)
switch viper.Get(cli.OutputFlag) {
case "text":
resp, err := bond.HumanReadableString()
resp, err := delegation.HumanReadableString()
if err != nil {
return err
}
fmt.Println(resp)
case "json":
cdc.MustUnmarshalBinary(res, bond)
output, err := wire.MarshalJSONIndent(cdc, bond)
cdc.MustUnmarshalBinary(res, delegation)
output, err := wire.MarshalJSONIndent(cdc, delegation)
if err != nil {
return err
}
@ -157,7 +157,7 @@ func GetCmdQueryDelegation(storeName string, cdc *wire.Codec) *cobra.Command {
return cmd
}
// get the command to query all the validators bonded to a delegation
// get the command to query all the delegations made from one delegator
func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "delegations [delegator-addr]",
@ -196,3 +196,190 @@ func GetCmdQueryDelegations(storeName string, cdc *wire.Codec) *cobra.Command {
}
return cmd
}
// get the command to query a single unbonding-delegation record
func GetCmdQueryUnbondingDelegation(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbonding-delegation",
Short: "Query an unbonding-delegation record based on delegator and validator address",
RunE: func(cmd *cobra.Command, args []string) error {
valAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator))
if err != nil {
return err
}
delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
key := stake.GetUBDKey(delAddr, valAddr, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.QueryStore(key, storeName)
if err != nil {
return err
}
// parse out the unbonding delegation
ubd := new(stake.UnbondingDelegation)
switch viper.Get(cli.OutputFlag) {
case "text":
resp, err := ubd.HumanReadableString()
if err != nil {
return err
}
fmt.Println(resp)
case "json":
cdc.MustUnmarshalBinary(res, ubd)
output, err := wire.MarshalJSONIndent(cdc, ubd)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
return nil
},
}
cmd.Flags().AddFlagSet(fsValidator)
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}
// get the command to query all the unbonding-delegation records for a delegator
func GetCmdQueryUnbondingDelegations(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbonding-delegations [delegator-addr]",
Short: "Query all unbonding-delegations records for one delegator",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
delegatorAddr, err := sdk.GetAccAddressBech32(args[0])
if err != nil {
return err
}
key := stake.GetUBDsKey(delegatorAddr, cdc)
ctx := context.NewCoreContextFromViper()
resKVs, err := ctx.QuerySubspace(cdc, key, storeName)
if err != nil {
return err
}
// parse out the validators
var ubds []stake.UnbondingDelegation
for _, KV := range resKVs {
var ubd stake.UnbondingDelegation
cdc.MustUnmarshalBinary(KV.Value, &ubd)
ubds = append(ubds, ubd)
}
output, err := wire.MarshalJSONIndent(cdc, ubds)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
return cmd
}
// get the command to query a single unbonding-delegation record
func GetCmdQueryRedelegation(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbonding-delegation",
Short: "Query an unbonding-delegation record based on delegator and validator address",
RunE: func(cmd *cobra.Command, args []string) error {
valSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc))
if err != nil {
return err
}
valDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst))
if err != nil {
return err
}
delAddr, err := sdk.GetValAddressHex(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
key := stake.GetREDKey(delAddr, valSrcAddr, valDstAddr, cdc)
ctx := context.NewCoreContextFromViper()
res, err := ctx.QueryStore(key, storeName)
if err != nil {
return err
}
// parse out the unbonding delegation
red := new(stake.Redelegation)
switch viper.Get(cli.OutputFlag) {
case "text":
resp, err := red.HumanReadableString()
if err != nil {
return err
}
fmt.Println(resp)
case "json":
cdc.MustUnmarshalBinary(res, red)
output, err := wire.MarshalJSONIndent(cdc, red)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
return nil
},
}
cmd.Flags().AddFlagSet(fsRedelegation)
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}
// get the command to query all the unbonding-delegation records for a delegator
func GetCmdQueryRedelegations(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbonding-delegations [delegator-addr]",
Short: "Query all unbonding-delegations records for one delegator",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
delegatorAddr, err := sdk.GetAccAddressBech32(args[0])
if err != nil {
return err
}
key := stake.GetREDsKey(delegatorAddr, cdc)
ctx := context.NewCoreContextFromViper()
resKVs, err := ctx.QuerySubspace(cdc, key, storeName)
if err != nil {
return err
}
// parse out the validators
var reds []stake.Redelegation
for _, KV := range resKVs {
var red stake.Redelegation
cdc.MustUnmarshalBinary(KV.Value, &red)
reds = append(reds, red)
}
output, err := wire.MarshalJSONIndent(cdc, reds)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
return cmd
}

View File

@ -3,6 +3,7 @@ package cli
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -104,11 +105,11 @@ func GetCmdEditValidator(cdc *wire.Codec) *cobra.Command {
return cmd
}
// create edit validator command
// delegate command
func GetCmdDelegate(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "delegate",
Short: "delegate coins to an existing validator",
Short: "delegate liquid tokens to an validator",
RunE: func(cmd *cobra.Command, args []string) error {
amount, err := sdk.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
@ -143,33 +144,185 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command {
}
// create edit validator command
func GetCmdUnbond(cdc *wire.Codec) *cobra.Command {
func GetCmdRedelegate(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbond",
Short: "unbond shares from a validator",
Use: "redelegate",
Short: "redelegate illiquid tokens from one validator to another",
}
cmd.AddCommand(
GetCmdBeginRedelegate(storeName, cdc),
GetCmdCompleteRedelegate(cdc),
)
return cmd
}
// redelegate command
func GetCmdBeginRedelegate(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "begin",
Short: "begin redelegation",
RunE: func(cmd *cobra.Command, args []string) error {
// check the shares before broadcasting
sharesStr := viper.GetString(FlagShares)
var shares sdk.Rat
if sharesStr != "MAX" {
var err error
shares, err = sdk.NewRatFromDecimal(sharesStr)
if err != nil {
return err
}
if !shares.GT(sdk.ZeroRat()) {
return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)")
}
var err error
delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc))
if err != nil {
return err
}
validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst))
if err != nil {
return err
}
// get the shares amount
sharesAmountStr := viper.GetString(FlagSharesAmount)
sharesPercentStr := viper.GetString(FlagSharesPercent)
sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr,
delegatorAddr, validatorSrcAddr)
if err != nil {
return err
}
msg := stake.NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr, sharesAmount)
// build and sign the transaction, then broadcast to Tendermint
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsShares)
cmd.Flags().AddFlagSet(fsDelegator)
cmd.Flags().AddFlagSet(fsRedelegation)
return cmd
}
func getShares(storeName string, cdc *wire.Codec, sharesAmountStr, sharesPercentStr string,
delegatorAddr, validatorAddr sdk.Address) (sharesAmount sdk.Rat, err error) {
switch {
case sharesAmountStr != "" && sharesPercentStr != "":
return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both")
case sharesAmountStr == "" && sharesPercentStr == "":
return sharesAmount, errors.Errorf("can either specify the amount OR the percent of the shares, not both")
case sharesAmountStr != "":
sharesAmount, err = sdk.NewRatFromDecimal(sharesAmountStr)
if err != nil {
return sharesAmount, err
}
if !sharesAmount.GT(sdk.ZeroRat()) {
return sharesAmount, errors.Errorf("shares amount must be positive number (ex. 123, 1.23456789)")
}
case sharesPercentStr != "":
var sharesPercent sdk.Rat
sharesPercent, err = sdk.NewRatFromDecimal(sharesPercentStr)
if err != nil {
return sharesAmount, err
}
if !sharesPercent.GT(sdk.ZeroRat()) || !sharesPercent.LTE(sdk.OneRat()) {
return sharesAmount, errors.Errorf("shares percent must be >0 and <=1 (ex. 0.01, 0.75, 1)")
}
// make a query to get the existing delegation shares
key := stake.GetDelegationKey(delegatorAddr, validatorAddr, cdc)
ctx := context.NewCoreContextFromViper()
resQuery, err := ctx.QueryStore(key, storeName)
if err != nil {
return sharesAmount, err
}
var delegation stake.Delegation
err = cdc.UnmarshalBinary(resQuery, &delegation)
if err != nil {
return sharesAmount, errors.Errorf("cannot find delegation to determine percent Error: %v", err)
}
sharesAmount = sharesPercent.Mul(delegation.Shares)
}
return
}
// redelegate command
func GetCmdCompleteRedelegate(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "complete",
Short: "complete redelegation",
RunE: func(cmd *cobra.Command, args []string) error {
delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator))
validatorSrcAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorSrc))
validatorDstAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidatorDst))
if err != nil {
return err
}
msg := stake.NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr, validatorDstAddr)
// build and sign the transaction, then broadcast to Tendermint
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsDelegator)
cmd.Flags().AddFlagSet(fsRedelegation)
return cmd
}
// create edit validator command
func GetCmdUnbond(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbond",
Short: "begin or complete unbonding shares from a validator",
}
cmd.AddCommand(
GetCmdBeginUnbonding(storeName, cdc),
GetCmdCompleteUnbonding(cdc),
)
return cmd
}
// create edit validator command
func GetCmdBeginUnbonding(storeName string, cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "begin",
Short: "begin unbonding",
RunE: func(cmd *cobra.Command, args []string) error {
delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator))
if err != nil {
return err
}
validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator))
if err != nil {
return err
}
msg := stake.NewMsgUnbond(delegatorAddr, validatorAddr, sharesStr)
// get the shares amount
sharesAmountStr := viper.GetString(FlagSharesAmount)
sharesPercentStr := viper.GetString(FlagSharesPercent)
sharesAmount, err := getShares(storeName, cdc, sharesAmountStr, sharesPercentStr,
delegatorAddr, validatorAddr)
if err != nil {
return err
}
msg := stake.NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sharesAmount)
// build and sign the transaction, then broadcast to Tendermint
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
@ -189,3 +342,35 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command {
cmd.Flags().AddFlagSet(fsValidator)
return cmd
}
// create edit validator command
func GetCmdCompleteUnbonding(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "complete",
Short: "complete unbonding",
RunE: func(cmd *cobra.Command, args []string) error {
delegatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressDelegator))
validatorAddr, err := sdk.GetAccAddressBech32(viper.GetString(FlagAddressValidator))
if err != nil {
return err
}
msg := stake.NewMsgCompleteUnbonding(delegatorAddr, validatorAddr)
// build and sign the transaction, then broadcast to Tendermint
ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc))
res, err := ctx.EnsureSignBuildBroadcast(ctx.FromAddressName, []sdk.Msg{msg}, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsDelegator)
cmd.Flags().AddFlagSet(fsValidator)
return cmd
}

View File

@ -13,18 +13,30 @@ import (
)
func registerQueryRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) {
r.HandleFunc(
"/stake/{delegator}/bonding_status/{validator}",
bondingStatusHandlerFn(ctx, "stake", cdc),
"/stake/{delegator}/delegation/{validator}",
delegationHandlerFn(ctx, "stake", cdc),
).Methods("GET")
r.HandleFunc(
"/stake/{delegator}/ubd/{validator}",
ubdHandlerFn(ctx, "stake", cdc),
).Methods("GET")
r.HandleFunc(
"/stake/{delegator}/red/{validator_src}/{validator_dst}",
redHandlerFn(ctx, "stake", cdc),
).Methods("GET")
r.HandleFunc(
"/stake/validators",
validatorsHandlerFn(ctx, "stake", cdc),
).Methods("GET")
}
// http request handler to query delegator bonding status
func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc {
// http request handler to query a delegation
func delegationHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// read parameters
@ -51,25 +63,147 @@ func bondingStatusHandlerFn(ctx context.CoreContext, storeName string, cdc *wire
res, err := ctx.QueryStore(key, storeName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't query bond. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("couldn't query delegation. Error: %s", err.Error())))
return
}
// the query will return empty if there is no data for this bond
// the query will return empty if there is no data for this record
if len(res) == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
var bond stake.Delegation
err = cdc.UnmarshalBinary(res, &bond)
var delegation stake.Delegation
err = cdc.UnmarshalBinary(res, &delegation)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode bond. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("couldn't decode delegation. Error: %s", err.Error())))
return
}
output, err := cdc.MarshalJSON(bond)
output, err := cdc.MarshalJSON(delegation)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}
// http request handler to query an unbonding-delegation
func ubdHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// read parameters
vars := mux.Vars(r)
bech32delegator := vars["delegator"]
bech32validator := vars["validator"]
delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
validatorAddr, err := sdk.GetValAddressBech32(bech32validator)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
key := stake.GetUBDKey(delegatorAddr, validatorAddr, cdc)
res, err := ctx.QueryStore(key, storeName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't query unbonding-delegation. Error: %s", err.Error())))
return
}
// the query will return empty if there is no data for this record
if len(res) == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
var ubd stake.UnbondingDelegation
err = cdc.UnmarshalBinary(res, &ubd)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode unbonding-delegation. Error: %s", err.Error())))
return
}
output, err := cdc.MarshalJSON(ubd)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
w.Write(output)
}
}
// http request handler to query an redelegation
func redHandlerFn(ctx context.CoreContext, storeName string, cdc *wire.Codec) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// read parameters
vars := mux.Vars(r)
bech32delegator := vars["delegator"]
bech32validatorSrc := vars["validator_src"]
bech32validatorDst := vars["validator_dst"]
delegatorAddr, err := sdk.GetAccAddressBech32(bech32delegator)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
validatorSrcAddr, err := sdk.GetValAddressBech32(bech32validatorSrc)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
validatorDstAddr, err := sdk.GetValAddressBech32(bech32validatorDst)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error()))
return
}
key := stake.GetREDKey(delegatorAddr, validatorSrcAddr, validatorDstAddr, cdc)
res, err := ctx.QueryStore(key, storeName)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't query redelegation. Error: %s", err.Error())))
return
}
// the query will return empty if there is no data for this record
if len(res) == 0 {
w.WriteHeader(http.StatusNoContent)
return
}
var red stake.Redelegation
err = cdc.UnmarshalBinary(res, &red)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode redelegation. Error: %s", err.Error())))
return
}
output, err := cdc.MarshalJSON(red)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))

View File

@ -24,31 +24,50 @@ func registerTxRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec, k
).Methods("POST")
}
type msgDelegateInput struct {
type msgDelegationsInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorAddr string `json:"validator_addr"` // in bech32
Bond sdk.Coin `json:"bond"`
}
type msgUnbondInput struct {
type msgBeginRedelegateInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32
ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32
SharesAmount string `json:"shares"`
}
type msgCompleteRedelegateInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorSrcAddr string `json:"validator_src_addr"` // in bech32
ValidatorDstAddr string `json:"validator_dst_addr"` // in bech32
}
type msgBeginUnbondingInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorAddr string `json:"validator_addr"` // in bech32
SharesAmount string `json:"shares"`
}
type msgCompleteUnbondingInput struct {
DelegatorAddr string `json:"delegator_addr"` // in bech32
ValidatorAddr string `json:"validator_addr"` // in bech32
Shares string `json:"shares"`
}
type editDelegationsBody struct {
LocalAccountName string `json:"name"`
Password string `json:"password"`
ChainID string `json:"chain_id"`
AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"`
Delegate []msgDelegateInput `json:"delegate"`
Unbond []msgUnbondInput `json:"unbond"`
// request body for edit delegations
type EditDelegationsBody struct {
LocalAccountName string `json:"name"`
Password string `json:"password"`
ChainID string `json:"chain_id"`
AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"`
Gas int64 `json:"gas"`
Delegations []msgDelegationsInput `json:"delegations"`
BeginUnbondings []msgBeginUnbondingInput `json:"begin_unbondings"`
CompleteUnbondings []msgCompleteUnbondingInput `json:"complete_unbondings"`
BeginRedelegates []msgBeginRedelegateInput `json:"begin_redelegates"`
CompleteRedelegates []msgCompleteRedelegateInput `json:"complete_redelegates"`
}
func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx context.CoreContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var m editDelegationsBody
var m EditDelegationsBody
body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
@ -70,24 +89,29 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte
}
// build messages
messages := make([]sdk.Msg, len(m.Delegate)+len(m.Unbond))
messages := make([]sdk.Msg, len(m.Delegations)+
len(m.BeginRedelegates)+
len(m.CompleteRedelegates)+
len(m.BeginUnbondings)+
len(m.CompleteUnbondings))
i := 0
for _, msg := range m.Delegate {
for _, msg := range m.Delegations {
delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode delegator. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())))
return
}
validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode validator. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
if !bytes.Equal(info.Address(), delegatorAddr) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("must use own delegator address"))
w.Write([]byte("Must use own delegator address"))
return
}
messages[i] = stake.MsgDelegate{
@ -97,28 +121,131 @@ func editDelegationsRequestHandlerFn(cdc *wire.Codec, kb keys.Keybase, ctx conte
}
i++
}
for _, msg := range m.Unbond {
for _, msg := range m.BeginRedelegates {
delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode delegator. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())))
return
}
if !bytes.Equal(info.Address(), delegatorAddr) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Must use own delegator address"))
return
}
validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
shares, err := sdk.NewRatFromDecimal(msg.SharesAmount)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())))
return
}
messages[i] = stake.MsgBeginRedelegate{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
SharesAmount: shares,
}
i++
}
for _, msg := range m.CompleteRedelegates {
delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())))
return
}
validatorSrcAddr, err := sdk.GetValAddressBech32(msg.ValidatorSrcAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
validatorDstAddr, err := sdk.GetValAddressBech32(msg.ValidatorDstAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
if !bytes.Equal(info.Address(), delegatorAddr) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Must use own delegator address"))
return
}
messages[i] = stake.MsgCompleteRedelegate{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
}
i++
}
for _, msg := range m.BeginUnbondings {
delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())))
return
}
if !bytes.Equal(info.Address(), delegatorAddr) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Must use own delegator address"))
return
}
validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("couldn't decode validator. Error: %s", err.Error())))
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
shares, err := sdk.NewRatFromDecimal(msg.SharesAmount)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode shares amount. Error: %s", err.Error())))
return
}
messages[i] = stake.MsgBeginUnbonding{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
SharesAmount: shares,
}
i++
}
for _, msg := range m.CompleteUnbondings {
delegatorAddr, err := sdk.GetAccAddressBech32(msg.DelegatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode delegator. Error: %s", err.Error())))
return
}
validatorAddr, err := sdk.GetValAddressBech32(msg.ValidatorAddr)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("Couldn't decode validator. Error: %s", err.Error())))
return
}
if !bytes.Equal(info.Address(), delegatorAddr) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("must use own delegator address"))
w.Write([]byte("Must use own delegator address"))
return
}
messages[i] = stake.MsgUnbond{
messages[i] = stake.MsgCompleteUnbonding{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Shares: msg.Shares,
}
i++
}

View File

@ -1,52 +0,0 @@
package stake
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Delegation represents the bond with tokens held by an account. It is
// owned by one delegator, and is associated with the voting power of one
// pubKey.
type Delegation struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
Shares sdk.Rat `json:"shares"`
Height int64 `json:"height"` // Last height bond updated
}
func (b Delegation) equal(b2 Delegation) bool {
return bytes.Equal(b.DelegatorAddr, b2.DelegatorAddr) &&
bytes.Equal(b.ValidatorAddr, b2.ValidatorAddr) &&
b.Height == b2.Height &&
b.Shares.Equal(b2.Shares)
}
// ensure fulfills the sdk validator types
var _ sdk.Delegation = Delegation{}
// nolint - for sdk.Delegation
func (b Delegation) GetDelegator() sdk.Address { return b.DelegatorAddr }
func (b Delegation) GetValidator() sdk.Address { return b.ValidatorAddr }
func (b Delegation) GetBondShares() sdk.Rat { return b.Shares }
//Human Friendly pretty printer
func (b Delegation) HumanReadableString() (string, error) {
bechAcc, err := sdk.Bech32ifyAcc(b.DelegatorAddr)
if err != nil {
return "", err
}
bechVal, err := sdk.Bech32ifyAcc(b.ValidatorAddr)
if err != nil {
return "", err
}
resp := "Delegation \n"
resp += fmt.Sprintf("Delegator: %s\n", bechAcc)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Shares: %s", b.Shares.String())
resp += fmt.Sprintf("Height: %d", b.Height)
return resp, nil
}

View File

@ -1,117 +0,0 @@
// nolint
package stake
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
type CodeType = sdk.CodeType
const (
DefaultCodespace sdk.CodespaceType = 4
// Gaia errors reserve 200 ~ 299.
CodeInvalidValidator CodeType = 201
CodeInvalidBond CodeType = 202
CodeInvalidInput CodeType = 203
CodeValidatorJailed CodeType = 204
CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)
// NOTE: Don't stringer this, we'll put better messages in later.
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidValidator:
return "invalid Validator"
case CodeInvalidBond:
return "invalid Bond"
case CodeInvalidInput:
return "invalid Input"
case CodeUnauthorized:
return "unauthorized"
case CodeInternal:
return "internal Error"
case CodeUnknownRequest:
return "unknown request"
default:
return sdk.CodeToDefaultMsg(code)
}
}
//----------------------------------------
// Error constructors
func ErrNotEnoughBondShares(codespace sdk.CodespaceType, shares string) sdk.Error {
return newError(codespace, CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares))
}
func ErrValidatorEmpty(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "cannot bond to an empty validator")
}
func ErrBadBondingDenom(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidBond, "invalid coin denomination")
}
func ErrBadBondingAmount(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidBond, "amount must be > 0")
}
func ErrNoBondingAcct(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "no bond account for this (address, validator) pair")
}
func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "commission must be positive")
}
func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "commission cannot be more than 100%")
}
func ErrBadValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "validator does not exist for that address")
}
func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "delegator does not exist for that address")
}
func ErrValidatorExistsAddr(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator")
}
func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "validator for this address is currently revoked")
}
func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "missing signature")
}
func ErrBondNotNominated(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "cannot bond to non-nominated account")
}
func ErrNoValidatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "validator does not exist for that address")
}
func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "delegator does not contain validator bond")
}
func ErrInsufficientFunds(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidInput, "insufficient bond shares")
}
func ErrBadShares(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidInput, "bad shares provided as input, must be MAX or decimal")
}
func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error {
return newError(codespace, CodeInvalidValidator, "error removing validator")
}
//----------------------------------------
// TODO group with code from x/bank/errors.go
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
}
return codeToDefaultMsg(code)
}
func newError(codespace sdk.CodespaceType, code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(codespace, code, msg)
}

View File

@ -4,63 +4,39 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// GenesisState - all staking state that must be provided at genesis
type GenesisState struct {
Pool Pool `json:"pool"`
Params Params `json:"params"`
Validators []Validator `json:"validators"`
Bonds []Delegation `json:"bonds"`
}
func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState {
return GenesisState{
Pool: pool,
Params: params,
Validators: validators,
Bonds: bonds,
}
}
// get raw genesis raw message for testing
func DefaultGenesisState() GenesisState {
return GenesisState{
Pool: InitialPool(),
Params: DefaultParams(),
}
}
// InitGenesis - store genesis parameters
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
store := ctx.KVStore(k.storeKey)
k.setPool(ctx, data.Pool)
k.setNewParams(ctx, data.Params)
func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) {
keeper.SetPool(ctx, data.Pool)
keeper.SetNewParams(ctx, data.Params)
keeper.InitIntraTxCounter(ctx)
for _, validator := range data.Validators {
// set validator
k.setValidator(ctx, validator)
keeper.SetValidator(ctx, validator)
// manually set indexes for the first time
k.setValidatorByPubKeyIndex(ctx, validator)
k.setValidatorByPowerIndex(ctx, validator, data.Pool)
keeper.SetValidatorByPubKeyIndex(ctx, validator)
keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool)
if validator.Status() == sdk.Bonded {
store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner)
keeper.SetValidatorBondedIndex(ctx, validator)
}
}
for _, bond := range data.Bonds {
k.setDelegation(ctx, bond)
keeper.SetDelegation(ctx, bond)
}
k.updateBondedValidatorsFull(ctx, store)
keeper.UpdateBondedValidatorsFull(ctx)
}
// WriteGenesis - output genesis parameters
func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState {
pool := k.GetPool(ctx)
params := k.GetParams(ctx)
validators := k.getAllValidators(ctx)
bonds := k.getAllDelegations(ctx)
return GenesisState{
func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
pool := keeper.GetPool(ctx)
params := keeper.GetParams(ctx)
validators := keeper.GetAllValidators(ctx)
bonds := keeper.GetAllDelegations(ctx)
return types.GenesisState{
pool,
params,
validators,
@ -69,8 +45,8 @@ func WriteGenesis(ctx sdk.Context, k Keeper) GenesisState {
}
// WriteValidators - output current validator set
func WriteValidators(ctx sdk.Context, k Keeper) (vals []tmtypes.GenesisValidator) {
k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
func WriteValidators(ctx sdk.Context, keeper Keeper) (vals []tmtypes.GenesisValidator) {
keeper.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) (stop bool) {
vals = append(vals, tmtypes.GenesisValidator{
PubKey: validator.GetPubKey(),
Power: validator.GetPower().Evaluate(),

View File

@ -1,24 +1,32 @@
package stake
import (
"bytes"
abci "github.com/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/x/stake/keeper"
"github.com/cosmos/cosmos-sdk/x/stake/tags"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
func NewHandler(k Keeper) sdk.Handler {
func NewHandler(k keeper.Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgCreateValidator:
case types.MsgCreateValidator:
return handleMsgCreateValidator(ctx, msg, k)
case MsgEditValidator:
case types.MsgEditValidator:
return handleMsgEditValidator(ctx, msg, k)
case MsgDelegate:
case types.MsgDelegate:
return handleMsgDelegate(ctx, msg, k)
case MsgUnbond:
return handleMsgUnbond(ctx, msg, k)
case types.MsgBeginRedelegate:
return handleMsgBeginRedelegate(ctx, msg, k)
case types.MsgCompleteRedelegate:
return handleMsgCompleteRedelegate(ctx, msg, k)
case types.MsgBeginUnbonding:
return handleMsgBeginUnbonding(ctx, msg, k)
case types.MsgCompleteUnbonding:
return handleMsgCompleteUnbonding(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
}
@ -26,25 +34,25 @@ func NewHandler(k Keeper) sdk.Handler {
}
// Called every block, process inflation, update validator set
func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) {
func EndBlocker(ctx sdk.Context, k keeper.Keeper) (ValidatorUpdates []abci.Validator) {
pool := k.GetPool(ctx)
// Process Validator Provisions
blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm
// Process types.Validator Provisions
blockTime := ctx.BlockHeader().Time
if pool.InflationLastTime+blockTime >= 3600 {
pool.InflationLastTime = blockTime
pool = k.processProvisions(ctx)
pool = k.ProcessProvisions(ctx)
}
// save the params
k.setPool(ctx, pool)
k.SetPool(ctx, pool)
// reset the intra-transaction counter
k.setIntraTxCounter(ctx, 0)
k.SetIntraTxCounter(ctx, 0)
// calculate validator set changes
ValidatorUpdates = k.getTendermintUpdates(ctx)
k.clearTendermintUpdates(ctx)
ValidatorUpdates = k.GetTendermintUpdates(ctx)
k.ClearTendermintUpdates(ctx)
return
}
@ -53,212 +61,150 @@ func EndBlocker(ctx sdk.Context, k Keeper) (ValidatorUpdates []abci.Validator) {
// These functions assume everything has been authenticated,
// now we just perform action and save
func handleMsgCreateValidator(ctx sdk.Context, msg MsgCreateValidator, k Keeper) sdk.Result {
func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k keeper.Keeper) sdk.Result {
// check to see if the pubkey or sender has been registered before
_, found := k.GetValidator(ctx, msg.ValidatorAddr)
if found {
return ErrValidatorExistsAddr(k.codespace).Result()
return ErrValidatorAlreadyExists(k.Codespace()).Result()
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
return ErrBadBondingDenom(k.codespace).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
if msg.SelfDelegation.Denom != k.GetParams(ctx).BondDenom {
return ErrBadDenom(k.Codespace()).Result()
}
validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description)
k.setValidator(ctx, validator)
k.setValidatorByPubKeyIndex(ctx, validator)
tags := sdk.NewTags(
"action", []byte("createValidator"),
"validator", msg.ValidatorAddr.Bytes(),
"moniker", []byte(msg.Description.Moniker),
"identity", []byte(msg.Description.Identity),
)
k.SetValidator(ctx, validator)
k.SetValidatorByPubKeyIndex(ctx, validator)
// move coins from the msg.Address account to a (self-bond) delegator account
// move coins from the msg.Address account to a (self-delegation) delegator account
// the validator account and global shares are updated within here
delegateTags, err := delegate(ctx, k, msg.ValidatorAddr, msg.Bond, validator)
_, err := k.Delegate(ctx, msg.ValidatorAddr, msg.SelfDelegation, validator)
if err != nil {
return err.Result()
}
tags = tags.AppendTags(delegateTags)
tags := sdk.NewTags(
tags.Action, tags.ActionCreateValidator,
tags.DstValidator, []byte(msg.ValidatorAddr.String()),
tags.Moniker, []byte(msg.Description.Moniker),
tags.Identity, []byte(msg.Description.Identity),
)
return sdk.Result{
Tags: tags,
}
}
func handleMsgEditValidator(ctx sdk.Context, msg MsgEditValidator, k Keeper) sdk.Result {
func handleMsgEditValidator(ctx sdk.Context, msg types.MsgEditValidator, k keeper.Keeper) sdk.Result {
// validator must already be registered
validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
if !found {
return ErrBadValidatorAddr(k.codespace).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
return ErrNoValidatorFound(k.Codespace()).Result()
}
// XXX move to types
// replace all editable fields (clients should autofill existing values)
validator.Description.Moniker = msg.Description.Moniker
validator.Description.Identity = msg.Description.Identity
validator.Description.Website = msg.Description.Website
validator.Description.Details = msg.Description.Details
description, err := validator.Description.UpdateDescription(msg.Description)
if err != nil {
return err.Result()
}
validator.Description = description
k.updateValidator(ctx, validator)
k.UpdateValidator(ctx, validator)
tags := sdk.NewTags(
"action", []byte("editValidator"),
"validator", msg.ValidatorAddr.Bytes(),
"moniker", []byte(msg.Description.Moniker),
"identity", []byte(msg.Description.Identity),
tags.Action, tags.ActionEditValidator,
tags.DstValidator, []byte(msg.ValidatorAddr.String()),
tags.Moniker, []byte(description.Moniker),
tags.Identity, []byte(description.Identity),
)
return sdk.Result{
Tags: tags,
}
}
func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
func handleMsgDelegate(ctx sdk.Context, msg types.MsgDelegate, k keeper.Keeper) sdk.Result {
validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
if !found {
return ErrBadValidatorAddr(k.codespace).Result()
return ErrNoValidatorFound(k.Codespace()).Result()
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
return ErrBadBondingDenom(k.codespace).Result()
return ErrBadDenom(k.Codespace()).Result()
}
if validator.Revoked == true {
return ErrValidatorRevoked(k.codespace).Result()
return ErrValidatorRevoked(k.Codespace()).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
}
tags, err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, validator)
_, err := k.Delegate(ctx, msg.DelegatorAddr, msg.Bond, validator)
if err != nil {
return err.Result()
}
tags := sdk.NewTags(
tags.Action, tags.ActionDelegate,
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.DstValidator, []byte(msg.ValidatorAddr.String()),
)
return sdk.Result{
Tags: tags,
}
}
// common functionality between handlers
func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
bondAmt sdk.Coin, validator Validator) (sdk.Tags, sdk.Error) {
// Get or create the delegator bond
bond, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner)
if !found {
bond = Delegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validator.Owner,
Shares: sdk.ZeroRat(),
}
}
// Account new shares, save
pool := k.GetPool(ctx)
_, _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt})
func handleMsgBeginUnbonding(ctx sdk.Context, msg types.MsgBeginUnbonding, k keeper.Keeper) sdk.Result {
err := k.BeginUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr, msg.SharesAmount)
if err != nil {
return nil, err
return err.Result()
}
validator, pool, newShares := validator.addTokensFromDel(pool, bondAmt.Amount)
bond.Shares = bond.Shares.Add(newShares)
// Update bond height
bond.Height = ctx.BlockHeight()
k.setPool(ctx, pool)
k.setDelegation(ctx, bond)
k.updateValidator(ctx, validator)
tags := sdk.NewTags("action", []byte("delegate"), "delegator", delegatorAddr.Bytes(), "validator", validator.Owner.Bytes())
return tags, nil
tags := sdk.NewTags(
tags.Action, tags.ActionBeginUnbonding,
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorAddr.String()),
)
return sdk.Result{Tags: tags}
}
func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
func handleMsgCompleteUnbonding(ctx sdk.Context, msg types.MsgCompleteUnbonding, k keeper.Keeper) sdk.Result {
// check if bond has any shares in it unbond
bond, found := k.GetDelegation(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
if !found {
return ErrNoDelegatorForAddress(k.codespace).Result()
err := k.CompleteUnbonding(ctx, msg.DelegatorAddr, msg.ValidatorAddr)
if err != nil {
return err.Result()
}
var delShares sdk.Rat
tags := sdk.NewTags(
tags.Action, ActionCompleteUnbonding,
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorAddr.String()),
)
// test that there are enough shares to unbond
if msg.Shares == "MAX" {
if !bond.Shares.GT(sdk.ZeroRat()) {
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
}
} else {
var err sdk.Error
delShares, err = sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return err.Result()
}
if bond.Shares.LT(delShares) {
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
}
}
// get validator
validator, found := k.GetValidator(ctx, msg.ValidatorAddr)
if !found {
return ErrNoValidatorForAddress(k.codespace).Result()
}
if ctx.IsCheckTx() {
return sdk.Result{}
}
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
if msg.Shares == "MAX" {
delShares = bond.Shares
}
// subtract bond tokens from delegator bond
bond.Shares = bond.Shares.Sub(delShares)
// remove the bond
revokeValidator := false
if bond.Shares.IsZero() {
// if the bond is the owner of the validator then
// trigger a revoke validator
if bytes.Equal(bond.DelegatorAddr, validator.Owner) &&
validator.Revoked == false {
revokeValidator = true
}
k.removeDelegation(ctx, bond)
} else {
// Update bond height
bond.Height = ctx.BlockHeight()
k.setDelegation(ctx, bond)
}
// Add the coins
pool := k.GetPool(ctx)
validator, pool, returnAmount := validator.removeDelShares(pool, delShares)
k.setPool(ctx, pool)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
/////////////////////////////////////
// revoke validator if necessary
if revokeValidator {
validator.Revoked = true
}
validator = k.updateValidator(ctx, validator)
if validator.DelegatorShares.IsZero() {
k.removeValidator(ctx, validator.Owner)
}
tags := sdk.NewTags("action", []byte("unbond"), "delegator", msg.DelegatorAddr.Bytes(), "validator", msg.ValidatorAddr.Bytes())
return sdk.Result{
Tags: tags,
}
return sdk.Result{Tags: tags}
}
func handleMsgBeginRedelegate(ctx sdk.Context, msg types.MsgBeginRedelegate, k keeper.Keeper) sdk.Result {
err := k.BeginRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr,
msg.ValidatorDstAddr, msg.SharesAmount)
if err != nil {
return err.Result()
}
tags := sdk.NewTags(
tags.Action, tags.ActionBeginRedelegation,
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()),
tags.DstValidator, []byte(msg.ValidatorDstAddr.String()),
)
return sdk.Result{Tags: tags}
}
func handleMsgCompleteRedelegate(ctx sdk.Context, msg types.MsgCompleteRedelegate, k keeper.Keeper) sdk.Result {
err := k.CompleteRedelegation(ctx, msg.DelegatorAddr, msg.ValidatorSrcAddr, msg.ValidatorDstAddr)
if err != nil {
return err.Result()
}
tags := sdk.NewTags(
tags.Action, tags.ActionCompleteRedelegation,
tags.Delegator, []byte(msg.DelegatorAddr.String()),
tags.SrcValidator, []byte(msg.ValidatorSrcAddr.String()),
tags.DstValidator, []byte(msg.ValidatorDstAddr.String()),
)
return sdk.Result{Tags: tags}
}

View File

@ -1,7 +1,6 @@
package stake
import (
"strconv"
"testing"
"github.com/stretchr/testify/assert"
@ -10,38 +9,48 @@ import (
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
keep "github.com/cosmos/cosmos-sdk/x/stake/keeper"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
//______________________________________________________________________
func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt sdk.Int) MsgCreateValidator {
func newTestMsgCreateValidator(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgCreateValidator {
return MsgCreateValidator{
Description: Description{},
ValidatorAddr: address,
PubKey: pubKey,
Bond: sdk.Coin{"steak", amt},
Description: Description{},
ValidatorAddr: address,
PubKey: pubKey,
SelfDelegation: sdk.Coin{"steak", sdk.NewInt(amt)},
}
}
func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt sdk.Int) MsgDelegate {
func newTestMsgDelegate(delegatorAddr, validatorAddr sdk.Address, amt int64) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: sdk.Coin{"steak", amt},
Bond: sdk.Coin{"steak", sdk.NewInt(amt)},
}
}
// retrieve params which are instant
func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params {
params := keeper.GetParams(ctx)
params.UnbondingTime = 0
keeper.SetParams(ctx, params)
return params
}
//______________________________________________________________________
func TestValidatorByPowerIndex(t *testing.T) {
validatorAddr, validatorAddr3 := addrs[0], addrs[1]
validatorAddr, validatorAddr3 := keep.Addrs[0], keep.Addrs[1]
initBond := sdk.NewInt(1000000)
initBondStr := "1000"
ctx, _, keeper := createTestInput(t, false, initBond)
initBond := int64(1000000)
ctx, _, keeper := keep.CreateTestInput(t, false, initBond)
_ = setInstantUnbondPeriod(keeper, ctx)
// create validator
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond)
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got)
@ -49,7 +58,7 @@ func TestValidatorByPowerIndex(t *testing.T) {
bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr)
require.True(t, found)
gotBond := bond.Shares.Evaluate()
require.Equal(t, initBond.Int64(), gotBond,
require.Equal(t, initBond, gotBond,
"initBond: %v\ngotBond: %v\nbond: %v\n",
initBond, gotBond, bond)
@ -57,60 +66,62 @@ func TestValidatorByPowerIndex(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
pool := keeper.GetPool(ctx)
power := GetValidatorsByPowerKey(validator, pool)
require.True(t, keeper.validatorByPowerIndexExists(ctx, power))
power := keep.GetValidatorsByPowerIndexKey(validator, pool)
require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power))
// create a second validator keep it bonded
msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, pks[2], sdk.NewInt(1000000))
msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000))
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got)
// slash and revoke the first validator
keeper.Slash(ctx, pks[0], 0, sdk.NewRat(1, 2))
keeper.Revoke(ctx, pks[0])
keeper.Slash(ctx, keep.PKs[0], 0, sdk.NewRat(1, 2))
keeper.Revoke(ctx, keep.PKs[0])
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Unbonded, validator.PoolShares.Status) // ensure is unbonded
require.Equal(t, int64(500000), validator.PoolShares.Amount.Evaluate()) // ensure is unbonded
// the old power record should have been deleted as the power changed
assert.False(t, keeper.validatorByPowerIndexExists(ctx, power))
assert.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power))
// but the new power record should have been created
validator, found = keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
pool = keeper.GetPool(ctx)
power2 := GetValidatorsByPowerKey(validator, pool)
require.True(t, keeper.validatorByPowerIndexExists(ctx, power2))
power2 := GetValidatorsByPowerIndexKey(validator, pool)
require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power2))
// inflate a bunch
for i := 0; i < 20000; i++ {
pool = keeper.processProvisions(ctx)
keeper.setPool(ctx, pool)
pool = keeper.ProcessProvisions(ctx)
keeper.SetPool(ctx, pool)
}
// now the new record power index should be the same as the original record
power3 := GetValidatorsByPowerKey(validator, pool)
power3 := GetValidatorsByPowerIndexKey(validator, pool)
assert.Equal(t, power2, power3)
// unbond self-delegation
msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "MAX")
got = handleMsgUnbond(ctx, msgUnbond, keeper)
assert.True(t, got.IsOK(),
"got: %v\nmsgUnbond: %v\ninitBondStr: %v\n", got, msgUnbond, initBondStr)
msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(1000000))
msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// verify that by power key nolonger exists
_, found = keeper.GetValidator(ctx, validatorAddr)
require.False(t, found)
assert.False(t, keeper.validatorByPowerIndexExists(ctx, power3))
assert.False(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power3))
}
func TestDuplicatesMsgCreateValidator(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000))
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr := addrs[0]
pk := pks[0]
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, sdk.NewInt(10))
validatorAddr := keep.Addrs[0]
pk := keep.PKs[0]
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pk, 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "%v", got)
validator, found := keeper.GetValidator(ctx, validatorAddr)
@ -124,41 +135,41 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) {
assert.Equal(t, Description{}, validator.Description)
// one validator cannot bond twice
msgCreateValidator.PubKey = pks[1]
msgCreateValidator.PubKey = keep.PKs[1]
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.False(t, got.IsOK(), "%v", got)
}
func TestIncrementsMsgDelegate(t *testing.T) {
initBond := sdk.NewInt(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
initBond := int64(1000)
ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
bondAmount := sdk.NewInt(10)
validatorAddr, delegatorAddr := addrs[0], addrs[1]
bondAmount := int64(10)
validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1]
// first create validator
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], bondAmount)
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got)
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
assert.Equal(t, bondAmount, validator.DelegatorShares.EvaluateInt())
assert.Equal(t, bondAmount, validator.PoolShares.Bonded().EvaluateInt(), "validator: %v", validator)
assert.Equal(t, bondAmount, validator.DelegatorShares.Evaluate())
assert.Equal(t, bondAmount, validator.PoolShares.Bonded().Evaluate(), "validator: %v", validator)
_, found = keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.False(t, found)
bond, found := keeper.GetDelegation(ctx, validatorAddr, validatorAddr)
require.True(t, found)
assert.Equal(t, bondAmount, bond.Shares.EvaluateInt())
assert.Equal(t, bondAmount, bond.Shares.Evaluate())
pool := keeper.GetPool(ctx)
exRate := validator.DelegatorShareExRate(pool)
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v", exRate)
assert.Equal(t, bondAmount, pool.BondedShares.EvaluateInt())
assert.Equal(t, bondAmount, pool.BondedShares.Evaluate())
assert.Equal(t, bondAmount, pool.BondedTokens)
// just send the same msgbond multiple times
@ -180,14 +191,14 @@ func TestIncrementsMsgDelegate(t *testing.T) {
exRate := validator.DelegatorShareExRate(pool)
require.True(t, exRate.Equal(sdk.OneRat()), "expected exRate 1 got %v, i = %v", exRate, i)
expBond := sdk.NewInt(int64(i + 1)).Mul(bondAmount)
expDelegatorShares := sdk.NewInt(int64(i + 2)).Mul(bondAmount) // (1 self delegation)
expDelegatorAcc := initBond.Sub(expBond)
expBond := int64(i+1) * bondAmount
expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation)
expDelegatorAcc := sdk.NewInt(initBond - expBond)
require.Equal(t, bond.Height, int64(i), "Incorrect bond height")
gotBond := bond.Shares.EvaluateInt()
gotDelegatorShares := validator.DelegatorShares.EvaluateInt()
gotBond := bond.Shares.Evaluate()
gotDelegatorShares := validator.DelegatorShares.Evaluate()
gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, expBond, gotBond,
@ -203,14 +214,14 @@ func TestIncrementsMsgDelegate(t *testing.T) {
}
func TestIncrementsMsgUnbond(t *testing.T) {
initBond := sdk.NewInt(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
initBond := int64(1000)
ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond)
params := setInstantUnbondPeriod(keeper, ctx)
// create validator, delegate
validatorAddr, delegatorAddr := addrs[0], addrs[1]
validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1]
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], initBond)
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got)
@ -220,16 +231,19 @@ func TestIncrementsMsgUnbond(t *testing.T) {
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
assert.Equal(t, initBond.MulRaw(2), validator.DelegatorShares.EvaluateInt())
assert.Equal(t, initBond.MulRaw(2), validator.PoolShares.Bonded().EvaluateInt())
assert.Equal(t, initBond*2, validator.DelegatorShares.Evaluate())
assert.Equal(t, initBond*2, validator.PoolShares.Bonded().Evaluate())
// just send the same msgUnbond multiple times
// TODO use decimals here
unbondShares, unbondSharesStr := int64(10), "10"
msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr)
unbondShares := sdk.NewRat(10)
msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares)
msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr)
numUnbonds := 5
for i := 0; i < numUnbonds; i++ {
got := handleMsgUnbond(ctx, msgUnbond, keeper)
got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the accounts and the bond account have the appropriate values
@ -238,12 +252,12 @@ func TestIncrementsMsgUnbond(t *testing.T) {
bond, found := keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
require.True(t, found)
expBond := initBond.SubRaw(int64(i+1) * unbondShares)
expDelegatorShares := initBond.MulRaw(2).SubRaw(int64(i+1) * unbondShares)
expDelegatorAcc := initBond.Sub(expBond)
expBond := initBond - int64(i+1)*unbondShares.Evaluate()
expDelegatorShares := 2*initBond - int64(i+1)*unbondShares.Evaluate()
expDelegatorAcc := sdk.NewInt(initBond - expBond)
gotBond := bond.Shares.EvaluateInt()
gotDelegatorShares := validator.DelegatorShares.EvaluateInt()
gotBond := bond.Shares.Evaluate()
gotDelegatorShares := validator.DelegatorShares.Evaluate()
gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, expBond, gotBond,
@ -263,41 +277,42 @@ func TestIncrementsMsgUnbond(t *testing.T) {
//1<<63 + 1, // more than int64
1<<63 - 1,
1 << 31,
initBond.Int64(),
initBond,
}
for _, c := range errorCases {
unbondShares := strconv.Itoa(int(c))
msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, unbondShares)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
unbondShares := sdk.NewRat(int64(c))
msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.False(t, got.IsOK(), "expected unbond msg to fail")
}
leftBonded := initBond.SubRaw(unbondShares * int64(numUnbonds))
leftBonded := initBond - int64(numUnbonds)*unbondShares.Evaluate()
// should be unable to unbond one more than we have
unbondSharesStr = strconv.Itoa(int(leftBonded.Int64()) + 1)
msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
unbondShares = sdk.NewRat(leftBonded + 1)
msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
assert.False(t, got.IsOK(),
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded)
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares.String(), leftBonded)
// should be able to unbond just what we have
unbondSharesStr = strconv.Itoa(int(leftBonded.Int64()))
msgUnbond = NewMsgUnbond(delegatorAddr, validatorAddr, unbondSharesStr)
got = handleMsgUnbond(ctx, msgUnbond, keeper)
unbondShares = sdk.NewRat(leftBonded)
msgBeginUnbonding = NewMsgBeginUnbonding(delegatorAddr, validatorAddr, unbondShares)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
assert.True(t, got.IsOK(),
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded)
"got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgBeginUnbonding, unbondShares, leftBonded)
}
func TestMultipleMsgCreateValidator(t *testing.T) {
initBond := sdk.NewInt(1000)
ctx, accMapper, keeper := createTestInput(t, false, initBond)
params := keeper.GetParams(ctx)
validatorAddrs := []sdk.Address{addrs[0], addrs[1], addrs[2]}
initBond := int64(1000)
ctx, accMapper, keeper := keep.CreateTestInput(t, false, initBond)
params := setInstantUnbondPeriod(keeper, ctx)
validatorAddrs := []sdk.Address{keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]}
// bond them all
for i, validatorAddr := range validatorAddrs {
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[i], sdk.NewInt(10))
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[i], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
@ -305,7 +320,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
validators := keeper.GetValidators(ctx, 100)
require.Equal(t, (i + 1), len(validators))
val := validators[i]
balanceExpd := initBond.SubRaw(10)
balanceExpd := sdk.NewInt(initBond - 10)
balanceGot := accMapper.GetAccount(ctx, val.Owner).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, i+1, len(validators), "expected %d validators got %d, validators: %v", i+1, len(validators), validators)
require.Equal(t, 10, int(val.DelegatorShares.Evaluate()), "expected %d shares, got %d", 10, val.DelegatorShares)
@ -316,8 +331,11 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
for i, validatorAddr := range validatorAddrs {
validatorPre, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
msgUnbond := NewMsgUnbond(validatorAddr, validatorAddr, "10") // self-delegation
got := handleMsgUnbond(ctx, msgUnbond, keeper)
msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10)) // self-delegation
msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr)
got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the account is unbonded
@ -328,24 +346,25 @@ func TestMultipleMsgCreateValidator(t *testing.T) {
_, found = keeper.GetValidator(ctx, validatorAddr)
require.False(t, found)
expBalance := initBond
expBalance := sdk.NewInt(initBond)
gotBalance := accMapper.GetAccount(ctx, validatorPre.Owner).GetCoins().AmountOf(params.BondDenom)
require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance)
}
}
func TestMultipleMsgDelegate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000))
validatorAddr, delegatorAddrs := addrs[0], addrs[1:]
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr, delegatorAddrs := keep.Addrs[0], keep.Addrs[1:]
_ = setInstantUnbondPeriod(keeper, ctx)
//first make a validator
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10))
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected msg to be ok, got %v", got)
// delegate multiple parties
for i, delegatorAddr := range delegatorAddrs {
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewInt(10))
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10)
got := handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
@ -357,8 +376,11 @@ func TestMultipleMsgDelegate(t *testing.T) {
// unbond them all
for i, delegatorAddr := range delegatorAddrs {
msgUnbond := NewMsgUnbond(delegatorAddr, validatorAddr, "10")
got := handleMsgUnbond(ctx, msgUnbond, keeper)
msgBeginUnbonding := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10))
msgCompleteUnbonding := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr)
got := handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got)
//Check that the account is unbonded
@ -368,37 +390,173 @@ func TestMultipleMsgDelegate(t *testing.T) {
}
func TestRevokeValidator(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(1000))
validatorAddr, delegatorAddr := addrs[0], addrs[1]
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr, delegatorAddr := keep.Addrs[0], keep.Addrs[1]
_ = setInstantUnbondPeriod(keeper, ctx)
// create the validator
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, pks[0], sdk.NewInt(10))
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// bond a delegator
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, sdk.NewInt(10))
msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10)
got = handleMsgDelegate(ctx, msgDelegate, keeper)
require.True(t, got.IsOK(), "expected ok, got %v", got)
validator, _ := keeper.GetValidator(ctx, validatorAddr)
// unbond the validators bond portion
msgUnbondValidator := NewMsgUnbond(validatorAddr, validatorAddr, "10")
got = handleMsgUnbond(ctx, msgUnbondValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgBeginUnbondingValidator := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10))
msgCompleteUnbondingValidator := NewMsgCompleteUnbonding(validatorAddr, validatorAddr)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingValidator, keeper)
require.True(t, got.IsOK(), "expected no error")
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingValidator, keeper)
require.True(t, got.IsOK(), "expected no error")
validator, found := keeper.GetValidator(ctx, validatorAddr)
require.True(t, found)
require.True(t, validator.Revoked)
require.True(t, validator.Revoked, "%v", validator)
// test that this address cannot yet be bonded too because is revoked
got = handleMsgDelegate(ctx, msgDelegate, keeper)
assert.False(t, got.IsOK(), "expected error, got %v", got)
// test that the delegator can still withdraw their bonds
msgUnbondDelegator := NewMsgUnbond(delegatorAddr, validatorAddr, "10")
got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgBeginUnbondingDelegator := NewMsgBeginUnbonding(delegatorAddr, validatorAddr, sdk.NewRat(10))
msgCompleteUnbondingDelegator := NewMsgCompleteUnbonding(delegatorAddr, validatorAddr)
got = handleMsgBeginUnbonding(ctx, msgBeginUnbondingDelegator, keeper)
require.True(t, got.IsOK(), "expected no error")
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbondingDelegator, keeper)
require.True(t, got.IsOK(), "expected no error")
// verify that the pubkey can now be reused
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
assert.True(t, got.IsOK(), "expected ok, got %v", got)
}
func TestUnbondingPeriod(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr := keep.Addrs[0]
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 7
keeper.SetParams(ctx, params)
// create the validator
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// begin unbonding
msgBeginUnbonding := NewMsgBeginUnbonding(validatorAddr, validatorAddr, sdk.NewRat(10))
got = handleMsgBeginUnbonding(ctx, msgBeginUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error")
// cannot complete unbonding at same time
msgCompleteUnbonding := NewMsgCompleteUnbonding(validatorAddr, validatorAddr)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, !got.IsOK(), "expected no error")
// cannot complete unbonding at time 6 seconds later
origHeader := ctx.BlockHeader()
headerTime6 := origHeader
headerTime6.Time += 6
ctx = ctx.WithBlockHeader(headerTime6)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, !got.IsOK(), "expected no error")
// can complete unbonding at time 7 seconds later
headerTime7 := origHeader
headerTime7.Time += 7
ctx = ctx.WithBlockHeader(headerTime7)
got = handleMsgCompleteUnbonding(ctx, msgCompleteUnbonding, keeper)
require.True(t, got.IsOK(), "expected no error")
}
func TestRedelegationPeriod(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr, validatorAddr2 := keep.Addrs[0], keep.Addrs[1]
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 7
keeper.SetParams(ctx, params)
// create the validators
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// begin redelegate
msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error, %v", got)
// cannot complete redelegation at same time
msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2)
got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper)
require.True(t, !got.IsOK(), "expected an error")
// cannot complete redelegation at time 6 seconds later
origHeader := ctx.BlockHeader()
headerTime6 := origHeader
headerTime6.Time += 6
ctx = ctx.WithBlockHeader(headerTime6)
got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper)
require.True(t, !got.IsOK(), "expected an error")
// can complete redelegation at time 7 seconds later
headerTime7 := origHeader
headerTime7.Time += 7
ctx = ctx.WithBlockHeader(headerTime7)
got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error")
}
func TestTransitiveRedelegation(t *testing.T) {
ctx, _, keeper := keep.CreateTestInput(t, false, 1000)
validatorAddr, validatorAddr2, validatorAddr3 := keep.Addrs[0], keep.Addrs[1], keep.Addrs[2]
// set the unbonding time
params := keeper.GetParams(ctx)
params.UnbondingTime = 0
keeper.SetParams(ctx, params)
// create the validators
msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10)
got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10)
got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper)
require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator")
// begin redelegate
msgBeginRedelegate := NewMsgBeginRedelegate(validatorAddr, validatorAddr, validatorAddr2, sdk.NewRat(10))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error, %v", got)
// cannot redelegation to next validator while first delegation exists
msgBeginRedelegate = NewMsgBeginRedelegate(validatorAddr, validatorAddr2, validatorAddr3, sdk.NewRat(10))
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate)
// complete first redelegation
msgCompleteRedelegate := NewMsgCompleteRedelegate(validatorAddr, validatorAddr, validatorAddr2)
got = handleMsgCompleteRedelegate(ctx, msgCompleteRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error")
// now should be able to redelegate from the second validator to the third
got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper)
require.True(t, got.IsOK(), "expected no error")
}

View File

@ -1,859 +0,0 @@
package stake
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
)
// keeper of the staking store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec
coinKeeper bank.Keeper
// codespace
codespace sdk.CodespaceType
}
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
coinKeeper: ck,
codespace: codespace,
}
return keeper
}
//_________________________________________________________________________
// get a single validator
func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator Validator, found bool) {
store := ctx.KVStore(k.storeKey)
return k.getValidator(store, addr)
}
// get a single validator by pubkey
func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator Validator, found bool) {
store := ctx.KVStore(k.storeKey)
addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey))
if addr == nil {
return validator, false
}
return k.getValidator(store, addr)
}
// get a single validator (reuse store)
func (k Keeper) getValidator(store sdk.KVStore, addr sdk.Address) (validator Validator, found bool) {
b := store.Get(GetValidatorKey(addr))
if b == nil {
return validator, false
}
k.cdc.MustUnmarshalBinary(b, &validator)
return validator, true
}
// set the main record holding validator details
func (k Keeper) setValidator(ctx sdk.Context, validator Validator) {
store := ctx.KVStore(k.storeKey)
// set main store
bz := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bz)
}
func (k Keeper) setValidatorByPubKeyIndex(ctx sdk.Context, validator Validator) {
store := ctx.KVStore(k.storeKey)
// set pointer by pubkey
store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner)
}
func (k Keeper) setValidatorByPowerIndex(ctx sdk.Context, validator Validator, pool Pool) {
store := ctx.KVStore(k.storeKey)
store.Set(GetValidatorsByPowerKey(validator, pool), validator.Owner)
}
// used in testing
func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool {
store := ctx.KVStore(k.storeKey)
return store.Get(power) != nil
}
// Get the set of all validators with no limits, used during genesis dump
func (k Keeper) getAllValidators(ctx sdk.Context) (validators Validators) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
i := 0
for ; ; i++ {
if !iterator.Valid() {
iterator.Close()
break
}
bz := iterator.Value()
var validator Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
validators = append(validators, validator)
iterator.Next()
}
return validators
}
// Get the set of all validators, retrieve a maxRetrieve number of records
func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators Validators) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
validators = make([]Validator, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
iterator.Close()
break
}
bz := iterator.Value()
var validator Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
validators[i] = validator
iterator.Next()
}
return validators[:i] // trim
}
//___________________________________________________________________________
// get the group of the bonded validators
func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []Validator) {
store := ctx.KVStore(k.storeKey)
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
validators = make([]Validator, maxValidators)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey)
i := 0
for ; iterator.Valid(); iterator.Next() {
// sanity check
if i > int(maxValidators-1) {
panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated")
}
address := iterator.Value()
validator, found := k.getValidator(store, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
validators[i] = validator
i++
}
iterator.Close()
return validators[:i] // trim
}
// get the group of bonded validators sorted by power-rank
func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []Validator {
store := ctx.KVStore(k.storeKey)
maxValidators := k.GetParams(ctx).MaxValidators
validators := make([]Validator, maxValidators)
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest
i := 0
for {
if !iterator.Valid() || i > int(maxValidators-1) {
iterator.Close()
break
}
address := iterator.Value()
validator, found := k.getValidator(store, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
// Reached to revoked validators, stop iterating
if validator.Revoked {
iterator.Close()
break
}
if validator.Status() == sdk.Bonded {
validators[i] = validator
i++
}
iterator.Next()
}
return validators[:i] // trim
}
//_________________________________________________________________________
// Accumulated updates to the active/bonded validator set for tendermint
// get the most recently updated validators
func (k Keeper) getTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val abci.Validator
k.cdc.MustUnmarshalBinary(valBytes, &val)
updates = append(updates, val)
}
iterator.Close()
return
}
// remove all validator update entries after applied to Tendermint
func (k Keeper) clearTendermintUpdates(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
// delete subspace
iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey)
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key())
}
iterator.Close()
}
//___________________________________________________________________________
// perfom all the nessisary steps for when a validator changes its power
// updates all validator stores as well as tendermint update store
// may kick out validators if new validator is entering the bonded validator group
func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) Validator {
store := ctx.KVStore(k.storeKey)
pool := k.getPool(store)
ownerAddr := validator.Owner
// always update the main list ordered by owner address before exiting
defer func() {
bz := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(ownerAddr), bz)
}()
// retrieve the old validator record
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
validator = k.unbondValidator(ctx, store, validator)
// need to also clear the cliff validator spot because the revoke has
// opened up a new spot which will be filled when
// updateValidatorsBonded is called
k.clearCliffValidator(ctx)
}
powerIncreasing := false
if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) {
powerIncreasing = true
}
// if already a validator, copy the old block height and counter, else set them
if oldFound && oldValidator.Status() == sdk.Bonded {
validator.BondHeight = oldValidator.BondHeight
validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter
} else {
validator.BondHeight = ctx.BlockHeight()
counter := k.getIntraTxCounter(ctx)
validator.BondIntraTxCounter = counter
k.setIntraTxCounter(ctx, counter+1)
}
// update the list ordered by voting power
if oldFound {
store.Delete(GetValidatorsByPowerKey(oldValidator, pool))
}
valPower := GetValidatorsByPowerKey(validator, pool)
store.Set(valPower, validator.Owner)
// efficiency case:
// if already bonded and power increasing only need to update tendermint
if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded {
bz := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc))
store.Set(GetTendermintUpdatesKey(ownerAddr), bz)
return validator
}
// efficiency case:
// if was unbonded/or is a new validator - and the new power is less than the cliff validator
cliffPower := k.getCliffValidatorPower(ctx)
if cliffPower != nil &&
(!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) &&
bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower
return validator
}
// update the validator set for this validator
updatedVal := k.updateBondedValidators(ctx, store, validator)
if updatedVal.Owner != nil { // updates to validator occurred to be updated
validator = updatedVal
}
return validator
}
// Update the validator group and kick out any old validators. In addition this
// function adds (or doesn't add) a validator which has updated its bonded
// tokens to the validator group. -> this validator is specified through the
// updatedValidatorAddr term.
//
// The correct subset is retrieved by iterating through an index of the
// validators sorted by power, stored using the ValidatorsByPowerKey.
// Simultaneously the current validator records are updated in store with the
// ValidatorsBondedKey. This store is used to determine if a validator is a
// validator without needing to iterate over the subspace as we do in
// GetValidators.
//
// Optionally also return the validator from a retrieve address if the validator has been bonded
func (k Keeper) updateBondedValidators(ctx sdk.Context, store sdk.KVStore,
newValidator Validator) (updatedVal Validator) {
kickCliffValidator := false
oldCliffValidatorAddr := k.getCliffValidator(ctx)
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest
bondedValidatorsCount := 0
var validator Validator
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
// TODO benchmark if we should read the current power and not write if it's the same
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
k.setCliffValidator(ctx, validator, k.GetPool(ctx))
}
iterator.Close()
break
}
// either retrieve the original validator from the store, or under the
// situation that this is the "new validator" just use the validator
// provided because it has not yet been updated in the main validator
// store
ownerAddr := iterator.Value()
if bytes.Equal(ownerAddr, newValidator.Owner) {
validator = newValidator
} else {
var found bool
validator, found = k.getValidator(store, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
}
// if not previously a validator (and unrevoked),
// kick the cliff validator / bond this new validator
if validator.Status() != sdk.Bonded && !validator.Revoked {
kickCliffValidator = true
validator = k.bondValidator(ctx, store, validator)
if bytes.Equal(ownerAddr, newValidator.Owner) {
updatedVal = validator
}
}
if validator.Revoked && validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
} else {
bondedValidatorsCount++
}
iterator.Next()
}
// perform the actual kicks
if oldCliffValidatorAddr != nil && kickCliffValidator {
validator, found := k.getValidator(store, oldCliffValidatorAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr))
}
k.unbondValidator(ctx, store, validator)
}
return
}
// full update of the bonded validator set, many can be added/kicked
func (k Keeper) updateBondedValidatorsFull(ctx sdk.Context, store sdk.KVStore) {
// clear the current validators store, add to the ToKickOut temp store
toKickOut := make(map[string]byte)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey)
for ; iterator.Valid(); iterator.Next() {
ownerAddr := iterator.Value()
toKickOut[string(ownerAddr)] = 0 // set anything
}
iterator.Close()
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerKey) // largest to smallest
bondedValidatorsCount := 0
var validator Validator
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
k.setCliffValidator(ctx, validator, k.GetPool(ctx))
}
iterator.Close()
break
}
// either retrieve the original validator from the store,
// or under the situation that this is the "new validator" just
// use the validator provided because it has not yet been updated
// in the main validator store
ownerAddr := iterator.Value()
var found bool
validator, found = k.getValidator(store, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
_, found = toKickOut[string(ownerAddr)]
if found {
delete(toKickOut, string(ownerAddr))
} else {
// if it wasn't in the toKickOut group it means
// this wasn't a previously a validator, therefor
// update the validator to enter the validator group
validator = k.bondValidator(ctx, store, validator)
}
if validator.Revoked && validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
} else {
bondedValidatorsCount++
}
iterator.Next()
}
// perform the actual kicks
for key := range toKickOut {
ownerAddr := []byte(key)
validator, found := k.getValidator(store, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
k.unbondValidator(ctx, store, validator)
}
return
}
// perform all the store operations for when a validator status becomes unbonded
func (k Keeper) unbondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator {
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Unbonded {
panic(fmt.Sprintf("should not already be be unbonded, validator: %v\n", validator))
}
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
k.setPool(ctx, pool)
// save the now unbonded validator record
bzVal := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bzVal)
// add to accumulated changes for tendermint
bzABCI := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc))
store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI)
// also remove from the Bonded Validators Store
store.Delete(GetValidatorsBondedKey(validator.PubKey))
return validator
}
// perform all the store operations for when a validator status becomes bonded
func (k Keeper) bondValidator(ctx sdk.Context, store sdk.KVStore, validator Validator) Validator {
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("should not already be be bonded, validator: %v\n", validator))
}
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
k.setPool(ctx, pool)
// save the now bonded validator record to the three referenced stores
bzVal := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bzVal)
store.Set(GetValidatorsBondedKey(validator.PubKey), validator.Owner)
// add to accumulated changes for tendermint
bzABCI := k.cdc.MustMarshalBinary(validator.abciValidator(k.cdc))
store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI)
return validator
}
func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) {
// first retrieve the old validator record
validator, found := k.GetValidator(ctx, address)
if !found {
return
}
// delete the old validator record
store := ctx.KVStore(k.storeKey)
pool := k.getPool(store)
store.Delete(GetValidatorKey(address))
store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey))
store.Delete(GetValidatorsByPowerKey(validator, pool))
// delete from the current and power weighted validator groups if the validator
// is bonded - and add validator with zero power to the validator updates
if store.Get(GetValidatorsBondedKey(validator.PubKey)) == nil {
return
}
store.Delete(GetValidatorsBondedKey(validator.PubKey))
bz := k.cdc.MustMarshalBinary(validator.abciValidatorZero(k.cdc))
store.Set(GetTendermintUpdatesKey(address), bz)
}
//_____________________________________________________________________
// load a delegator bond
func (k Keeper) GetDelegation(ctx sdk.Context,
delegatorAddr, validatorAddr sdk.Address) (bond Delegation, found bool) {
store := ctx.KVStore(k.storeKey)
delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc))
if delegatorBytes == nil {
return bond, false
}
k.cdc.MustUnmarshalBinary(delegatorBytes, &bond)
return bond, true
}
// load all delegations used during genesis dump
func (k Keeper) getAllDelegations(ctx sdk.Context) (delegations []Delegation) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, DelegationKey)
i := 0
for ; ; i++ {
if !iterator.Valid() {
iterator.Close()
break
}
bondBytes := iterator.Value()
var delegation Delegation
k.cdc.MustUnmarshalBinary(bondBytes, &delegation)
delegations = append(delegations, delegation)
iterator.Next()
}
return delegations[:i] // trim
}
// load all bonds of a delegator
func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) {
store := ctx.KVStore(k.storeKey)
delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc)
iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest
bonds = make([]Delegation, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
iterator.Close()
break
}
bondBytes := iterator.Value()
var bond Delegation
k.cdc.MustUnmarshalBinary(bondBytes, &bond)
bonds[i] = bond
iterator.Next()
}
return bonds[:i] // trim
}
func (k Keeper) setDelegation(ctx sdk.Context, bond Delegation) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(bond)
store.Set(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc), b)
}
func (k Keeper) removeDelegation(ctx sdk.Context, bond Delegation) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegationKey(bond.DelegatorAddr, bond.ValidatorAddr, k.cdc))
}
//_______________________________________________________________________
// load/save the global staking params
func (k Keeper) GetParams(ctx sdk.Context) Params {
store := ctx.KVStore(k.storeKey)
return k.getParams(store)
}
func (k Keeper) getParams(store sdk.KVStore) (params Params) {
b := store.Get(ParamKey)
if b == nil {
panic("Stored params should not have been nil")
}
k.cdc.MustUnmarshalBinary(b, &params)
return
}
// Need a distinct function because setParams depends on an existing previous
// record of params to exist (to check if maxValidators has changed) - and we
// panic on retrieval if it doesn't exist - hence if we use setParams for the very
// first params set it will panic.
func (k Keeper) setNewParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(params)
store.Set(ParamKey, b)
}
// Public version of setNewParams
func (k Keeper) SetNewParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(params)
store.Set(ParamKey, b)
}
func (k Keeper) setParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(k.storeKey)
exParams := k.getParams(store)
// if max validator count changes, must recalculate validator set
if exParams.MaxValidators != params.MaxValidators {
k.updateBondedValidatorsFull(ctx, store)
}
b := k.cdc.MustMarshalBinary(params)
store.Set(ParamKey, b)
}
//_______________________________________________________________________
// load/save the pool
func (k Keeper) GetPool(ctx sdk.Context) (pool Pool) {
store := ctx.KVStore(k.storeKey)
return k.getPool(store)
}
func (k Keeper) getPool(store sdk.KVStore) (pool Pool) {
b := store.Get(PoolKey)
if b == nil {
panic("Stored pool should not have been nil")
}
k.cdc.MustUnmarshalBinary(b, &pool)
return
}
func (k Keeper) setPool(ctx sdk.Context, pool Pool) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(pool)
store.Set(PoolKey, b)
}
// Public version of setpool
func (k Keeper) SetPool(ctx sdk.Context, pool Pool) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(pool)
store.Set(PoolKey, b)
}
//__________________________________________________________________________
// get the current in-block validator operation counter
func (k Keeper) getIntraTxCounter(ctx sdk.Context) int16 {
store := ctx.KVStore(k.storeKey)
b := store.Get(IntraTxCounterKey)
if b == nil {
return 0
}
var counter int16
k.cdc.MustUnmarshalBinary(b, &counter)
return counter
}
// set the current in-block validator operation counter
func (k Keeper) setIntraTxCounter(ctx sdk.Context, counter int16) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(counter)
store.Set(IntraTxCounterKey, bz)
}
//__________________________________________________________________________
// get the current validator on the cliff
func (k Keeper) getCliffValidator(ctx sdk.Context) []byte {
store := ctx.KVStore(k.storeKey)
return store.Get(ValidatorCliffKey)
}
// get the current power of the validator on the cliff
func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte {
store := ctx.KVStore(k.storeKey)
return store.Get(ValidatorPowerCliffKey)
}
// set the current validator and power of the validator on the cliff
func (k Keeper) setCliffValidator(ctx sdk.Context, validator Validator, pool Pool) {
store := ctx.KVStore(k.storeKey)
bz := GetValidatorsByPowerKey(validator, pool)
store.Set(ValidatorPowerCliffKey, bz)
store.Set(ValidatorCliffKey, validator.Owner)
}
// clear the current validator and power of the validator on the cliff
func (k Keeper) clearCliffValidator(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
store.Delete(ValidatorPowerCliffKey)
store.Delete(ValidatorCliffKey)
}
//__________________________________________________________________________
// Implements ValidatorSet
var _ sdk.ValidatorSet = Keeper{}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
bz := iterator.Value()
var validator Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedKey)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
address := iterator.Value()
validator, found := k.getValidator(store, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}
// get the sdk.validator for a particular address
func (k Keeper) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator {
val, found := k.GetValidator(ctx, addr)
if !found {
return nil
}
return val
}
// total power from the bond
func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat {
pool := k.GetPool(ctx)
return pool.BondedShares
}
//__________________________________________________________________________
// Implements DelegationSet
var _ sdk.ValidatorSet = Keeper{}
// get the delegation for a particular set of delegator and validator addresses
func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation {
bond, ok := k.GetDelegation(ctx, addrDel, addrVal)
if !ok {
return nil
}
return bond
}
// Returns self as it is both a validatorset and delegationset
func (k Keeper) GetValidatorSet() sdk.ValidatorSet {
return k
}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) {
store := ctx.KVStore(k.storeKey)
key := GetDelegationsKey(delAddr, k.cdc)
iterator := sdk.KVStorePrefixIterator(store, key)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
bz := iterator.Value()
var delegation Delegation
k.cdc.MustUnmarshalBinary(bz, &delegation)
stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}
// slash a validator
func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) {
// TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("attempted to slash a nonexistent validator with address %s", pubkey.Address()))
}
sharesToRemove := val.PoolShares.Amount.Mul(fraction)
pool := k.GetPool(ctx)
val, pool, burned := val.removePoolShares(pool, sharesToRemove)
k.setPool(ctx, pool) // update the pool
k.updateValidator(ctx, val) // update the validator, possibly kicking it out
logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %v tokens", pubkey.Address(), fraction, sharesToRemove, burned))
return
}
// revoke a validator
func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("validator with pubkey %s not found, cannot revoke", pubkey))
}
val.Revoked = true
k.updateValidator(ctx, val) // update the validator, now revoked
logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address()))
return
}
// unrevoke a validator
func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
logger := ctx.Logger().With("module", "x/stake")
val, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("validator with pubkey %s not found, cannot unrevoke", pubkey))
}
val.Revoked = false
k.updateValidator(ctx, val) // update the validator, now unrevoked
logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address()))
return
}

View File

@ -0,0 +1,356 @@
package keeper
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// load a delegation
func (k Keeper) GetDelegation(ctx sdk.Context,
delegatorAddr, validatorAddr sdk.Address) (delegation types.Delegation, found bool) {
store := ctx.KVStore(k.storeKey)
delegatorBytes := store.Get(GetDelegationKey(delegatorAddr, validatorAddr, k.cdc))
if delegatorBytes == nil {
return delegation, false
}
k.cdc.MustUnmarshalBinary(delegatorBytes, &delegation)
return delegation, true
}
// load all delegations used during genesis dump
func (k Keeper) GetAllDelegations(ctx sdk.Context) (delegations []types.Delegation) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, DelegationKey)
i := 0
for ; ; i++ {
if !iterator.Valid() {
break
}
bondBytes := iterator.Value()
var delegation types.Delegation
k.cdc.MustUnmarshalBinary(bondBytes, &delegation)
delegations = append(delegations, delegation)
iterator.Next()
}
iterator.Close()
return delegations
}
// load all delegations for a delegator
func (k Keeper) GetDelegations(ctx sdk.Context, delegator sdk.Address,
maxRetrieve int16) (delegations []types.Delegation) {
store := ctx.KVStore(k.storeKey)
delegatorPrefixKey := GetDelegationsKey(delegator, k.cdc)
iterator := sdk.KVStorePrefixIterator(store, delegatorPrefixKey) //smallest to largest
delegations = make([]types.Delegation, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
break
}
bondBytes := iterator.Value()
var delegation types.Delegation
k.cdc.MustUnmarshalBinary(bondBytes, &delegation)
delegations[i] = delegation
iterator.Next()
}
iterator.Close()
return delegations[:i] // trim
}
// set the delegation
func (k Keeper) SetDelegation(ctx sdk.Context, delegation types.Delegation) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(delegation)
store.Set(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc), b)
}
// remove the delegation
func (k Keeper) RemoveDelegation(ctx sdk.Context, delegation types.Delegation) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegationKey(delegation.DelegatorAddr, delegation.ValidatorAddr, k.cdc))
}
//_____________________________________________________________________________________
// load a unbonding delegation
func (k Keeper) GetUnbondingDelegation(ctx sdk.Context,
DelegatorAddr, ValidatorAddr sdk.Address) (ubd types.UnbondingDelegation, found bool) {
store := ctx.KVStore(k.storeKey)
ubdKey := GetUBDKey(DelegatorAddr, ValidatorAddr, k.cdc)
bz := store.Get(ubdKey)
if bz == nil {
return ubd, false
}
k.cdc.MustUnmarshalBinary(bz, &ubd)
return ubd, true
}
// set the unbonding delegation and associated index
func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(ubd)
ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)
store.Set(ubdKey, bz)
store.Set(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc), ubdKey)
}
// remove the unbonding delegation object and associated index
func (k Keeper) RemoveUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) {
store := ctx.KVStore(k.storeKey)
ubdKey := GetUBDKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc)
store.Delete(ubdKey)
store.Delete(GetUBDByValIndexKey(ubd.DelegatorAddr, ubd.ValidatorAddr, k.cdc))
}
//_____________________________________________________________________________________
// load a redelegation
func (k Keeper) GetRedelegation(ctx sdk.Context,
DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr sdk.Address) (red types.Redelegation, found bool) {
store := ctx.KVStore(k.storeKey)
redKey := GetREDKey(DelegatorAddr, ValidatorSrcAddr, ValidatorDstAddr, k.cdc)
bz := store.Get(redKey)
if bz == nil {
return red, false
}
k.cdc.MustUnmarshalBinary(bz, &red)
return red, true
}
// has a redelegation
func (k Keeper) HasReceivingRedelegation(ctx sdk.Context,
DelegatorAddr, ValidatorDstAddr sdk.Address) bool {
store := ctx.KVStore(k.storeKey)
prefix := GetREDsByDelToValDstIndexKey(DelegatorAddr, ValidatorDstAddr, k.cdc)
iterator := sdk.KVStorePrefixIterator(store, prefix) //smallest to largest
found := false
if iterator.Valid() {
//record found
found = true
}
iterator.Close()
return found
}
// set a redelegation and associated index
func (k Keeper) SetRedelegation(ctx sdk.Context, red types.Redelegation) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(red)
redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)
store.Set(redKey, bz)
store.Set(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey)
store.Set(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc), redKey)
}
// remove a redelegation object and associated index
func (k Keeper) RemoveRedelegation(ctx sdk.Context, red types.Redelegation) {
store := ctx.KVStore(k.storeKey)
redKey := GetREDKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc)
store.Delete(redKey)
store.Delete(GetREDByValSrcIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc))
store.Delete(GetREDByValDstIndexKey(red.DelegatorAddr, red.ValidatorSrcAddr, red.ValidatorDstAddr, k.cdc))
}
//_____________________________________________________________________________________
// Perform a delegation, set/update everything necessary within the store
func (k Keeper) Delegate(ctx sdk.Context, delegatorAddr sdk.Address, bondAmt sdk.Coin,
validator types.Validator) (newShares sdk.Rat, err sdk.Error) {
// Get or create the delegator delegation
delegation, found := k.GetDelegation(ctx, delegatorAddr, validator.Owner)
if !found {
delegation = types.Delegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validator.Owner,
Shares: sdk.ZeroRat(),
}
}
// Account new shares, save
pool := k.GetPool(ctx)
_, _, err = k.coinKeeper.SubtractCoins(ctx, delegation.DelegatorAddr, sdk.Coins{bondAmt})
if err != nil {
return
}
validator, pool, newShares = validator.AddTokensFromDel(pool, bondAmt.Amount.Int64())
delegation.Shares = delegation.Shares.Add(newShares)
// Update delegation height
delegation.Height = ctx.BlockHeight()
k.SetPool(ctx, pool)
k.SetDelegation(ctx, delegation)
k.UpdateValidator(ctx, validator)
return
}
// unbond the the delegation return
func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address,
shares sdk.Rat) (amount int64, err sdk.Error) {
// check if delegation has any shares in it unbond
delegation, found := k.GetDelegation(ctx, delegatorAddr, validatorAddr)
if !found {
err = types.ErrNoDelegatorForAddress(k.Codespace())
return
}
// retrieve the amount to remove
if delegation.Shares.LT(shares) {
err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String())
return
}
// get validator
validator, found := k.GetValidator(ctx, validatorAddr)
if !found {
err = types.ErrNoValidatorFound(k.Codespace())
return
}
// subtract shares from delegator
delegation.Shares = delegation.Shares.Sub(shares)
// remove the delegation
if delegation.Shares.IsZero() {
// if the delegation is the owner of the validator then
// trigger a revoke validator
if bytes.Equal(delegation.DelegatorAddr, validator.Owner) && validator.Revoked == false {
validator.Revoked = true
}
k.RemoveDelegation(ctx, delegation)
} else {
// Update height
delegation.Height = ctx.BlockHeight()
k.SetDelegation(ctx, delegation)
}
// remove the coins from the validator
pool := k.GetPool(ctx)
validator, pool, amount = validator.RemoveDelShares(pool, shares)
k.SetPool(ctx, pool)
// update then remove validator if necessary
validator = k.UpdateValidator(ctx, validator)
if validator.DelegatorShares.IsZero() {
k.RemoveValidator(ctx, validator.Owner)
}
return amount, nil
}
//______________________________________________________________________________________________________
// complete unbonding an unbonding record
func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error {
returnAmount, err := k.unbond(ctx, delegatorAddr, validatorAddr, sharesAmount)
if err != nil {
return err
}
// create the unbonding delegation
params := k.GetParams(ctx)
minTime := ctx.BlockHeader().Time + params.UnbondingTime
ubd := types.UnbondingDelegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
MinTime: minTime,
Balance: sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)},
}
k.SetUnbondingDelegation(ctx, ubd)
return nil
}
// complete unbonding an unbonding record
func (k Keeper) CompleteUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.Address) sdk.Error {
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr)
if !found {
return types.ErrNoUnbondingDelegation(k.Codespace())
}
// ensure that enough time has passed
ctxTime := ctx.BlockHeader().Time
if ubd.MinTime > ctxTime {
return types.ErrNotMature(k.Codespace(), "unbonding", "unit-time", ubd.MinTime, ctxTime)
}
k.coinKeeper.AddCoins(ctx, ubd.DelegatorAddr, sdk.Coins{ubd.Balance})
k.RemoveUnbondingDelegation(ctx, ubd)
return nil
}
// complete unbonding an unbonding record
func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address, sharesAmount sdk.Rat) sdk.Error {
// check if this is a transitive redelegation
if k.HasReceivingRedelegation(ctx, delegatorAddr, validatorSrcAddr) {
return types.ErrTransitiveRedelegation(k.Codespace())
}
returnAmount, err := k.unbond(ctx, delegatorAddr, validatorSrcAddr, sharesAmount)
if err != nil {
return err
}
params := k.GetParams(ctx)
returnCoin := sdk.Coin{params.BondDenom, sdk.NewInt(returnAmount)}
dstValidator, found := k.GetValidator(ctx, validatorDstAddr)
if !found {
return types.ErrBadRedelegationDst(k.Codespace())
}
sharesCreated, err := k.Delegate(ctx, delegatorAddr, returnCoin, dstValidator)
// create the unbonding delegation
minTime := ctx.BlockHeader().Time + params.UnbondingTime
red := types.Redelegation{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
MinTime: minTime,
SharesDst: sharesCreated,
SharesSrc: sharesAmount,
}
k.SetRedelegation(ctx, red)
return nil
}
// complete unbonding an ongoing redelegation
func (k Keeper) CompleteRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAddr, validatorDstAddr sdk.Address) sdk.Error {
red, found := k.GetRedelegation(ctx, delegatorAddr, validatorSrcAddr, validatorDstAddr)
if !found {
return types.ErrNoRedelegation(k.Codespace())
}
// ensure that enough time has passed
ctxTime := ctx.BlockHeader().Time
if red.MinTime > ctxTime {
return types.ErrNotMature(k.Codespace(), "redelegation", "unit-time", red.MinTime, ctxTime)
}
k.RemoveRedelegation(ctx, red)
return nil
}

View File

@ -0,0 +1,223 @@
package keeper
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// tests GetDelegation, GetDelegations, SetDelegation, RemoveDelegation, GetDelegations
func TestDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 10)
pool := keeper.GetPool(ctx)
//construct the validators
amts := []int64{9, 8, 7}
var validators [3]types.Validator
for i, amt := range amts {
validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
}
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
validators[2] = keeper.UpdateValidator(ctx, validators[2])
// first add a validators[0] to delegate too
bond1to1 := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: sdk.NewRat(9),
}
// check the empty keeper first
_, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.False(t, found)
// set and retrieve a record
keeper.SetDelegation(ctx, bond1to1)
resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.Equal(resBond))
// modify a records, save, and retrieve
bond1to1.Shares = sdk.NewRat(99)
keeper.SetDelegation(ctx, bond1to1)
resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.Equal(resBond))
// add some more records
bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0}
bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1}
bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2}
bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3}
bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4}
keeper.SetDelegation(ctx, bond1to2)
keeper.SetDelegation(ctx, bond1to3)
keeper.SetDelegation(ctx, bond2to1)
keeper.SetDelegation(ctx, bond2to2)
keeper.SetDelegation(ctx, bond2to3)
// test all bond retrieve capabilities
resBonds := keeper.GetDelegations(ctx, addrDels[0], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond1to1.Equal(resBonds[0]))
assert.True(t, bond1to2.Equal(resBonds[1]))
assert.True(t, bond1to3.Equal(resBonds[2]))
resBonds = keeper.GetDelegations(ctx, addrDels[0], 3)
require.Equal(t, 3, len(resBonds))
resBonds = keeper.GetDelegations(ctx, addrDels[0], 2)
require.Equal(t, 2, len(resBonds))
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond2to1.Equal(resBonds[0]))
assert.True(t, bond2to2.Equal(resBonds[1]))
assert.True(t, bond2to3.Equal(resBonds[2]))
allBonds := keeper.GetAllDelegations(ctx)
require.Equal(t, 6, len(allBonds))
assert.True(t, bond1to1.Equal(allBonds[0]))
assert.True(t, bond1to2.Equal(allBonds[1]))
assert.True(t, bond1to3.Equal(allBonds[2]))
assert.True(t, bond2to1.Equal(allBonds[3]))
assert.True(t, bond2to2.Equal(allBonds[4]))
assert.True(t, bond2to3.Equal(allBonds[5]))
// delete a record
keeper.RemoveDelegation(ctx, bond2to3)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2])
assert.False(t, found)
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 2, len(resBonds))
assert.True(t, bond2to1.Equal(resBonds[0]))
assert.True(t, bond2to2.Equal(resBonds[1]))
// delete all the records from delegator 2
keeper.RemoveDelegation(ctx, bond2to1)
keeper.RemoveDelegation(ctx, bond2to2)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0])
assert.False(t, found)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1])
assert.False(t, found)
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 0, len(resBonds))
}
// tests Get/Set/Remove UnbondingDelegation
func TestUnbondingDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
ubd := types.UnbondingDelegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
CreationHeight: 0,
MinTime: 0,
Balance: sdk.NewCoin("steak", 5),
}
// set and retrieve a record
keeper.SetUnbondingDelegation(ctx, ubd)
resBond, found := keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, ubd.Equal(resBond))
// modify a records, save, and retrieve
ubd.Balance = sdk.NewCoin("steak", 21)
keeper.SetUnbondingDelegation(ctx, ubd)
resBond, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, ubd.Equal(resBond))
// delete a record
keeper.RemoveUnbondingDelegation(ctx, ubd)
_, found = keeper.GetUnbondingDelegation(ctx, addrDels[0], addrVals[0])
assert.False(t, found)
}
func TestUnbondDelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
pool.LooseTokens = 10
//create a validator and a delegator to that validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, issuedShares := validator.AddTokensFromDel(pool, 10)
require.Equal(t, int64(10), issuedShares.Evaluate())
keeper.SetPool(ctx, pool)
validator = keeper.UpdateValidator(ctx, validator)
pool = keeper.GetPool(ctx)
require.Equal(t, int64(10), pool.BondedTokens)
require.Equal(t, int64(10), validator.PoolShares.Bonded().Evaluate())
delegation := types.Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: issuedShares,
}
keeper.SetDelegation(ctx, delegation)
var err error
var amount int64
amount, err = keeper.unbond(ctx, addrDels[0], addrVals[0], sdk.NewRat(6))
require.NoError(t, err)
assert.Equal(t, int64(6), amount) // shares to be added to an unbonding delegation / redelegation
delegation, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
require.True(t, found)
validator, found = keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
pool = keeper.GetPool(ctx)
assert.Equal(t, int64(4), delegation.Shares.Evaluate())
assert.Equal(t, int64(4), validator.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(6), pool.LooseTokens, "%v", pool)
assert.Equal(t, int64(4), pool.BondedTokens)
}
// tests Get/Set/Remove/Has UnbondingDelegation
func TestRedelegation(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
rd := types.Redelegation{
DelegatorAddr: addrDels[0],
ValidatorSrcAddr: addrVals[0],
ValidatorDstAddr: addrVals[1],
CreationHeight: 0,
MinTime: 0,
SharesSrc: sdk.NewRat(5),
SharesDst: sdk.NewRat(5),
}
// test shouldn't have and redelegations
has := keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1])
assert.False(t, has)
// set and retrieve a record
keeper.SetRedelegation(ctx, rd)
resBond, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
assert.True(t, found)
assert.True(t, rd.Equal(resBond))
// check if has the redelegation
has = keeper.HasReceivingRedelegation(ctx, addrDels[0], addrVals[1])
assert.True(t, has)
// modify a records, save, and retrieve
rd.SharesSrc = sdk.NewRat(21)
rd.SharesDst = sdk.NewRat(21)
keeper.SetRedelegation(ctx, rd)
resBond, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
assert.True(t, found)
assert.True(t, rd.Equal(resBond))
// delete a record
keeper.RemoveRedelegation(ctx, rd)
_, found = keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1])
assert.False(t, found)
}

View File

@ -1,35 +1,32 @@
package stake
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
const (
hrsPerYr = 8766 // as defined by a julian year of 365.25 days
precision = 100000000000 // increased to this precision for accuracy with tests on tick_test.go
precision = 100000000000 // increased to this precision for accuracy
)
var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days
var hrsPerYrRat = sdk.NewRat(hrsPerYr)
// process provisions for an hour period
func (k Keeper) processProvisions(ctx sdk.Context) Pool {
func (k Keeper) ProcessProvisions(ctx sdk.Context) types.Pool {
pool := k.GetPool(ctx)
pool.Inflation = k.nextInflation(ctx)
pool.Inflation = k.NextInflation(ctx)
// Because the validators hold a relative bonded share (`GlobalStakeShare`), when
// more bonded tokens are added proportionally to all validators the only term
// which needs to be updated is the `BondedPool`. So for each previsions cycle:
provisions := pool.Inflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat).EvaluateInt()
provisions := pool.Inflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat).Evaluate()
// TODO add to the fees provisions
pool.LooseUnbondedTokens = pool.LooseUnbondedTokens.Add(provisions)
pool.LooseTokens += provisions
return pool
}
// get the next inflation rate for the hour
func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) {
func (k Keeper) NextInflation(ctx sdk.Context) (inflation sdk.Rat) {
params := k.GetParams(ctx)
pool := k.GetPool(ctx)
@ -40,7 +37,7 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) {
// 7% and 20%.
// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := sdk.OneRat().Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChangePerYear := sdk.OneRat().Sub(pool.BondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat)
// increase the new annual inflation for this next cycle

View File

@ -1,65 +1,64 @@
package stake
package keeper
import (
"math/rand"
"strconv"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
//changing the int in NewSource will allow you to test different, deterministic, sets of operations
var r = rand.New(rand.NewSource(6595))
func TestGetInflation(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
params := keeper.GetParams(ctx)
hrsPerYrRat := sdk.NewRat(hrsPerYr)
// Governing Mechanism:
// bondedRatio = BondedTokens / TotalSupply
// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
zero := sdk.ZeroInt()
one := sdk.OneInt()
// BondedRatio = BondedTokens / TotalSupply
// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange
tests := []struct {
name string
setBondedTokens, setLooseTokens sdk.Int
setBondedTokens, setLooseTokens int64
setInflation, expectedChange sdk.Rat
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{"test 1", zero, zero, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)},
{"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)},
// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
{"test 2", one, zero, sdk.NewRat(20, 100),
{"test 2", 1, 0, sdk.NewRat(20, 100),
sdk.OneRat().Sub(sdk.OneRat().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// 50% bonded, starting at 10% inflation and being increased
{"test 3", one, one, sdk.NewRat(10, 100),
{"test 3", 1, 1, sdk.NewRat(10, 100),
sdk.OneRat().Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)},
// test 7% minimum stop (testing with 100% bonded)
{"test 4", one, zero, sdk.NewRat(7, 100), sdk.ZeroRat()},
{"test 5", one, zero, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)},
{"test 4", 1, 0, sdk.NewRat(7, 100), sdk.ZeroRat()},
{"test 5", 1, 0, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)},
// test 20% maximum stop (testing with 0% bonded)
{"test 6", zero, zero, sdk.NewRat(20, 100), sdk.ZeroRat()},
{"test 7", zero, zero, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)},
{"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat()},
{"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)},
// perfect balance shouldn't change inflation
{"test 8", sdk.NewInt(67), sdk.NewInt(33), sdk.NewRat(15, 100), sdk.ZeroRat()},
{"test 8", 67, 33, sdk.NewRat(15, 100), sdk.ZeroRat()},
}
for _, tc := range tests {
pool.BondedTokens, pool.LooseUnbondedTokens = tc.setBondedTokens, tc.setLooseTokens
pool.BondedTokens, pool.LooseTokens = tc.setBondedTokens, tc.setLooseTokens
pool.Inflation = tc.setInflation
keeper.setPool(ctx, pool)
keeper.SetPool(ctx, pool)
inflation := keeper.nextInflation(ctx)
inflation := keeper.NextInflation(ctx)
diffInflation := inflation.Sub(tc.setInflation)
assert.True(t, diffInflation.Equal(tc.expectedChange),
@ -69,17 +68,18 @@ func TestGetInflation(t *testing.T) {
// Test that provisions are correctly added to the pool and validators each hour for 1 year
func TestProcessProvisions(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt())
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 250000000
initialUnbondedTokens int64 = 300000000
cumulativeExpProvs sdk.Int = sdk.ZeroInt()
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 2
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 250000000
initialUnbondedTokens int64 = 300000000
cumulativeExpProvs int64
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 2
)
pool.LooseTokens = initialTotalTokens
// create some validators some bonded, some unbonded
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
@ -89,7 +89,7 @@ func TestProcessProvisions(t *testing.T) {
for hr := 0; hr < 8766; hr++ {
pool := keeper.GetPool(ctx)
_, expProvisions, _ := updateProvisions(t, keeper, pool, ctx, hr)
cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions)
cumulativeExpProvs = cumulativeExpProvs + expProvisions
}
//get the pool and do the final value checks from checkFinalPoolValues
@ -100,17 +100,18 @@ func TestProcessProvisions(t *testing.T) {
// Tests that the hourly rate of change of inflation will be positive, negative, or zero, depending on bonded ratio and inflation rate
// Cycles through the whole gambit of inflation possibilities, starting at 7% inflation, up to 20%, back down to 7% (it takes ~11.4 years)
func TestHourlyInflationRateOfChange(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt())
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 150000000
initialUnbondedTokens int64 = 400000000
cumulativeExpProvs sdk.Int = sdk.ZeroInt()
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 1
initialTotalTokens int64 = 550000000
initialBondedTokens int64 = 150000000
initialUnbondedTokens int64 = 400000000
cumulativeExpProvs int64
validatorTokens = []int64{150000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 1
)
pool.LooseTokens = initialTotalTokens
// create some validators some bonded, some unbonded
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
@ -121,7 +122,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) {
pool := keeper.GetPool(ctx)
previousInflation := pool.Inflation
updatedInflation, expProvisions, pool := updateProvisions(t, keeper, pool, ctx, hr)
cumulativeExpProvs = cumulativeExpProvs.Add(expProvisions)
cumulativeExpProvs = cumulativeExpProvs + expProvisions
msg := strconv.Itoa(hr)
checkInflation(t, pool, previousInflation, updatedInflation, msg)
}
@ -133,7 +134,7 @@ func TestHourlyInflationRateOfChange(t *testing.T) {
//Test that a large unbonding will significantly lower the bonded ratio
func TestLargeUnbond(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt())
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
@ -147,32 +148,33 @@ func TestLargeUnbond(t *testing.T) {
validatorTokens = []int64{300000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000}
bondedValidators uint16 = 7
)
pool.LooseTokens = initialTotalTokens
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
pool = keeper.GetPool(ctx)
validator, found := keeper.GetValidator(ctx, addrs[0])
validator, found := keeper.GetValidator(ctx, Addrs[0])
assert.True(t, found)
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool.bondedRatio()
initialBondedRatio := pool.BondedRatio()
// validator[0] will be unbonded, bringing us from 75% bonded ratio to ~50% (unbonding 300,000,000)
pool, validator, _, _ = OpBondOrUnbond(r, pool, validator)
keeper.setPool(ctx, pool)
pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator)
keeper.SetPool(ctx, pool)
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0)
bondedShares = bondedShares.Sub(bondSharesVal0)
val0UnbondedTokens = pool.unbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate()
unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.unbondedShareExRate()))
val0UnbondedTokens = pool.UnbondedShareExRate().Mul(validator.PoolShares.Unbonded()).Evaluate()
unbondedShares = unbondedShares.Add(sdk.NewRat(val0UnbondedTokens, 1).Mul(pool.UnbondedShareExRate()))
// unbonded shares should increase
assert.True(t, unbondedShares.GT(sdk.NewRat(300000000, 1)))
// Ensure that new bonded ratio is less than old bonded ratio , because before they were increasing (i.e. 50% < 75)
assert.True(t, (pool.bondedRatio().LT(initialBondedRatio)))
assert.True(t, (pool.BondedRatio().LT(initialBondedRatio)))
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper.GetPool(ctx)
@ -181,7 +183,7 @@ func TestLargeUnbond(t *testing.T) {
//Test that a large bonding will significantly increase the bonded ratio
func TestLargeBond(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt())
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
var (
@ -193,24 +195,25 @@ func TestLargeBond(t *testing.T) {
validatorTokens = []int64{400000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 100000000, 400000000}
bondedValidators uint16 = 1
)
pool.LooseTokens = initialTotalTokens
_, keeper, pool = setupTestValidators(pool, keeper, ctx, validatorTokens, bondedValidators)
checkValidatorSetup(t, pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens)
pool = keeper.GetPool(ctx)
validator, found := keeper.GetValidator(ctx, addrs[9])
validator, found := keeper.GetValidator(ctx, Addrs[9])
assert.True(t, found)
// initialBondedRatio that we can use to compare to the new values after the unbond
initialBondedRatio := pool.bondedRatio()
initialBondedRatio := pool.BondedRatio()
params := DefaultParams()
params := types.DefaultParams()
params.MaxValidators = bondedValidators + 1 //must do this to allow for an extra validator to bond
keeper.setParams(ctx, params)
keeper.SetParams(ctx, params)
// validator[9] will be bonded, bringing us from 25% to ~50% (bonding 400,000,000 tokens)
pool, validator, _, _ = OpBondOrUnbond(r, pool, validator)
keeper.setPool(ctx, pool)
pool, validator, _, _ = types.OpBondOrUnbond(r, pool, validator)
keeper.SetPool(ctx, pool)
// process provisions after the bonding, to compare the difference in expProvisions and expInflation
_, expProvisionsAfter, pool := updateProvisions(t, keeper, pool, ctx, 0)
@ -219,7 +222,7 @@ func TestLargeBond(t *testing.T) {
// unbonded shares should decrease
assert.True(t, unbondedShares.LT(sdk.NewRat(1200000000, 1)))
// Ensure that new bonded ratio is greater than old bonded ratio (i.e. 50% > 25%)
assert.True(t, (pool.bondedRatio().GT(initialBondedRatio)))
assert.True(t, (pool.BondedRatio().GT(initialBondedRatio)))
// Final check that the pool equals initial values + provisions and adjustments we recorded
pool = keeper.GetPool(ctx)
@ -228,20 +231,19 @@ func TestLargeBond(t *testing.T) {
// Tests that inflation increases or decreases as expected when we do a random operation on 20 different validators
func TestInflationWithRandomOperations(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.ZeroInt())
params := DefaultParams()
keeper.setParams(ctx, params)
ctx, _, keeper := CreateTestInput(t, false, 0)
params := types.DefaultParams()
keeper.SetParams(ctx, params)
numValidators := 20
// start off by randomly setting up 20 validators
pool, validators := randomSetup(r, numValidators)
pool, validators := types.RandomSetup(r, numValidators)
require.Equal(t, numValidators, len(validators))
for i := 0; i < len(validators); i++ {
keeper.setValidator(ctx, validators[i])
keeper.SetValidator(ctx, validators[i])
}
keeper.setPool(ctx, pool)
keeper.SetPool(ctx, pool)
// Used to rotate validators so each random operation is applied to a different validator
validatorCounter := 0
@ -250,30 +252,30 @@ func TestInflationWithRandomOperations(t *testing.T) {
for i := 0; i < numValidators; i++ {
pool := keeper.GetPool(ctx)
// Get inflation before randomOperation, for comparison later
// Get inflation before RandomOperation, for comparison later
previousInflation := pool.Inflation
// Perform the random operation, and record how validators are modified
poolMod, validatorMod, tokens, msg := randomOperation(r)(r, pool, validators[validatorCounter])
validatorsMod := make([]Validator, len(validators))
poolMod, validatorMod, tokens, msg := types.RandomOperation(r)(r, pool, validators[validatorCounter])
validatorsMod := make([]types.Validator, len(validators))
copy(validatorsMod[:], validators[:])
require.Equal(t, numValidators, len(validators), "i %v", validatorCounter)
require.Equal(t, numValidators, len(validatorsMod), "i %v", validatorCounter)
validatorsMod[validatorCounter] = validatorMod
assertInvariants(t, msg,
types.AssertInvariants(t, msg,
pool, validators,
poolMod, validatorsMod, tokens)
// set pool and validators after the random operation
pool = poolMod
keeper.setPool(ctx, pool)
keeper.SetPool(ctx, pool)
validators = validatorsMod
// Must set inflation here manually, as opposed to most other tests in this suite, where we call keeper.processProvisions(), which updates pool.Inflation
updatedInflation := keeper.nextInflation(ctx)
updatedInflation := keeper.NextInflation(ctx)
pool.Inflation = updatedInflation
keeper.setPool(ctx, pool)
keeper.SetPool(ctx, pool)
// Ensure inflation changes as expected when random operations are applied.
checkInflation(t, pool, previousInflation, updatedInflation, msg)
@ -285,41 +287,43 @@ func TestInflationWithRandomOperations(t *testing.T) {
////////////////////////////////HELPER FUNCTIONS BELOW/////////////////////////////////////
// Final check on the global pool values for what the total tokens accumulated from each hour of provisions
func checkFinalPoolValues(t *testing.T, pool Pool, initialTotalTokens int64, cumulativeExpProvs sdk.Int) {
calculatedTotalTokens := cumulativeExpProvs.AddRaw(initialTotalTokens)
assert.Equal(t, calculatedTotalTokens.Int64(), pool.TokenSupply().Int64())
func checkFinalPoolValues(t *testing.T, pool types.Pool, initialTotalTokens, cumulativeExpProvs int64) {
calculatedTotalTokens := initialTotalTokens + cumulativeExpProvs
assert.Equal(t, calculatedTotalTokens, pool.TokenSupply())
}
// Processes provisions are added to the pool correctly every hour
// Returns expected Provisions, expected Inflation, and pool, to help with cumulative calculations back in main Tests
func updateProvisions(t *testing.T, keeper Keeper, pool Pool, ctx sdk.Context, hr int) (sdk.Rat, sdk.Int, Pool) {
expInflation := keeper.nextInflation(ctx)
expProvisions := (expInflation.Mul(sdk.NewRatFromInt(pool.TokenSupply())).Quo(hrsPerYrRat)).EvaluateInt()
func updateProvisions(t *testing.T, keeper Keeper, pool types.Pool, ctx sdk.Context, hr int) (sdk.Rat, int64, types.Pool) {
expInflation := keeper.NextInflation(ctx)
expProvisions := (expInflation.Mul(sdk.NewRat(pool.TokenSupply())).Quo(hrsPerYrRat)).Evaluate()
startTotalSupply := pool.TokenSupply()
pool = keeper.processProvisions(ctx)
keeper.setPool(ctx, pool)
pool = keeper.ProcessProvisions(ctx)
keeper.SetPool(ctx, pool)
//check provisions were added to pool
require.Equal(t, startTotalSupply.Add(expProvisions).Int64(), pool.TokenSupply().Int64())
require.Equal(t, startTotalSupply+expProvisions, pool.TokenSupply())
return expInflation, expProvisions, pool
}
// Deterministic setup of validators and pool
// Allows you to decide how many validators to setup
// Allows you to pick which validators are bonded by adjusting the MaxValidators of params
func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64, maxValidators uint16) ([]Validator, Keeper, Pool) {
params := DefaultParams()
func setupTestValidators(pool types.Pool, keeper Keeper, ctx sdk.Context, validatorTokens []int64,
maxValidators uint16) ([]types.Validator, Keeper, types.Pool) {
params := types.DefaultParams()
params.MaxValidators = maxValidators
keeper.setParams(ctx, params)
keeper.SetParams(ctx, params)
numValidators := len(validatorTokens)
validators := make([]Validator, numValidators)
validators := make([]types.Validator, numValidators)
for i := 0; i < numValidators; i++ {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i], pool, _ = validators[i].addTokensFromDel(pool, sdk.NewInt(validatorTokens[i]))
keeper.setPool(ctx, pool)
validators[i] = keeper.updateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, validatorTokens[i])
keeper.SetPool(ctx, pool)
validators[i] = keeper.UpdateValidator(ctx, validators[i]) //will kick out lower power validators. Keep this in mind when setting up the test validators order
pool = keeper.GetPool(ctx)
}
@ -327,28 +331,28 @@ func setupTestValidators(pool Pool, keeper Keeper, ctx sdk.Context, validatorTok
}
// Checks that the deterministic validator setup you wanted matches the values in the pool
func checkValidatorSetup(t *testing.T, pool Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) {
assert.Equal(t, initialTotalTokens, pool.TokenSupply().Int64())
assert.Equal(t, initialBondedTokens, pool.BondedTokens.Int64())
assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens.Int64())
func checkValidatorSetup(t *testing.T, pool types.Pool, initialTotalTokens, initialBondedTokens, initialUnbondedTokens int64) {
assert.Equal(t, initialTotalTokens, pool.TokenSupply(), "%v", pool)
assert.Equal(t, initialBondedTokens, pool.BondedTokens, "%v", pool)
assert.Equal(t, initialUnbondedTokens, pool.UnbondedTokens, "%v", pool)
// test initial bonded ratio
assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.bondedRatio())
assert.True(t, pool.BondedRatio().Equal(sdk.NewRat(initialBondedTokens, initialTotalTokens)), "%v", pool.BondedRatio())
// test the value of validator shares
assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat()), "%v", pool.bondedShareExRate())
assert.True(t, pool.BondedShareExRate().Equal(sdk.OneRat()), "%v", pool.BondedShareExRate())
}
// Checks that The inflation will correctly increase or decrease after an update to the pool
func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation sdk.Rat, msg string) {
func checkInflation(t *testing.T, pool types.Pool, previousInflation, updatedInflation sdk.Rat, msg string) {
inflationChange := updatedInflation.Sub(previousInflation)
switch {
//BELOW 67% - Rate of change positive and increasing, while we are between 7% <= and < 20% inflation
case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)):
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)):
assert.Equal(t, true, inflationChange.GT(sdk.ZeroRat()), msg)
//BELOW 67% - Rate of change should be 0 while inflation continually stays at 20% until we reach 67% bonded ratio
case pool.bondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)):
case pool.BondedRatio().LT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(20, 100)):
if previousInflation.Equal(sdk.NewRat(20, 100)) {
assert.Equal(t, true, inflationChange.IsZero(), msg)
@ -358,11 +362,11 @@ func checkInflation(t *testing.T, pool Pool, previousInflation, updatedInflation
}
//ABOVE 67% - Rate of change should be negative while the bond is above 67, and should stay negative until we reach inflation of 7%
case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)):
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.LT(sdk.NewRat(20, 100)) && updatedInflation.GT(sdk.NewRat(7, 100)):
assert.Equal(t, true, inflationChange.LT(sdk.ZeroRat()), msg)
//ABOVE 67% - Rate of change should be 0 while inflation continually stays at 7%.
case pool.bondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)):
case pool.BondedRatio().GT(sdk.NewRat(67, 100)) && updatedInflation.Equal(sdk.NewRat(7, 100)):
if previousInflation.Equal(sdk.NewRat(7, 100)) {
assert.Equal(t, true, inflationChange.IsZero(), msg)

122
x/stake/keeper/keeper.go Normal file
View File

@ -0,0 +1,122 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// keeper of the stake store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec
coinKeeper bank.Keeper
// codespace
codespace sdk.CodespaceType
}
func NewKeeper(cdc *wire.Codec, key sdk.StoreKey, ck bank.Keeper, codespace sdk.CodespaceType) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
coinKeeper: ck,
codespace: codespace,
}
return keeper
}
//_________________________________________________________________________
// return the codespace
func (k Keeper) Codespace() sdk.CodespaceType {
return k.codespace
}
//_________________________________________________________________________
// some generic reads/writes that don't need their own files
// load/save the global staking params
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
store := ctx.KVStore(k.storeKey)
b := store.Get(ParamKey)
if b == nil {
panic("Stored params should not have been nil")
}
k.cdc.MustUnmarshalBinary(b, &params)
return
}
// Need a distinct function because setParams depends on an existing previous
// record of params to exist (to check if maxValidators has changed) - and we
// panic on retrieval if it doesn't exist - hence if we use setParams for the very
// first params set it will panic.
func (k Keeper) SetNewParams(ctx sdk.Context, params types.Params) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(params)
store.Set(ParamKey, b)
}
// set the params
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
store := ctx.KVStore(k.storeKey)
exParams := k.GetParams(ctx)
// if max validator count changes, must recalculate validator set
if exParams.MaxValidators != params.MaxValidators {
k.UpdateBondedValidatorsFull(ctx)
}
b := k.cdc.MustMarshalBinary(params)
store.Set(ParamKey, b)
}
//_______________________________________________________________________
// load/save the pool
func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(PoolKey)
if b == nil {
panic("Stored pool should not have been nil")
}
k.cdc.MustUnmarshalBinary(b, &pool)
return
}
// set the pool
func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinary(pool)
store.Set(PoolKey, b)
}
//__________________________________________________________________________
// get the current in-block validator operation counter
func (k Keeper) InitIntraTxCounter(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
b := store.Get(IntraTxCounterKey)
if b == nil {
k.SetIntraTxCounter(ctx, 0)
}
}
// get the current in-block validator operation counter
func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 {
store := ctx.KVStore(k.storeKey)
b := store.Get(IntraTxCounterKey)
var counter int16
k.cdc.MustUnmarshalBinary(b, &counter)
return counter
}
// set the current in-block validator operation counter
func (k Keeper) SetIntraTxCounter(ctx sdk.Context, counter int16) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinary(counter)
store.Set(IntraTxCounterKey, bz)
}

View File

@ -0,0 +1,39 @@
package keeper
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
func TestParams(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
expParams := types.DefaultParams()
//check that the empty keeper loads the default
resParams := keeper.GetParams(ctx)
assert.True(t, expParams.Equal(resParams))
//modify a params, save, and retrieve
expParams.MaxValidators = 777
keeper.SetParams(ctx, expParams)
resParams = keeper.GetParams(ctx)
assert.True(t, expParams.Equal(resParams))
}
func TestPool(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
expPool := types.InitialPool()
//check that the empty keeper loads the default
resPool := keeper.GetPool(ctx)
assert.True(t, expPool.Equal(resPool))
//modify a params, save, and retrieve
expPool.BondedTokens = 777
keeper.SetPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
assert.True(t, expPool.Equal(resPool))
}

197
x/stake/keeper/key.go Normal file
View File

@ -0,0 +1,197 @@
package keeper
import (
"encoding/binary"
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// TODO remove some of these prefixes once have working multistore
//nolint
var (
// Keys for store prefixes
ParamKey = []byte{0x00} // key for parameters relating to staking
PoolKey = []byte{0x01} // key for the staking pools
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator index, by pubkey
ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators
ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power
ValidatorCliffIndexKey = []byte{0x06} // key for the validator index of the cliff validator
ValidatorPowerCliffKey = []byte{0x07} // key for the power of the validator on the cliff
TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated
IntraTxCounterKey = []byte{0x09} // key for intra-block tx index
DelegationKey = []byte{0x0A} // key for a delegation
UnbondingDelegationKey = []byte{0x0B} // key for an unbonding-delegation
UnbondingDelegationByValIndexKey = []byte{0x0C} // prefix for each key for an unbonding-delegation, by validator owner
RedelegationKey = []byte{0x0D} // key for a redelegation
RedelegationByValSrcIndexKey = []byte{0x0E} // prefix for each key for an redelegation, by validator owner
RedelegationByValDstIndexKey = []byte{0x0F} // prefix for each key for an redelegation, by validator owner
)
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
// get the key for the validator with address
func GetValidatorKey(ownerAddr sdk.Address) []byte {
return append(ValidatorsKey, ownerAddr.Bytes()...)
}
// get the key for the validator with pubkey
func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte {
return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...)
}
// get the key for the current validator group, ordered like tendermint
func GetValidatorsBondedIndexKey(ownerAddr sdk.Address) []byte {
return append(ValidatorsBondedIndexKey, ownerAddr.Bytes()...)
}
// get the power which is the key for the validator used in the power-store
func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) []byte {
// NOTE the address doesn't need to be stored because counter bytes must always be different
return GetValidatorPowerRank(validator, pool)
}
// get the power of a validator
func GetValidatorPowerRank(validator types.Validator, pool types.Pool) []byte {
power := validator.EquivalentBondedShares(pool)
powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
// TODO ensure that the key will be a readable string.. probably should add seperators and have
revokedBytes := make([]byte, 1)
if validator.Revoked {
revokedBytes[0] = byte(0x01)
} else {
revokedBytes[0] = byte(0x00)
}
// TODO ensure that the key will be a readable string.. probably should add seperators and have
// heightBytes and counterBytes represent strings like powerBytes does
heightBytes := make([]byte, binary.MaxVarintLen64)
binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first)
counterBytes := make([]byte, 2)
binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority)
return append(ValidatorsByPowerIndexKey,
append(revokedBytes,
append(powerBytes,
append(heightBytes, counterBytes...)...)...)...)
}
// get the key for the accumulated update validators
func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte {
return append(TendermintUpdatesKey, ownerAddr.Bytes()...)
}
//________________________________________________________________________________
// get the key for delegator bond with validator
func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...)
}
// get the prefix for a delegator for all validators
func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&delegatorAddr)
return append(DelegationKey, res...)
}
//________________________________________________________________________________
// get the key for an unbonding delegation
func GetUBDKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetUBDsKey(delegatorAddr, cdc), validatorAddr.Bytes()...)
}
// get the index-key for an unbonding delegation, stored by validator-index
func GetUBDByValIndexKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetUBDsByValIndexKey(validatorAddr, cdc), delegatorAddr.Bytes()...)
}
//______________
// get the prefix for all unbonding delegations from a delegator
func GetUBDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&delegatorAddr)
return append(UnbondingDelegationKey, res...)
}
// get the prefix keyspace for the indexs of unbonding delegations for a validator
func GetUBDsByValIndexKey(validatorAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&validatorAddr)
return append(UnbondingDelegationByValIndexKey, res...)
}
//________________________________________________________________________________
// get the key for a redelegation
func GetREDKey(delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address, cdc *wire.Codec) []byte {
return append(
GetREDsKey(delegatorAddr, cdc),
append(
validatorSrcAddr.Bytes(),
validatorDstAddr.Bytes()...)...,
)
}
// get the index-key for a redelegation, stored by source-validator-index
func GetREDByValSrcIndexKey(delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address, cdc *wire.Codec) []byte {
return append(
GetREDsFromValSrcIndexKey(validatorSrcAddr, cdc),
append(
delegatorAddr.Bytes(),
validatorDstAddr.Bytes()...)...,
)
}
// get the index-key for a redelegation, stored by destination-validator-index
func GetREDByValDstIndexKey(delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address, cdc *wire.Codec) []byte {
return append(
GetREDsToValDstIndexKey(validatorDstAddr, cdc),
append(
delegatorAddr.Bytes(),
validatorSrcAddr.Bytes()...)...,
)
}
//______________
// get the prefix keyspace for redelegations from a delegator
func GetREDsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&delegatorAddr)
return append(RedelegationKey, res...)
}
// get the prefix keyspace for all redelegations redelegating away from a source validator
func GetREDsFromValSrcIndexKey(validatorSrcAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&validatorSrcAddr)
return append(RedelegationByValSrcIndexKey, res...)
}
// get the prefix keyspace for all redelegations redelegating towards a destination validator
func GetREDsToValDstIndexKey(validatorDstAddr sdk.Address, cdc *wire.Codec) []byte {
res := cdc.MustMarshalBinary(&validatorDstAddr)
return append(RedelegationByValDstIndexKey, res...)
}
// get the prefix keyspace for all redelegations redelegating towards a destination validator
// from a particular delegator
func GetREDsByDelToValDstIndexKey(delegatorAddr sdk.Address,
validatorDstAddr sdk.Address, cdc *wire.Codec) []byte {
return append(
GetREDsToValDstIndexKey(validatorDstAddr, cdc),
delegatorAddr.Bytes()...)
}

104
x/stake/keeper/sdk_types.go Normal file
View File

@ -0,0 +1,104 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// Implements ValidatorSet
var _ sdk.ValidatorSet = Keeper{}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
bz := iterator.Value()
var validator types.Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
address := iterator.Value()
validator, found := k.GetValidator(ctx, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
stop := fn(i, validator) // XXX is this safe will the validator unexposed fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}
// get the sdk.validator for a particular address
func (k Keeper) Validator(ctx sdk.Context, address sdk.Address) sdk.Validator {
val, found := k.GetValidator(ctx, address)
if !found {
return nil
}
return val
}
// total power from the bond
func (k Keeper) TotalPower(ctx sdk.Context) sdk.Rat {
pool := k.GetPool(ctx)
return pool.BondedShares
}
//__________________________________________________________________________
// Implements DelegationSet
var _ sdk.DelegationSet = Keeper{}
// Returns self as it is both a validatorset and delegationset
func (k Keeper) GetValidatorSet() sdk.ValidatorSet {
return k
}
// get the delegation for a particular set of delegator and validator addresses
func (k Keeper) Delegation(ctx sdk.Context, addrDel sdk.Address, addrVal sdk.Address) sdk.Delegation {
bond, ok := k.GetDelegation(ctx, addrDel, addrVal)
if !ok {
return nil
}
return bond
}
// iterate through the active validator set and perform the provided function
func (k Keeper) IterateDelegations(ctx sdk.Context, delAddr sdk.Address, fn func(index int64, delegation sdk.Delegation) (stop bool)) {
store := ctx.KVStore(k.storeKey)
key := GetDelegationsKey(delAddr, k.cdc)
iterator := sdk.KVStorePrefixIterator(store, key)
i := int64(0)
for ; iterator.Valid(); iterator.Next() {
bz := iterator.Value()
var delegation types.Delegation
k.cdc.MustUnmarshalBinary(bz, &delegation)
stop := fn(i, delegation) // XXX is this safe will the fields be able to get written to?
if stop {
break
}
i++
}
iterator.Close()
}

59
x/stake/keeper/slash.go Normal file
View File

@ -0,0 +1,59 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// NOTE the current slash functionality doesn't take into consideration unbonding/rebonding records
// or the time of breach. This will be updated in slashing v2
// slash a validator
func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, fraction sdk.Rat) {
// TODO height ignored for now, see https://github.com/cosmos/cosmos-sdk/pull/1011#issuecomment-390253957
validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Attempted to slash a nonexistent validator with address %s", pubkey.Address()))
}
sharesToRemove := validator.PoolShares.Amount.Mul(fraction)
pool := k.GetPool(ctx)
validator, pool, burned := validator.RemovePoolShares(pool, sharesToRemove)
k.SetPool(ctx, pool) // update the pool
k.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out
logger := ctx.Logger().With("module", "x/stake")
logger.Info(fmt.Sprintf("Validator %s slashed by fraction %v, removed %v shares and burned %d tokens", pubkey.Address(), fraction, sharesToRemove, burned))
return
}
// revoke a validator
func (k Keeper) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot revoke", pubkey))
}
validator.Revoked = true
k.UpdateValidator(ctx, validator) // update the validator, now revoked
logger := ctx.Logger().With("module", "x/stake")
logger.Info(fmt.Sprintf("Validator %s revoked", pubkey.Address()))
return
}
// unrevoke a validator
func (k Keeper) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot unrevoke", pubkey))
}
validator.Revoked = false
k.UpdateValidator(ctx, validator) // update the validator, now unrevoked
logger := ctx.Logger().With("module", "x/stake")
logger.Info(fmt.Sprintf("Validator %s unrevoked", pubkey.Address()))
return
}

View File

@ -1,4 +1,4 @@
package stake
package keeper
import (
"bytes"
@ -18,35 +18,52 @@ import (
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// dummy addresses used for testing
var (
addrs = createTestAddrs(100)
pks = createTestPubKeys(100)
Addrs = createTestAddrs(100)
PKs = createTestPubKeys(100)
emptyAddr sdk.Address
emptyPubkey crypto.PubKey
addrDels = []sdk.Address{
Addrs[0],
Addrs[1],
}
addrVals = []sdk.Address{
Addrs[2],
Addrs[3],
Addrs[4],
Addrs[5],
Addrs[6],
}
)
//_______________________________________________________________________________________
// intended to be used with require/assert: require.True(ValEq(...))
func ValEq(t *testing.T, exp, got Validator) (*testing.T, bool, string, Validator, Validator) {
return t, exp.equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
func ValEq(t *testing.T, exp, got types.Validator) (*testing.T, bool, string, types.Validator, types.Validator) {
return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got
}
//_______________________________________________________________________________________
func makeTestCodec() *wire.Codec {
// create a codec used only for testing
func MakeTestCodec() *wire.Codec {
var cdc = wire.NewCodec()
// Register Msgs
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(bank.MsgSend{}, "test/stake/Send", nil)
cdc.RegisterConcrete(bank.MsgIssue{}, "test/stake/Issue", nil)
cdc.RegisterConcrete(MsgCreateValidator{}, "test/stake/CreateValidator", nil)
cdc.RegisterConcrete(MsgEditValidator{}, "test/stake/EditValidator", nil)
cdc.RegisterConcrete(MsgUnbond{}, "test/stake/Unbond", nil)
cdc.RegisterConcrete(types.MsgCreateValidator{}, "test/stake/CreateValidator", nil)
cdc.RegisterConcrete(types.MsgEditValidator{}, "test/stake/EditValidator", nil)
cdc.RegisterConcrete(types.MsgBeginUnbonding{}, "test/stake/BeginUnbonding", nil)
cdc.RegisterConcrete(types.MsgCompleteUnbonding{}, "test/stake/CompleteUnbonding", nil)
cdc.RegisterConcrete(types.MsgBeginRedelegate{}, "test/stake/BeginRedelegate", nil)
cdc.RegisterConcrete(types.MsgCompleteRedelegate{}, "test/stake/CompleteRedelegate", nil)
// Register AppAccount
cdc.RegisterInterface((*auth.Account)(nil), nil)
@ -56,8 +73,9 @@ func makeTestCodec() *wire.Codec {
return cdc
}
func paramsNoInflation() Params {
return Params{
// default params without inflation
func ParamsNoInflation() types.Params {
return types.Params{
InflationRateChange: sdk.ZeroRat(),
InflationMax: sdk.ZeroRat(),
InflationMin: sdk.ZeroRat(),
@ -68,7 +86,7 @@ func paramsNoInflation() Params {
}
// hogpodge of all sorts of input required for testing
func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Context, auth.AccountMapper, Keeper) {
func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context, auth.AccountMapper, Keeper) {
keyStake := sdk.NewKVStoreKey("stake")
keyAcc := sdk.NewKVStoreKey("acc")
@ -81,28 +99,32 @@ func createTestInput(t *testing.T, isCheckTx bool, initCoins sdk.Int) (sdk.Conte
require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger())
cdc := makeTestCodec()
cdc := MakeTestCodec()
accountMapper := auth.NewAccountMapper(
cdc, // amino codec
keyAcc, // target store
&auth.BaseAccount{}, // prototype
)
ck := bank.NewKeeper(accountMapper)
keeper := NewKeeper(cdc, keyStake, ck, DefaultCodespace)
keeper.setPool(ctx, InitialPool())
keeper.setNewParams(ctx, DefaultParams())
keeper := NewKeeper(cdc, keyStake, ck, types.DefaultCodespace)
keeper.SetPool(ctx, types.InitialPool())
keeper.SetNewParams(ctx, types.DefaultParams())
keeper.InitIntraTxCounter(ctx)
// fill all the addresses with some coins
for _, addr := range addrs {
// fill all the addresses with some coins, set the loose pool tokens simultaneously
for _, addr := range Addrs {
pool := keeper.GetPool(ctx)
ck.AddCoins(ctx, addr, sdk.Coins{
{keeper.GetParams(ctx).BondDenom, initCoins},
{keeper.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)},
})
pool.LooseTokens += initCoins
keeper.SetPool(ctx, pool)
}
return ctx, accountMapper, keeper
}
func newPubKey(pk string) (res crypto.PubKey) {
func NewPubKey(pk string) (res crypto.PubKey) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
panic(err)
@ -114,7 +136,7 @@ func newPubKey(pk string) (res crypto.PubKey) {
}
// for incode address generation
func testAddr(addr string, bech string) sdk.Address {
func TestAddr(addr string, bech string) sdk.Address {
res, err := sdk.GetAccAddressHex(addr)
if err != nil {
@ -151,7 +173,7 @@ func createTestAddrs(numAddrs int) []sdk.Address {
buffer.WriteString(numString) //adding on final two digits to make addresses unique
res, _ := sdk.GetAccAddressHex(buffer.String())
bech, _ := sdk.Bech32ifyAcc(res)
addresses = append(addresses, testAddr(buffer.String(), bech))
addresses = append(addresses, TestAddr(buffer.String(), bech))
buffer.Reset()
}
return addresses
@ -166,8 +188,16 @@ func createTestPubKeys(numPubKeys int) []crypto.PubKey {
numString := strconv.Itoa(i)
buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string
buffer.WriteString(numString) //adding on final two digits to make pubkeys unique
publicKeys = append(publicKeys, newPubKey(buffer.String()))
publicKeys = append(publicKeys, NewPubKey(buffer.String()))
buffer.Reset()
}
return publicKeys
}
//_____________________________________________________________________________________
// does a certain by-power index record exist
func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) bool {
store := ctx.KVStore(keeper.storeKey)
return store.Get(power) != nil
}

537
x/stake/keeper/validator.go Normal file
View File

@ -0,0 +1,537 @@
package keeper
import (
"bytes"
"fmt"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// get a single validator
func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.Address) (validator types.Validator, found bool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetValidatorKey(addr))
if b == nil {
return validator, false
}
k.cdc.MustUnmarshalBinary(b, &validator)
return validator, true
}
// get a single validator by pubkey
func (k Keeper) GetValidatorByPubKey(ctx sdk.Context, pubkey crypto.PubKey) (validator types.Validator, found bool) {
store := ctx.KVStore(k.storeKey)
addr := store.Get(GetValidatorByPubKeyIndexKey(pubkey))
if addr == nil {
return validator, false
}
return k.GetValidator(ctx, addr)
}
// set the main record holding validator details
func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
// set main store
bz := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bz)
}
// validator index
func (k Keeper) SetValidatorByPubKeyIndex(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
// set pointer by pubkey
store.Set(GetValidatorByPubKeyIndexKey(validator.PubKey), validator.Owner)
}
// validator index
func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator, pool types.Pool) {
store := ctx.KVStore(k.storeKey)
store.Set(GetValidatorsByPowerIndexKey(validator, pool), validator.Owner)
}
// validator index
func (k Keeper) SetValidatorBondedIndex(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner)
}
// used in testing
func (k Keeper) validatorByPowerIndexExists(ctx sdk.Context, power []byte) bool {
store := ctx.KVStore(k.storeKey)
return store.Get(power) != nil
}
// Get the set of all validators with no limits, used during genesis dump
func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
i := 0
for ; ; i++ {
if !iterator.Valid() {
break
}
bz := iterator.Value()
var validator types.Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
validators = append(validators, validator)
iterator.Next()
}
iterator.Close()
return validators
}
// Get the set of all validators, retrieve a maxRetrieve number of records
func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve int16) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey)
validators = make([]types.Validator, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
break
}
bz := iterator.Value()
var validator types.Validator
k.cdc.MustUnmarshalBinary(bz, &validator)
validators[i] = validator
iterator.Next()
}
iterator.Close()
return validators[:i] // trim
}
//___________________________________________________________________________
// get the group of the bonded validators
func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
validators = make([]types.Validator, maxValidators)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey)
i := 0
for ; iterator.Valid(); iterator.Next() {
// sanity check
if i > int(maxValidators-1) {
panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated")
}
address := iterator.Value()
validator, found := k.GetValidator(ctx, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
validators[i] = validator
i++
}
iterator.Close()
return validators[:i] // trim
}
// get the group of bonded validators sorted by power-rank
func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator {
store := ctx.KVStore(k.storeKey)
maxValidators := k.GetParams(ctx).MaxValidators
validators := make([]types.Validator, maxValidators)
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest
i := 0
for {
if !iterator.Valid() || i > int(maxValidators-1) {
break
}
address := iterator.Value()
validator, found := k.GetValidator(ctx, address)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", address))
}
if validator.Status() == sdk.Bonded {
validators[i] = validator
i++
}
iterator.Next()
}
iterator.Close()
return validators[:i] // trim
}
//_________________________________________________________________________
// Accumulated updates to the active/bonded validator set for tendermint
// get the most recently updated validators
func (k Keeper) GetTendermintUpdates(ctx sdk.Context) (updates []abci.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val abci.Validator
k.cdc.MustUnmarshalBinary(valBytes, &val)
updates = append(updates, val)
}
iterator.Close()
return
}
// remove all validator update entries after applied to Tendermint
func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
// delete subspace
iterator := sdk.KVStorePrefixIterator(store, TendermintUpdatesKey)
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key())
}
iterator.Close()
}
//___________________________________________________________________________
// perfom all the nessisary steps for when a validator changes its power
// updates all validator stores as well as tendermint update store
// may kick out validators if new validator is entering the bonded validator group
func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
ownerAddr := validator.Owner
// always update the main list ordered by owner address before exiting
defer func() {
bz := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(ownerAddr), bz)
}()
// retrieve the old validator record
oldValidator, oldFound := k.GetValidator(ctx, ownerAddr)
if validator.Revoked && oldValidator.Status() == sdk.Bonded {
validator = k.unbondValidator(ctx, validator)
// need to also clear the cliff validator spot because the revoke has
// opened up a new spot which will be filled when
// updateValidatorsBonded is called
k.clearCliffValidator(ctx)
}
powerIncreasing := false
if oldFound && oldValidator.PoolShares.Bonded().LT(validator.PoolShares.Bonded()) {
powerIncreasing = true
}
// if already a validator, copy the old block height and counter, else set them
if oldFound && oldValidator.Status() == sdk.Bonded {
validator.BondHeight = oldValidator.BondHeight
validator.BondIntraTxCounter = oldValidator.BondIntraTxCounter
} else {
validator.BondHeight = ctx.BlockHeight()
counter := k.GetIntraTxCounter(ctx)
validator.BondIntraTxCounter = counter
k.SetIntraTxCounter(ctx, counter+1)
}
// update the list ordered by voting power
if oldFound {
store.Delete(GetValidatorsByPowerIndexKey(oldValidator, pool))
}
valPower := GetValidatorsByPowerIndexKey(validator, pool)
store.Set(valPower, validator.Owner)
// efficiency case:
// if already bonded and power increasing only need to update tendermint
if powerIncreasing && !validator.Revoked && oldValidator.Status() == sdk.Bonded {
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc))
store.Set(GetTendermintUpdatesKey(ownerAddr), bz)
return validator
}
// efficiency case:
// if was unbonded/or is a new validator - and the new power is less than the cliff validator
cliffPower := k.getCliffValidatorPower(ctx)
if cliffPower != nil &&
(!oldFound || (oldFound && oldValidator.Status() == sdk.Unbonded)) &&
bytes.Compare(valPower, cliffPower) == -1 { //(valPower < cliffPower
return validator
}
// update the validator set for this validator
updatedVal := k.UpdateBondedValidators(ctx, validator)
if updatedVal.Owner != nil { // updates to validator occurred to be updated
validator = updatedVal
}
return validator
}
// Update the validator group and kick out any old validators. In addition this
// function adds (or doesn't add) a validator which has updated its bonded
// tokens to the validator group. -> this validator is specified through the
// updatedValidatorAddr term.
//
// The correct subset is retrieved by iterating through an index of the
// validators sorted by power, stored using the ValidatorsByPowerIndexKey.
// Simultaneously the current validator records are updated in store with the
// ValidatorsBondedIndexKey. This store is used to determine if a validator is a
// validator without needing to iterate over the subspace as we do in
// GetValidators.
//
// Optionally also return the validator from a retrieve address if the validator has been bonded
func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
newValidator types.Validator) (updatedVal types.Validator) {
store := ctx.KVStore(k.storeKey)
kickCliffValidator := false
oldCliffValidatorAddr := k.getCliffValidator(ctx)
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest
bondedValidatorsCount := 0
var validator types.Validator
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
// TODO benchmark if we should read the current power and not write if it's the same
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
k.setCliffValidator(ctx, validator, k.GetPool(ctx))
}
break
}
// either retrieve the original validator from the store, or under the
// situation that this is the "new validator" just use the validator
// provided because it has not yet been updated in the main validator
// store
ownerAddr := iterator.Value()
if bytes.Equal(ownerAddr, newValidator.Owner) {
validator = newValidator
} else {
var found bool
validator, found = k.GetValidator(ctx, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
}
// if not previously a validator (and unrevoked),
// kick the cliff validator / bond this new validator
if validator.Status() != sdk.Bonded && !validator.Revoked {
kickCliffValidator = true
validator = k.bondValidator(ctx, validator)
if bytes.Equal(ownerAddr, newValidator.Owner) {
updatedVal = validator
}
}
if validator.Revoked && validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
} else {
bondedValidatorsCount++
}
iterator.Next()
}
iterator.Close()
// perform the actual kicks
if oldCliffValidatorAddr != nil && kickCliffValidator {
validator, found := k.GetValidator(ctx, oldCliffValidatorAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr))
}
k.unbondValidator(ctx, validator)
}
return
}
// full update of the bonded validator set, many can be added/kicked
func (k Keeper) UpdateBondedValidatorsFull(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
// clear the current validators store, add to the ToKickOut temp store
toKickOut := make(map[string]byte)
iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey)
for ; iterator.Valid(); iterator.Next() {
ownerAddr := iterator.Value()
toKickOut[string(ownerAddr)] = 0 // set anything
}
iterator.Close()
// add the actual validator power sorted store
maxValidators := k.GetParams(ctx).MaxValidators
iterator = sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest
bondedValidatorsCount := 0
var validator types.Validator
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
if bondedValidatorsCount == int(maxValidators) { // is cliff validator
k.setCliffValidator(ctx, validator, k.GetPool(ctx))
}
break
}
// either retrieve the original validator from the store,
// or under the situation that this is the "new validator" just
// use the validator provided because it has not yet been updated
// in the main validator store
ownerAddr := iterator.Value()
var found bool
validator, found = k.GetValidator(ctx, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
_, found = toKickOut[string(ownerAddr)]
if found {
delete(toKickOut, string(ownerAddr))
} else {
// if it wasn't in the toKickOut group it means
// this wasn't a previously a validator, therefor
// update the validator to enter the validator group
validator = k.bondValidator(ctx, validator)
}
if validator.Revoked && validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("revoked validator cannot be bonded, address: %v\n", ownerAddr))
} else {
bondedValidatorsCount++
}
iterator.Next()
}
iterator.Close()
// perform the actual kicks
for key := range toKickOut {
ownerAddr := []byte(key)
validator, found := k.GetValidator(ctx, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
k.unbondValidator(ctx, validator)
}
return
}
// perform all the store operations for when a validator status becomes unbonded
func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Unbonded {
panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator))
}
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
k.SetPool(ctx, pool)
// save the now unbonded validator record
bzVal := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bzVal)
// add to accumulated changes for tendermint
bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc))
store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI)
// also remove from the Bonded types.Validators Store
store.Delete(GetValidatorsBondedIndexKey(validator.Owner))
return validator
}
// perform all the store operations for when a validator status becomes bonded
func (k Keeper) bondValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
// sanity check
if validator.Status() == sdk.Bonded {
panic(fmt.Sprintf("should not already be bonded, validator: %v\n", validator))
}
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Bonded)
k.SetPool(ctx, pool)
// save the now bonded validator record to the three referenced stores
bzVal := k.cdc.MustMarshalBinary(validator)
store.Set(GetValidatorKey(validator.Owner), bzVal)
store.Set(GetValidatorsBondedIndexKey(validator.Owner), validator.Owner)
// add to accumulated changes for tendermint
bzABCI := k.cdc.MustMarshalBinary(validator.ABCIValidator(k.cdc))
store.Set(GetTendermintUpdatesKey(validator.Owner), bzABCI)
return validator
}
// remove the validator record and associated indexes
func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.Address) {
// first retrieve the old validator record
validator, found := k.GetValidator(ctx, address)
if !found {
return
}
// delete the old validator record
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
store.Delete(GetValidatorKey(address))
store.Delete(GetValidatorByPubKeyIndexKey(validator.PubKey))
store.Delete(GetValidatorsByPowerIndexKey(validator, pool))
// delete from the current and power weighted validator groups if the validator
// is bonded - and add validator with zero power to the validator updates
if store.Get(GetValidatorsBondedIndexKey(validator.Owner)) == nil {
return
}
store.Delete(GetValidatorsBondedIndexKey(validator.Owner))
bz := k.cdc.MustMarshalBinary(validator.ABCIValidatorZero(k.cdc))
store.Set(GetTendermintUpdatesKey(address), bz)
}
//__________________________________________________________________________
// get the current validator on the cliff
func (k Keeper) getCliffValidator(ctx sdk.Context) []byte {
store := ctx.KVStore(k.storeKey)
return store.Get(ValidatorCliffIndexKey)
}
// get the current power of the validator on the cliff
func (k Keeper) getCliffValidatorPower(ctx sdk.Context) []byte {
store := ctx.KVStore(k.storeKey)
return store.Get(ValidatorPowerCliffKey)
}
// set the current validator and power of the validator on the cliff
func (k Keeper) setCliffValidator(ctx sdk.Context, validator types.Validator, pool types.Pool) {
store := ctx.KVStore(k.storeKey)
bz := GetValidatorsByPowerIndexKey(validator, pool)
store.Set(ValidatorPowerCliffKey, bz)
store.Set(ValidatorCliffIndexKey, validator.Owner)
}
// clear the current validator and power of the validator on the cliff
func (k Keeper) clearCliffValidator(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
store.Delete(ValidatorPowerCliffKey)
store.Delete(ValidatorCliffIndexKey)
}

View File

@ -0,0 +1,674 @@
package keeper
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSetValidator(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 10)
pool := keeper.GetPool(ctx)
// test how the validator is set from a purely unbonbed pool
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, _ = validator.AddTokensFromDel(pool, 10)
require.Equal(t, sdk.Unbonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded()))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
// after the save the validator should be bonded
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
// Check each store for being saved
resVal, found := keeper.GetValidator(ctx, addrVals[0])
assert.True(ValEq(t, validator, resVal))
resVals := keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validator, resVals[0]))
resVals = keeper.GetValidatorsByPower(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validator, resVals[0]))
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
assert.Equal(t, validator.ABCIValidator(keeper.cdc), updates[0])
}
func TestUpdateValidatorByPowerIndex(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
// create a random pool
pool.LooseTokens = 10000
pool.BondedTokens = 1234
pool.BondedShares = sdk.NewRat(124)
pool.UnbondingTokens = 13934
pool.UnbondingShares = sdk.NewRat(145)
pool.UnbondedTokens = 154
pool.UnbondedShares = sdk.NewRat(1333)
keeper.SetPool(ctx, pool)
// add a validator
validator := types.NewValidator(addrVals[0], PKs[0], types.Description{})
validator, pool, delSharesCreated := validator.AddTokensFromDel(pool, 100)
require.Equal(t, sdk.Unbonded, validator.Status())
assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate())
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validator)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool)
pool = keeper.GetPool(ctx)
power := GetValidatorsByPowerIndexKey(validator, pool)
assert.True(t, keeper.validatorByPowerIndexExists(ctx, power))
// burn half the delegator shares
validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2)))
assert.Equal(t, int64(50), burned)
keeper.SetPool(ctx, pool) // update the pool
keeper.UpdateValidator(ctx, validator) // update the validator, possibly kicking it out
assert.False(t, keeper.validatorByPowerIndexExists(ctx, power))
pool = keeper.GetPool(ctx)
validator, found = keeper.GetValidator(ctx, addrVals[0])
power = GetValidatorsByPowerIndexKey(validator, pool)
assert.True(t, keeper.validatorByPowerIndexExists(ctx, power))
}
// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator
func TestValidatorBasics(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
pool := keeper.GetPool(ctx)
//construct the validators
var validators [3]types.Validator
amts := []int64{9, 8, 7}
for i, amt := range amts {
validators[i] = types.NewValidator(addrVals[i], PKs[i], types.Description{})
validators[i].PoolShares = types.NewUnbondedShares(sdk.ZeroRat())
validators[i].AddTokensFromDel(pool, amt)
}
// check the empty keeper first
_, found := keeper.GetValidator(ctx, addrVals[0])
assert.False(t, found)
resVals := keeper.GetValidatorsBonded(ctx)
assert.Zero(t, len(resVals))
// set and retrieve a record
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resVal, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.True(ValEq(t, validators[0], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0]))
// modify a records, save, and retrieve
validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(10))
validators[0].DelegatorShares = sdk.NewRat(10)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resVal, found = keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.True(ValEq(t, validators[0], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0]))
// add other validators
validators[1] = keeper.UpdateValidator(ctx, validators[1])
validators[2] = keeper.UpdateValidator(ctx, validators[2])
resVal, found = keeper.GetValidator(ctx, addrVals[1])
require.True(t, found)
assert.True(ValEq(t, validators[1], resVal))
resVal, found = keeper.GetValidator(ctx, addrVals[2])
require.True(t, found)
assert.True(ValEq(t, validators[2], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 3, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here
assert.True(ValEq(t, validators[1], resVals[1]))
assert.True(ValEq(t, validators[2], resVals[2]))
// remove a record
keeper.RemoveValidator(ctx, validators[1].Owner)
_, found = keeper.GetValidator(ctx, addrVals[1])
assert.False(t, found)
}
// test how the validators are sorted, tests GetValidatorsByPower
func GetValidatorSortingUnmixed(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
// initialize some validators into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
var validators [5]types.Validator
for i, amt := range amts {
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i].PoolShares = types.NewBondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
keeper.UpdateValidator(ctx, validators[i])
}
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators)
assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators)
// test a basic increase in voting power
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(500))
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
// test a decrease in voting power
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300))
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
// test equal voting power, different age
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(200))
ctx = ctx.WithBlockHeight(10)
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators)
assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators)
// no change in voting power - no change in sort
ctx = ctx.WithBlockHeight(20)
keeper.UpdateValidator(ctx, validators[4])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
// change in voting power of both validators, both still in v-set, no age change
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(300))
validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(300))
keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
ctx = ctx.WithBlockHeight(30)
keeper.UpdateValidator(ctx, validators[4])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n, "%v", resValidators)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
}
func GetValidatorSortingMixed(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
// now 2 max resValidators
params := keeper.GetParams(ctx)
params.MaxValidators = 2
keeper.SetParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
var validators [5]types.Validator
for i, amt := range amts {
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[0]))
validators[1].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[1]))
validators[2].PoolShares = types.NewUnbondedShares(sdk.NewRat(amts[2]))
validators[3].PoolShares = types.NewBondedShares(sdk.NewRat(amts[3]))
validators[4].PoolShares = types.NewBondedShares(sdk.NewRat(amts[4]))
for i := range amts {
keeper.UpdateValidator(ctx, validators[i])
}
val0, found := keeper.GetValidator(ctx, Addrs[0])
require.True(t, found)
val1, found := keeper.GetValidator(ctx, Addrs[1])
require.True(t, found)
val2, found := keeper.GetValidator(ctx, Addrs[2])
require.True(t, found)
val3, found := keeper.GetValidator(ctx, Addrs[3])
require.True(t, found)
val4, found := keeper.GetValidator(ctx, Addrs[4])
require.True(t, found)
assert.Equal(t, sdk.Unbonded, val0.Status())
assert.Equal(t, sdk.Unbonded, val1.Status())
assert.Equal(t, sdk.Unbonded, val2.Status())
assert.Equal(t, sdk.Bonded, val3.Status())
assert.Equal(t, sdk.Bonded, val4.Status())
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators)
assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators)
}
// TODO separate out into multiple tests
func TestGetValidatorsEdgeCases(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
var found bool
// now 2 max resValidators
params := keeper.GetParams(ctx)
nMax := uint16(2)
params.MaxValidators = nMax
keeper.SetParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 400, 400}
var validators [4]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
validators[i] = keeper.UpdateValidator(ctx, validators[i])
}
for i := range amts {
validators[i], found = keeper.GetValidator(ctx, validators[i].Owner)
require.True(t, found)
}
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[2], resValidators[0]))
assert.True(ValEq(t, validators[3], resValidators[1]))
pool := keeper.GetPool(ctx)
validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 500)
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
// A validator which leaves the gotValidator set due to a decrease in voting power,
// then increases to the original voting power, does not get its spot back in the
// case of a tie.
// validator 3 enters bonded validator set
ctx = ctx.WithBlockHeight(40)
validators[3], found = keeper.GetValidator(ctx, validators[3].Owner)
require.True(t, found)
validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 1)
keeper.SetPool(ctx, pool)
validators[3] = keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[3], resValidators[1]))
// validator 3 kicked out temporarily
validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewRat(201))
keeper.SetPool(ctx, pool)
validators[3] = keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
// validator 4 does not get spot back
validators[3], pool, _ = validators[3].AddTokensFromDel(pool, 200)
keeper.SetPool(ctx, pool)
validators[3] = keeper.UpdateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
validator, exists := keeper.GetValidator(ctx, validators[3].Owner)
require.Equal(t, exists, true)
require.Equal(t, int64(40), validator.BondHeight)
}
func TestValidatorBondHeight(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
// now 2 max resValidators
params := keeper.GetParams(ctx)
params.MaxValidators = 2
keeper.SetParams(ctx, params)
// initialize some validators into the state
pool := keeper.GetPool(ctx)
var validators [3]types.Validator
validators[0] = types.NewValidator(Addrs[0], PKs[0], types.Description{})
validators[1] = types.NewValidator(Addrs[1], PKs[1], types.Description{})
validators[2] = types.NewValidator(Addrs[2], PKs[2], types.Description{})
validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 200)
validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 100)
validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 100)
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
////////////////////////////////////////
// If two validators both increase to the same voting power in the same block,
// the one with the first transaction should become bonded
validators[1] = keeper.UpdateValidator(ctx, validators[1])
validators[2] = keeper.UpdateValidator(ctx, validators[2])
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, uint16(len(resValidators)), params.MaxValidators)
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[1], resValidators[1]))
validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 50)
validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 50)
keeper.SetPool(ctx, pool)
validators[2] = keeper.UpdateValidator(ctx, validators[2])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, params.MaxValidators, uint16(len(resValidators)))
validators[1] = keeper.UpdateValidator(ctx, validators[1])
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
}
func TestFullValidatorSetPowerChange(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
params := keeper.GetParams(ctx)
max := 2
params.MaxValidators = uint16(2)
keeper.SetParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 400, 400, 200}
var validators [5]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validators[i])
}
for i := range amts {
var found bool
validators[i], found = keeper.GetValidator(ctx, validators[i].Owner)
require.True(t, found)
}
assert.Equal(t, sdk.Unbonded, validators[0].Status())
assert.Equal(t, sdk.Unbonded, validators[1].Status())
assert.Equal(t, sdk.Bonded, validators[2].Status())
assert.Equal(t, sdk.Bonded, validators[3].Status())
assert.Equal(t, sdk.Unbonded, validators[4].Status())
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, max, len(resValidators))
assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs
assert.True(ValEq(t, validators[3], resValidators[1]))
// test a swap in voting power
pool := keeper.GetPool(ctx)
validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 600)
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, max, len(resValidators))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
}
// clear the tracked changes to the gotValidator set
func TestClearTendermintUpdates(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{100, 400, 200}
validators := make([]types.Validator, len(amts))
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
keeper.UpdateValidator(ctx, validators[i])
}
updates := keeper.GetTendermintUpdates(ctx)
assert.Equal(t, len(amts), len(updates))
keeper.ClearTendermintUpdates(ctx)
updates = keeper.GetTendermintUpdates(ctx)
assert.Equal(t, 0, len(updates))
}
func TestGetTendermintUpdatesAllNone(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{10, 20}
var validators [2]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
// test from nothing to something
// tendermintUpdate set: {} -> {c1, c3}
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0])
assert.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1])
// test from something to nothing
// tendermintUpdate set: {} -> {c1, c2, c3, c4}
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
keeper.RemoveValidator(ctx, validators[0].Owner)
keeper.RemoveValidator(ctx, validators[1].Owner)
updates = keeper.GetTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey)
assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey)
assert.Equal(t, int64(0), updates[0].Power)
assert.Equal(t, int64(0), updates[1].Power)
}
func TestGetTendermintUpdatesIdentical(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{10, 20}
var validators [2]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
// test identical,
// tendermintUpdate set: {} -> {}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
}
func TestGetTendermintUpdatesSingleValueChange(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{10, 20}
var validators [2]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
// test single value change
// tendermintUpdate set: {} -> {c1'}
validators[0].PoolShares = types.NewBondedShares(sdk.NewRat(600))
validators[0] = keeper.UpdateValidator(ctx, validators[0])
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
assert.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0])
}
func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{10, 20}
var validators [2]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
// test multiple value change
// tendermintUpdate set: {c1, c3} -> {c1', c3'}
pool := keeper.GetPool(ctx)
validators[0], pool, _ = validators[0].AddTokensFromDel(pool, 190)
validators[1], pool, _ = validators[1].AddTokensFromDel(pool, 80)
keeper.SetPool(ctx, pool)
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
require.Equal(t, validators[0].ABCIValidator(keeper.cdc), updates[0])
require.Equal(t, validators[1].ABCIValidator(keeper.cdc), updates[1])
}
func TestGetTendermintUpdatesInserted(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
amts := []int64{10, 20, 5, 15, 25}
var validators [5]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
// test validtor added at the beginning
// tendermintUpdate set: {} -> {c0}
validators[2] = keeper.UpdateValidator(ctx, validators[2])
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[0])
// test validtor added at the beginning
// tendermintUpdate set: {} -> {c0}
keeper.ClearTendermintUpdates(ctx)
validators[3] = keeper.UpdateValidator(ctx, validators[3])
updates = keeper.GetTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[3].ABCIValidator(keeper.cdc), updates[0])
// test validtor added at the end
// tendermintUpdate set: {} -> {c0}
keeper.ClearTendermintUpdates(ctx)
validators[4] = keeper.UpdateValidator(ctx, validators[4])
updates = keeper.GetTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[4].ABCIValidator(keeper.cdc), updates[0])
}
func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) {
ctx, _, keeper := CreateTestInput(t, false, 1000)
params := types.DefaultParams()
params.MaxValidators = 2
keeper.SetParams(ctx, params)
amts := []int64{10, 20, 5}
var validators [5]types.Validator
for i, amt := range amts {
pool := keeper.GetPool(ctx)
validators[i] = types.NewValidator(Addrs[i], PKs[i], types.Description{})
validators[i], pool, _ = validators[i].AddTokensFromDel(pool, amt)
keeper.SetPool(ctx, pool)
}
validators[0] = keeper.UpdateValidator(ctx, validators[0])
validators[1] = keeper.UpdateValidator(ctx, validators[1])
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
// test validator added at the end but not inserted in the valset
// tendermintUpdate set: {} -> {}
keeper.UpdateValidator(ctx, validators[2])
updates := keeper.GetTendermintUpdates(ctx)
require.Equal(t, 0, len(updates))
// test validator change its power and become a gotValidator (pushing out an existing)
// tendermintUpdate set: {} -> {c0, c4}
keeper.ClearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.GetTendermintUpdates(ctx)))
pool := keeper.GetPool(ctx)
validators[2], pool, _ = validators[2].AddTokensFromDel(pool, 10)
keeper.SetPool(ctx, pool)
validators[2] = keeper.UpdateValidator(ctx, validators[2])
updates = keeper.GetTendermintUpdates(ctx)
require.Equal(t, 2, len(updates), "%v", updates)
require.Equal(t, validators[0].ABCIValidatorZero(keeper.cdc), updates[0])
require.Equal(t, validators[2].ABCIValidator(keeper.cdc), updates[1])
}

View File

@ -1,89 +0,0 @@
package stake
import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
crypto "github.com/tendermint/go-crypto"
)
// TODO remove some of these prefixes once have working multistore
//nolint
var (
// Keys for store prefixes
ParamKey = []byte{0x00} // key for parameters relating to staking
PoolKey = []byte{0x01} // key for the staking pools
ValidatorsKey = []byte{0x02} // prefix for each key to a validator
ValidatorsByPubKeyIndexKey = []byte{0x03} // prefix for each key to a validator by pubkey
ValidatorsBondedKey = []byte{0x04} // prefix for each key to bonded/actively validating validators
ValidatorsByPowerKey = []byte{0x05} // prefix for each key to a validator sorted by power
ValidatorCliffKey = []byte{0x06} // key for block-local tx index
ValidatorPowerCliffKey = []byte{0x07} // key for block-local tx index
TendermintUpdatesKey = []byte{0x08} // prefix for each key to a validator which is being updated
DelegationKey = []byte{0x09} // prefix for each key to a delegator's bond
IntraTxCounterKey = []byte{0x10} // key for block-local tx index
)
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
// get the key for the validator with address
func GetValidatorKey(ownerAddr sdk.Address) []byte {
return append(ValidatorsKey, ownerAddr.Bytes()...)
}
// get the key for the validator with pubkey
func GetValidatorByPubKeyIndexKey(pubkey crypto.PubKey) []byte {
return append(ValidatorsByPubKeyIndexKey, pubkey.Bytes()...)
}
// get the key for the current validator group, ordered like tendermint
func GetValidatorsBondedKey(pk crypto.PubKey) []byte {
addr := pk.Address()
return append(ValidatorsBondedKey, addr.Bytes()...)
}
// get the key for the validator used in the power-store
func GetValidatorsByPowerKey(validator Validator, pool Pool) []byte {
power := validator.EquivalentBondedShares(pool)
powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) // power big-endian (more powerful validators first)
// TODO ensure that the key will be a readable string.. probably should add seperators and have
revokedBytes := make([]byte, 1)
if validator.Revoked {
revokedBytes[0] = byte(0x01)
} else {
revokedBytes[0] = byte(0x00)
}
// heightBytes and counterBytes represent strings like powerBytes does
heightBytes := make([]byte, binary.MaxVarintLen64)
binary.BigEndian.PutUint64(heightBytes, ^uint64(validator.BondHeight)) // invert height (older validators first)
counterBytes := make([]byte, 2)
binary.BigEndian.PutUint16(counterBytes, ^uint16(validator.BondIntraTxCounter)) // invert counter (first txns have priority)
return append(ValidatorsByPowerKey,
append(revokedBytes,
append(powerBytes,
append(heightBytes,
append(counterBytes, validator.Owner.Bytes()...)...)...)...)...) // TODO don't technically need to store owner
}
// get the key for the accumulated update validators
func GetTendermintUpdatesKey(ownerAddr sdk.Address) []byte {
return append(TendermintUpdatesKey, ownerAddr.Bytes()...)
}
// get the key for delegator bond with validator
func GetDelegationKey(delegatorAddr, validatorAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegationsKey(delegatorAddr, cdc), validatorAddr.Bytes()...)
}
// get the prefix for a delegator for all validators
func GetDelegationsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalBinary(&delegatorAddr)
if err != nil {
panic(err)
}
return append(DelegationKey, res...)
}

View File

@ -1,792 +0,0 @@
package stake
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
addrDels = []sdk.Address{
addrs[0],
addrs[1],
}
addrVals = []sdk.Address{
addrs[2],
addrs[3],
addrs[4],
addrs[5],
addrs[6],
}
)
func TestUpdateValidatorByPowerIndex(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
// create a random pool
pool.BondedTokens = sdk.NewInt(1234)
pool.BondedShares = sdk.NewRat(124)
pool.UnbondingTokens = sdk.NewInt(13934)
pool.UnbondingShares = sdk.NewRat(145)
pool.UnbondedTokens = sdk.NewInt(154)
pool.UnbondedShares = sdk.NewRat(1333)
keeper.setPool(ctx, pool)
// add a validator
validator := NewValidator(addrVals[0], pks[0], Description{})
validator, pool, delSharesCreated := validator.addTokensFromDel(pool, sdk.NewInt(100))
require.Equal(t, sdk.Unbonded, validator.Status())
assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate())
keeper.setPool(ctx, pool)
keeper.updateValidator(ctx, validator)
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.Equal(t, int64(100), validator.PoolShares.Tokens(pool).Evaluate(), "\nvalidator %v\npool %v", validator, pool)
pool = keeper.GetPool(ctx)
power := GetValidatorsByPowerKey(validator, pool)
assert.True(t, keeper.validatorByPowerIndexExists(ctx, power))
// burn half the delegator shares
validator, pool, burned := validator.removeDelShares(pool, delSharesCreated.Quo(sdk.NewRat(2)))
assert.Equal(t, int64(50), burned.Int64())
keeper.setPool(ctx, pool) // update the pool
keeper.updateValidator(ctx, validator) // update the validator, possibly kicking it out
assert.False(t, keeper.validatorByPowerIndexExists(ctx, power))
pool = keeper.GetPool(ctx)
validator, found = keeper.GetValidator(ctx, addrVals[0])
power = GetValidatorsByPowerKey(validator, pool)
assert.True(t, keeper.validatorByPowerIndexExists(ctx, power))
}
func TestSetValidator(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
// test how the validator is set from a purely unbonbed pool
validator := NewValidator(addrVals[0], pks[0], Description{})
validator, pool, _ = validator.addTokensFromDel(pool, sdk.NewInt(10))
require.Equal(t, sdk.Unbonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Unbonded()))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
keeper.setPool(ctx, pool)
keeper.updateValidator(ctx, validator)
// after the save the validator should be bonded
validator, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
require.Equal(t, sdk.Bonded, validator.Status())
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.PoolShares.Bonded()))
assert.True(sdk.RatEq(t, sdk.NewRat(10), validator.DelegatorShares))
// Check each store for being saved
resVal, found := keeper.GetValidator(ctx, addrVals[0])
assert.True(ValEq(t, validator, resVal))
resVals := keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validator, resVals[0]))
resVals = keeper.GetValidatorsByPower(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validator, resVals[0]))
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
assert.Equal(t, validator.abciValidator(keeper.cdc), updates[0])
}
// This function tests updateValidator, GetValidator, GetValidatorsBonded, removeValidator
func TestValidatorBasics(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
//construct the validators
var validators [3]Validator
amts := []int64{9, 8, 7}
for i, amt := range amts {
validators[i] = NewValidator(addrVals[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.ZeroRat())
validators[i].addTokensFromDel(pool, sdk.NewInt(amt))
}
// check the empty keeper first
_, found := keeper.GetValidator(ctx, addrVals[0])
assert.False(t, found)
resVals := keeper.GetValidatorsBonded(ctx)
assert.Zero(t, len(resVals))
// set and retrieve a record
validators[0] = keeper.updateValidator(ctx, validators[0])
resVal, found := keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.True(ValEq(t, validators[0], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0]))
// modify a records, save, and retrieve
validators[0].PoolShares = NewBondedShares(sdk.NewRat(10))
validators[0].DelegatorShares = sdk.NewRat(10)
validators[0] = keeper.updateValidator(ctx, validators[0])
resVal, found = keeper.GetValidator(ctx, addrVals[0])
require.True(t, found)
assert.True(ValEq(t, validators[0], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 1, len(resVals))
assert.True(ValEq(t, validators[0], resVals[0]))
// add other validators
validators[1] = keeper.updateValidator(ctx, validators[1])
validators[2] = keeper.updateValidator(ctx, validators[2])
resVal, found = keeper.GetValidator(ctx, addrVals[1])
require.True(t, found)
assert.True(ValEq(t, validators[1], resVal))
resVal, found = keeper.GetValidator(ctx, addrVals[2])
require.True(t, found)
assert.True(ValEq(t, validators[2], resVal))
resVals = keeper.GetValidatorsBonded(ctx)
require.Equal(t, 3, len(resVals))
assert.True(ValEq(t, validators[0], resVals[2])) // order doesn't matter here
assert.True(ValEq(t, validators[1], resVals[1]))
assert.True(ValEq(t, validators[2], resVals[0]))
// remove a record
keeper.removeValidator(ctx, validators[1].Owner)
_, found = keeper.GetValidator(ctx, addrVals[1])
assert.False(t, found)
}
// test how the validators are sorted, tests GetValidatorsByPower
func GetValidatorSortingUnmixed(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
// initialize some validators into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
var validators [5]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewBondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
keeper.updateValidator(ctx, validators[i])
}
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators)
assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators)
// test a basic increase in voting power
validators[3].PoolShares = NewBondedShares(sdk.NewRat(500))
keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
// test a decrease in voting power
validators[3].PoolShares = NewBondedShares(sdk.NewRat(300))
keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
// test equal voting power, different age
validators[3].PoolShares = NewBondedShares(sdk.NewRat(200))
ctx = ctx.WithBlockHeight(10)
keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
assert.Equal(t, int64(0), resValidators[0].BondHeight, "%v", resValidators)
assert.Equal(t, int64(0), resValidators[1].BondHeight, "%v", resValidators)
// no change in voting power - no change in sort
ctx = ctx.WithBlockHeight(20)
keeper.updateValidator(ctx, validators[4])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
// change in voting power of both validators, both still in v-set, no age change
validators[3].PoolShares = NewBondedShares(sdk.NewRat(300))
validators[4].PoolShares = NewBondedShares(sdk.NewRat(300))
keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n)
ctx = ctx.WithBlockHeight(30)
keeper.updateValidator(ctx, validators[4])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, len(resValidators), n, "%v", resValidators)
assert.True(ValEq(t, validators[3], resValidators[0]))
assert.True(ValEq(t, validators[4], resValidators[1]))
}
func GetValidatorSortingMixed(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
// now 2 max resValidators
params := keeper.GetParams(ctx)
params.MaxValidators = 2
keeper.setParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
var validators [5]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(amts[0]))
validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(amts[1]))
validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(amts[2]))
validators[3].PoolShares = NewBondedShares(sdk.NewRat(amts[3]))
validators[4].PoolShares = NewBondedShares(sdk.NewRat(amts[4]))
for i := range amts {
keeper.updateValidator(ctx, validators[i])
}
val0, found := keeper.GetValidator(ctx, addrs[0])
require.True(t, found)
val1, found := keeper.GetValidator(ctx, addrs[1])
require.True(t, found)
val2, found := keeper.GetValidator(ctx, addrs[2])
require.True(t, found)
val3, found := keeper.GetValidator(ctx, addrs[3])
require.True(t, found)
val4, found := keeper.GetValidator(ctx, addrs[4])
require.True(t, found)
assert.Equal(t, sdk.Unbonded, val0.Status())
assert.Equal(t, sdk.Unbonded, val1.Status())
assert.Equal(t, sdk.Unbonded, val2.Status())
assert.Equal(t, sdk.Bonded, val3.Status())
assert.Equal(t, sdk.Bonded, val4.Status())
// first make sure everything made it in to the gotValidator group
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, n, len(resValidators))
assert.Equal(t, sdk.NewRat(400), resValidators[0].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(200), resValidators[1].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(100), resValidators[2].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(1), resValidators[3].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, sdk.NewRat(0), resValidators[4].PoolShares.Bonded(), "%v", resValidators)
assert.Equal(t, validators[3].Owner, resValidators[0].Owner, "%v", resValidators)
assert.Equal(t, validators[4].Owner, resValidators[1].Owner, "%v", resValidators)
assert.Equal(t, validators[1].Owner, resValidators[2].Owner, "%v", resValidators)
assert.Equal(t, validators[2].Owner, resValidators[3].Owner, "%v", resValidators)
assert.Equal(t, validators[0].Owner, resValidators[4].Owner, "%v", resValidators)
}
// TODO separate out into multiple tests
func TestGetValidatorsEdgeCases(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
var found bool
// now 2 max resValidators
params := keeper.GetParams(ctx)
nMax := uint16(2)
params.MaxValidators = nMax
keeper.setParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 400, 400}
var validators [4]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
validators[i] = keeper.updateValidator(ctx, validators[i])
}
for i := range amts {
validators[i], found = keeper.GetValidator(ctx, validators[i].Owner)
require.True(t, found)
}
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[2], resValidators[0]))
assert.True(ValEq(t, validators[3], resValidators[1]))
validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(500))
validators[0] = keeper.updateValidator(ctx, validators[0])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
// A validator which leaves the gotValidator set due to a decrease in voting power,
// then increases to the original voting power, does not get its spot back in the
// case of a tie.
// validator 3 enters bonded validator set
ctx = ctx.WithBlockHeight(40)
validators[3], found = keeper.GetValidator(ctx, validators[3].Owner)
require.True(t, found)
validators[3].PoolShares = NewUnbondedShares(sdk.NewRat(401))
validators[3] = keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[3], resValidators[1]))
// validator 3 kicked out temporarily
validators[3].PoolShares = NewBondedShares(sdk.NewRat(200))
validators[3] = keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
// validator 4 does not get spot back
validators[3].PoolShares = NewBondedShares(sdk.NewRat(400))
validators[3] = keeper.updateValidator(ctx, validators[3])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, nMax, uint16(len(resValidators)))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
validator, exists := keeper.GetValidator(ctx, validators[3].Owner)
require.Equal(t, exists, true)
require.Equal(t, int64(40), validator.BondHeight)
}
func TestValidatorBondHeight(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
// now 2 max resValidators
params := keeper.GetParams(ctx)
params.MaxValidators = 2
keeper.setParams(ctx, params)
// initialize some validators into the state
var validators [3]Validator
validators[0] = NewValidator(addrs[0], pks[0], Description{})
validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(200))
validators[0].DelegatorShares = sdk.NewRat(200)
validators[1] = NewValidator(addrs[1], pks[1], Description{})
validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(100))
validators[1].DelegatorShares = sdk.NewRat(100)
validators[2] = NewValidator(addrs[2], pks[2], Description{})
validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(100))
validators[2].DelegatorShares = sdk.NewRat(100)
validators[0] = keeper.updateValidator(ctx, validators[0])
////////////////////////////////////////
// If two validators both increase to the same voting power in the same block,
// the one with the first transaction should become bonded
validators[1] = keeper.updateValidator(ctx, validators[1])
validators[2] = keeper.updateValidator(ctx, validators[2])
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, uint16(len(resValidators)), params.MaxValidators)
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[1], resValidators[1]))
validators[1].PoolShares = NewUnbondedShares(sdk.NewRat(150))
validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(150))
validators[2] = keeper.updateValidator(ctx, validators[2])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, params.MaxValidators, uint16(len(resValidators)))
validators[1] = keeper.updateValidator(ctx, validators[1])
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
}
func TestFullValidatorSetPowerChange(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
params := keeper.GetParams(ctx)
max := 2
params.MaxValidators = uint16(2)
keeper.setParams(ctx, params)
// initialize some validators into the state
amts := []int64{0, 100, 400, 400, 200}
var validators [5]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
keeper.updateValidator(ctx, validators[i])
}
for i := range amts {
var found bool
validators[i], found = keeper.GetValidator(ctx, validators[i].Owner)
require.True(t, found)
}
assert.Equal(t, sdk.Unbonded, validators[0].Status())
assert.Equal(t, sdk.Unbonded, validators[1].Status())
assert.Equal(t, sdk.Bonded, validators[2].Status())
assert.Equal(t, sdk.Bonded, validators[3].Status())
assert.Equal(t, sdk.Unbonded, validators[4].Status())
resValidators := keeper.GetValidatorsByPower(ctx)
require.Equal(t, max, len(resValidators))
assert.True(ValEq(t, validators[2], resValidators[0])) // in the order of txs
assert.True(ValEq(t, validators[3], resValidators[1]))
// test a swap in voting power
validators[0].PoolShares = NewUnbondedShares(sdk.NewRat(600))
validators[0] = keeper.updateValidator(ctx, validators[0])
resValidators = keeper.GetValidatorsByPower(ctx)
require.Equal(t, max, len(resValidators))
assert.True(ValEq(t, validators[0], resValidators[0]))
assert.True(ValEq(t, validators[2], resValidators[1]))
}
// clear the tracked changes to the gotValidator set
func TestClearTendermintUpdates(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{100, 400, 200}
validators := make([]Validator, len(amts))
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
keeper.updateValidator(ctx, validators[i])
}
updates := keeper.getTendermintUpdates(ctx)
assert.Equal(t, len(amts), len(updates))
keeper.clearTendermintUpdates(ctx)
updates = keeper.getTendermintUpdates(ctx)
assert.Equal(t, 0, len(updates))
}
func TestGetTendermintUpdatesAllNone(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{10, 20}
var validators [2]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
// test from nothing to something
// tendermintUpdate set: {} -> {c1, c3}
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0])
assert.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1])
// test from something to nothing
// tendermintUpdate set: {} -> {c1, c2, c3, c4}
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
keeper.removeValidator(ctx, validators[0].Owner)
keeper.removeValidator(ctx, validators[1].Owner)
updates = keeper.getTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
assert.Equal(t, tmtypes.TM2PB.PubKey(validators[0].PubKey), updates[0].PubKey)
assert.Equal(t, tmtypes.TM2PB.PubKey(validators[1].PubKey), updates[1].PubKey)
assert.Equal(t, int64(0), updates[0].Power)
assert.Equal(t, int64(0), updates[1].Power)
}
func TestGetTendermintUpdatesIdentical(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{10, 20}
var validators [2]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
// test identical,
// tendermintUpdate set: {} -> {}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
}
func TestGetTendermintUpdatesSingleValueChange(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{10, 20}
var validators [2]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
// test single value change
// tendermintUpdate set: {} -> {c1'}
validators[0].PoolShares = NewBondedShares(sdk.NewRat(600))
validators[0] = keeper.updateValidator(ctx, validators[0])
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
assert.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0])
}
func TestGetTendermintUpdatesMultipleValueChange(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{10, 20}
var validators [2]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
// test multiple value change
// tendermintUpdate set: {c1, c3} -> {c1', c3'}
validators[0].PoolShares = NewBondedShares(sdk.NewRat(200))
validators[1].PoolShares = NewBondedShares(sdk.NewRat(100))
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 2, len(updates))
require.Equal(t, validators[0].abciValidator(keeper.cdc), updates[0])
require.Equal(t, validators[1].abciValidator(keeper.cdc), updates[1])
}
func TestGetTendermintUpdatesInserted(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
amts := []int64{10, 20, 5, 15, 25}
var validators [5]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
// test validtor added at the beginning
// tendermintUpdate set: {} -> {c0}
validators[2] = keeper.updateValidator(ctx, validators[2])
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[0])
// test validtor added at the beginning
// tendermintUpdate set: {} -> {c0}
keeper.clearTendermintUpdates(ctx)
validators[3] = keeper.updateValidator(ctx, validators[3])
updates = keeper.getTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[3].abciValidator(keeper.cdc), updates[0])
// test validtor added at the end
// tendermintUpdate set: {} -> {c0}
keeper.clearTendermintUpdates(ctx)
validators[4] = keeper.updateValidator(ctx, validators[4])
updates = keeper.getTendermintUpdates(ctx)
require.Equal(t, 1, len(updates))
require.Equal(t, validators[4].abciValidator(keeper.cdc), updates[0])
}
func TestGetTendermintUpdatesNotValidatorCliff(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
params := DefaultParams()
params.MaxValidators = 2
keeper.setParams(ctx, params)
amts := []int64{10, 20, 5}
var validators [5]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrs[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
validators[0] = keeper.updateValidator(ctx, validators[0])
validators[1] = keeper.updateValidator(ctx, validators[1])
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
// test validator added at the end but not inserted in the valset
// tendermintUpdate set: {} -> {}
keeper.updateValidator(ctx, validators[2])
updates := keeper.getTendermintUpdates(ctx)
require.Equal(t, 0, len(updates))
// test validator change its power and become a gotValidator (pushing out an existing)
// tendermintUpdate set: {} -> {c0, c4}
keeper.clearTendermintUpdates(ctx)
assert.Equal(t, 0, len(keeper.getTendermintUpdates(ctx)))
validators[2].PoolShares = NewUnbondedShares(sdk.NewRat(15))
validators[2] = keeper.updateValidator(ctx, validators[2])
updates = keeper.getTendermintUpdates(ctx)
require.Equal(t, 2, len(updates), "%v", updates)
require.Equal(t, validators[0].abciValidatorZero(keeper.cdc), updates[0])
require.Equal(t, validators[2].abciValidator(keeper.cdc), updates[1])
}
// tests GetDelegation, GetDelegations, SetDelegation, removeDelegation, GetBonds
func TestBond(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
//construct the validators
amts := []int64{9, 8, 7}
var validators [3]Validator
for i, amt := range amts {
validators[i] = NewValidator(addrVals[i], pks[i], Description{})
validators[i].PoolShares = NewUnbondedShares(sdk.NewRat(amt))
validators[i].DelegatorShares = sdk.NewRat(amt)
}
// first add a validators[0] to delegate too
validators[0] = keeper.updateValidator(ctx, validators[0])
bond1to1 := Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: sdk.NewRat(9),
}
// check the empty keeper first
_, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.False(t, found)
// set and retrieve a record
keeper.setDelegation(ctx, bond1to1)
resBond, found := keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.equal(resBond))
// modify a records, save, and retrieve
bond1to1.Shares = sdk.NewRat(99)
keeper.setDelegation(ctx, bond1to1)
resBond, found = keeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.equal(resBond))
// add some more records
validators[1] = keeper.updateValidator(ctx, validators[1])
validators[2] = keeper.updateValidator(ctx, validators[2])
bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0}
bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1}
bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2}
bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3}
bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4}
keeper.setDelegation(ctx, bond1to2)
keeper.setDelegation(ctx, bond1to3)
keeper.setDelegation(ctx, bond2to1)
keeper.setDelegation(ctx, bond2to2)
keeper.setDelegation(ctx, bond2to3)
// test all bond retrieve capabilities
resBonds := keeper.GetDelegations(ctx, addrDels[0], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond1to1.equal(resBonds[0]))
assert.True(t, bond1to2.equal(resBonds[1]))
assert.True(t, bond1to3.equal(resBonds[2]))
resBonds = keeper.GetDelegations(ctx, addrDels[0], 3)
require.Equal(t, 3, len(resBonds))
resBonds = keeper.GetDelegations(ctx, addrDels[0], 2)
require.Equal(t, 2, len(resBonds))
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond2to1.equal(resBonds[0]))
assert.True(t, bond2to2.equal(resBonds[1]))
assert.True(t, bond2to3.equal(resBonds[2]))
allBonds := keeper.getAllDelegations(ctx)
require.Equal(t, 6, len(allBonds))
assert.True(t, bond1to1.equal(allBonds[0]))
assert.True(t, bond1to2.equal(allBonds[1]))
assert.True(t, bond1to3.equal(allBonds[2]))
assert.True(t, bond2to1.equal(allBonds[3]))
assert.True(t, bond2to2.equal(allBonds[4]))
assert.True(t, bond2to3.equal(allBonds[5]))
// delete a record
keeper.removeDelegation(ctx, bond2to3)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[2])
assert.False(t, found)
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 2, len(resBonds))
assert.True(t, bond2to1.equal(resBonds[0]))
assert.True(t, bond2to2.equal(resBonds[1]))
// delete all the records from delegator 2
keeper.removeDelegation(ctx, bond2to1)
keeper.removeDelegation(ctx, bond2to2)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[0])
assert.False(t, found)
_, found = keeper.GetDelegation(ctx, addrDels[1], addrVals[1])
assert.False(t, found)
resBonds = keeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 0, len(resBonds))
}
func TestParams(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
expParams := DefaultParams()
//check that the empty keeper loads the default
resParams := keeper.GetParams(ctx)
assert.True(t, expParams.equal(resParams))
//modify a params, save, and retrieve
expParams.MaxValidators = 777
keeper.setParams(ctx, expParams)
resParams = keeper.GetParams(ctx)
assert.True(t, expParams.equal(resParams))
}
func TestPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
expPool := InitialPool()
//check that the empty keeper loads the default
resPool := keeper.GetPool(ctx)
assert.True(t, expPool.equal(resPool))
//modify a params, save, and retrieve
expPool.BondedTokens = sdk.NewInt(777)
keeper.setPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
assert.True(t, expPool.equal(resPool))
}

View File

@ -1,243 +0,0 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// name to idetify transaction types
const MsgType = "stake"
// XXX remove: think it makes more sense belonging with the Params so we can
// initialize at genesis - to allow for the same tests we should should make
// the ValidateBasic() function a return from an initializable function
// ValidateBasic(bondDenom string) function
const StakingToken = "steak"
//Verify interface at compile time
var _, _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}, &MsgUnbond{}
//______________________________________________________________________
// MsgCreateValidator - struct for unbonding transactions
type MsgCreateValidator struct {
Description
ValidatorAddr sdk.Address `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey,
bond sdk.Coin, description Description) MsgCreateValidator {
return MsgCreateValidator{
Description: description,
ValidatorAddr: validatorAddr,
PubKey: pubkey,
Bond: bond,
}
}
//nolint
func (msg MsgCreateValidator) Type() string { return MsgType }
func (msg MsgCreateValidator) GetSigners() []sdk.Address {
return []sdk.Address{msg.ValidatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgCreateValidator) GetSignBytes() []byte {
b, err := msgCdc.MarshalJSON(struct {
Description
ValidatorAddr string `json:"address"`
PubKey string `json:"pubkey"`
Bond sdk.Coin `json:"bond"`
}{
Description: msg.Description,
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
PubKey: sdk.MustBech32ifyValPub(msg.PubKey),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgCreateValidator) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrValidatorEmpty(DefaultCodespace)
}
if msg.Bond.Denom != StakingToken {
return ErrBadBondingDenom(DefaultCodespace)
}
if msg.Bond.Amount.Sign() != 1 {
return ErrBadBondingAmount(DefaultCodespace)
}
empty := Description{}
if msg.Description == empty {
return newError(DefaultCodespace, CodeInvalidInput, "description must be included")
}
return nil
}
//______________________________________________________________________
// MsgEditValidator - struct for editing a validator
type MsgEditValidator struct {
Description
ValidatorAddr sdk.Address `json:"address"`
}
func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator {
return MsgEditValidator{
Description: description,
ValidatorAddr: validatorAddr,
}
}
//nolint
func (msg MsgEditValidator) Type() string { return MsgType }
func (msg MsgEditValidator) GetSigners() []sdk.Address {
return []sdk.Address{msg.ValidatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgEditValidator) GetSignBytes() []byte {
b, err := msgCdc.MarshalJSON(struct {
Description
ValidatorAddr string `json:"address"`
}{
Description: msg.Description,
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgEditValidator) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrValidatorEmpty(DefaultCodespace)
}
empty := Description{}
if msg.Description == empty {
return newError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify")
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgDelegate struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: bond,
}
}
//nolint
func (msg MsgDelegate) Type() string { return MsgType }
func (msg MsgDelegate) GetSigners() []sdk.Address {
return []sdk.Address{msg.DelegatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgDelegate) GetSignBytes() []byte {
b, err := msgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorAddr string `json:"validator_addr"`
Bond sdk.Coin `json:"bond"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
Bond: msg.Bond,
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrBadValidatorAddr(DefaultCodespace)
}
if msg.Bond.Denom != StakingToken {
return ErrBadBondingDenom(DefaultCodespace)
}
if msg.Bond.Amount.Sign() != 1 {
return ErrBadBondingAmount(DefaultCodespace)
}
return nil
}
//______________________________________________________________________
// MsgUnbond - struct for unbonding transactions
type MsgUnbond struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
Shares string `json:"shares"`
}
func NewMsgUnbond(delegatorAddr, validatorAddr sdk.Address, shares string) MsgUnbond {
return MsgUnbond{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Shares: shares,
}
}
//nolint
func (msg MsgUnbond) Type() string { return MsgType }
func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
// get the bytes for the message signer to sign on
func (msg MsgUnbond) GetSignBytes() []byte {
b, err := msgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorAddr string `json:"validator_addr"`
Shares string `json:"shares"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
Shares: msg.Shares,
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgUnbond) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrBadValidatorAddr(DefaultCodespace)
}
if msg.Shares != "MAX" {
rat, err := sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return ErrBadShares(DefaultCodespace)
}
if rat.IsZero() || rat.LT(sdk.ZeroRat()) {
return ErrBadShares(DefaultCodespace)
}
}
return nil
}

View File

@ -1,156 +0,0 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
var (
coinPos = sdk.NewCoin("steak", 1000)
coinZero = sdk.NewCoin("steak", 0)
coinNeg = sdk.NewCoin("steak", -10000)
coinPosNotAtoms = sdk.NewCoin("foo", 10000)
coinZeroNotAtoms = sdk.NewCoin("foo", 0)
coinNegNotAtoms = sdk.NewCoin("foo", -10000)
)
// test ValidateBasic for MsgCreateValidator
func TestMsgCreateValidator(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
validatorAddr sdk.Address
pubkey crypto.PubKey
bond sdk.Coin
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true},
{"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true},
{"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false},
{"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false},
{"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true},
{"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false},
{"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false},
{"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false},
{"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgEditValidator
func TestMsgEditValidator(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
validatorAddr sdk.Address
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addrs[0], true},
{"partial description", "", "", "c", "", addrs[0], true},
{"empty description", "", "", "", "", addrs[0], false},
{"empty address", "a", "b", "c", "d", emptyAddr, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgEditValidator(tc.validatorAddr, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgDelegate
func TestMsgDelegate(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorAddr sdk.Address
bond sdk.Coin
expectPass bool
}{
{"basic good", addrs[0], addrs[1], coinPos, true},
{"self bond", addrs[0], addrs[0], coinPos, true},
{"empty delegator", emptyAddr, addrs[0], coinPos, false},
{"empty validator", addrs[0], emptyAddr, coinPos, false},
{"empty bond", addrs[0], addrs[1], coinZero, false},
{"negative bond", addrs[0], addrs[1], coinNeg, false},
{"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false},
}
for _, tc := range tests {
msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgUnbond(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorAddr sdk.Address
shares string
expectPass bool
}{
{"max unbond", addrs[0], addrs[1], "MAX", true},
{"decimal unbond", addrs[0], addrs[1], "0.1", true},
{"negative decimal unbond", addrs[0], addrs[1], "-0.1", false},
{"zero unbond", addrs[0], addrs[1], "0.0", false},
{"invalid decimal", addrs[0], addrs[0], "sunny", false},
{"empty delegator", emptyAddr, addrs[0], "0.1", false},
{"empty validator", addrs[0], emptyAddr, "0.1", false},
}
for _, tc := range tests {
msg := NewMsgUnbond(tc.delegatorAddr, tc.validatorAddr, tc.shares)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// TODO introduce with go-amino
//func TestSerializeMsg(t *testing.T) {
//// make sure all types construct properly
//bondAmt := 1234321
//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)}
//tests := []struct {
//tx sdk.Msg
//}{
//{NewMsgCreateValidator(addrs[0], pks[0], bond, Description{})},
//{NewMsgEditValidator(addrs[0], Description{})},
//{NewMsgDelegate(addrs[0], addrs[1], bond)},
//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))},
//}
//for i, tc := range tests {
//var tx sdk.Tx
//bs := wire.BinaryBytes(tc.tx)
//err := wire.ReadBinaryBytes(bs, &tx)
//if assert.NoError(t, err, "%d", i) {
//assert.Equal(t, tc.tx, tx, "%d", i)
//}
//}
//}

View File

@ -1,133 +0,0 @@
package stake
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Pool - dynamic parameters of the current state
type Pool struct {
LooseUnbondedTokens sdk.Int `json:"loose_unbonded_tokens"` // tokens not associated with any validator
UnbondedTokens sdk.Int `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators
UnbondingTokens sdk.Int `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool
BondedTokens sdk.Int `json:"bonded_tokens"` // reserve of bonded tokens
UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time
Inflation sdk.Rat `json:"inflation"` // current annual inflation rate
DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily)
// Fee Related
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations
}
func (p Pool) equal(p2 Pool) bool {
bz1 := msgCdc.MustMarshalBinary(&p)
bz2 := msgCdc.MustMarshalBinary(&p2)
return bytes.Equal(bz1, bz2)
}
// initial pool for testing
func InitialPool() Pool {
return Pool{
LooseUnbondedTokens: sdk.ZeroInt(),
BondedTokens: sdk.ZeroInt(),
UnbondingTokens: sdk.ZeroInt(),
UnbondedTokens: sdk.ZeroInt(),
BondedShares: sdk.ZeroRat(),
UnbondingShares: sdk.ZeroRat(),
UnbondedShares: sdk.ZeroRat(),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
DateLastCommissionReset: 0,
PrevBondedShares: sdk.ZeroRat(),
}
}
//____________________________________________________________________
// Sum total of all staking tokens in the pool
func (p Pool) TokenSupply() sdk.Int {
return p.LooseUnbondedTokens.Add(p.UnbondedTokens).Add(p.UnbondingTokens).Add(p.BondedTokens)
}
//____________________________________________________________________
// get the bond ratio of the global state
func (p Pool) bondedRatio() sdk.Rat {
if p.TokenSupply().Sign() == 1 {
return sdk.NewRatFromInt(p.BondedTokens, p.TokenSupply())
}
return sdk.ZeroRat()
}
// get the exchange rate of bonded token per issued share
func (p Pool) bondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRatFromInt(p.BondedTokens).Quo(p.BondedShares)
}
// get the exchange rate of unbonding tokens held in validators per issued share
func (p Pool) unbondingShareExRate() sdk.Rat {
if p.UnbondingShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRatFromInt(p.UnbondingTokens).Quo(p.UnbondingShares)
}
// get the exchange rate of unbonded tokens held in validators per issued share
func (p Pool) unbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRatFromInt(p.UnbondedTokens).Quo(p.UnbondedShares)
}
//_______________________________________________________________________
func (p Pool) addTokensUnbonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondedShareExRate()) // tokens * (shares/tokens)
p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount)
p.UnbondedTokens = p.UnbondedTokens.Add(amount)
return p, NewUnbondedShares(issuedSharesAmount)
}
func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) {
removedTokens = sdk.NewIntFromBigInt(p.unbondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares
p.UnbondedShares = p.UnbondedShares.Sub(shares)
p.UnbondedTokens = p.UnbondedTokens.Sub(removedTokens)
return p, removedTokens
}
func (p Pool) addTokensUnbonding(amount sdk.Int) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.unbondingShareExRate()) // tokens * (shares/tokens)
p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount)
p.UnbondingTokens = p.UnbondingTokens.Add(amount)
return p, NewUnbondingShares(issuedSharesAmount)
}
func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) {
removedTokens = sdk.NewIntFromBigInt(p.unbondingShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares
p.UnbondingShares = p.UnbondingShares.Sub(shares)
p.UnbondingTokens = p.UnbondingTokens.Sub(removedTokens)
return p, removedTokens
}
func (p Pool) addTokensBonded(amount sdk.Int) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRatFromInt(amount).Quo(p.bondedShareExRate()) // tokens * (shares/tokens)
p.BondedShares = p.BondedShares.Add(issuedSharesAmount)
p.BondedTokens = p.BondedTokens.Add(amount)
return p, NewBondedShares(issuedSharesAmount)
}
func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens sdk.Int) {
removedTokens = sdk.NewIntFromBigInt(p.bondedShareExRate().Mul(shares).EvaluateBig()) // (tokens/shares) * shares
p.BondedShares = p.BondedShares.Sub(shares)
p.BondedTokens = p.BondedTokens.Sub(removedTokens)
return p, removedTokens
}

View File

@ -1,131 +0,0 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestBondedRatio(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
pool.LooseUnbondedTokens = sdk.NewInt(1)
pool.BondedTokens = sdk.NewInt(2)
// bonded pool / total supply
require.Equal(t, pool.bondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3)))
// avoids divide-by-zero
pool.LooseUnbondedTokens = sdk.NewInt(0)
pool.BondedTokens = sdk.NewInt(0)
require.Equal(t, pool.bondedRatio(), sdk.ZeroRat())
}
func TestBondedShareExRate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
pool.BondedTokens = sdk.NewInt(3)
pool.BondedShares = sdk.NewRat(10)
// bonded pool / bonded shares
require.Equal(t, pool.bondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.BondedShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.bondedShareExRate(), sdk.OneRat())
}
func TestUnbondingShareExRate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
pool.UnbondingTokens = sdk.NewInt(3)
pool.UnbondingShares = sdk.NewRat(10)
// unbonding pool / unbonding shares
require.Equal(t, pool.unbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondingShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.unbondingShareExRate(), sdk.OneRat())
}
func TestUnbondedShareExRate(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
pool.UnbondedTokens = sdk.NewInt(3)
pool.UnbondedShares = sdk.NewRat(10)
// unbonded pool / unbonded shares
require.Equal(t, pool.unbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondedShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.unbondedShareExRate(), sdk.OneRat())
}
func TestAddTokensBonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensBonded(sdk.NewInt(10))
assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount))
assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Add(sdk.NewInt(10)))
// same number of bonded shares / tokens when exchange rate is one
assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens)))
}
func TestRemoveSharesBonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10))
assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens.Sub(tokensB))
// same number of bonded shares / tokens when exchange rate is one
assert.True(t, poolB.BondedShares.Equal(sdk.NewRatFromInt(poolB.BondedTokens)))
}
func TestAddTokensUnbonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensUnbonded(sdk.NewInt(10))
assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and unbonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount))
assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Add(sdk.NewInt(10)))
// same number of unbonded shares / tokens when exchange rate is one
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens)))
}
func TestRemoveSharesUnbonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
poolA := keeper.GetPool(ctx)
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10))
assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and bonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens.Sub(tokensB))
// same number of unbonded shares / tokens when exchange rate is one
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRatFromInt(poolB.UnbondedTokens)))
}

145
x/stake/stake.go Normal file
View File

@ -0,0 +1,145 @@
// nolint
package stake
import (
"github.com/cosmos/cosmos-sdk/x/stake/keeper"
"github.com/cosmos/cosmos-sdk/x/stake/tags"
"github.com/cosmos/cosmos-sdk/x/stake/types"
)
// keeper
type Keeper = keeper.Keeper
var NewKeeper = keeper.NewKeeper
// types
type Validator = types.Validator
type Description = types.Description
type Delegation = types.Delegation
type UnbondingDelegation = types.UnbondingDelegation
type Redelegation = types.Redelegation
type Params = types.Params
type Pool = types.Pool
type PoolShares = types.PoolShares
type MsgCreateValidator = types.MsgCreateValidator
type MsgEditValidator = types.MsgEditValidator
type MsgDelegate = types.MsgDelegate
type MsgBeginUnbonding = types.MsgBeginUnbonding
type MsgCompleteUnbonding = types.MsgCompleteUnbonding
type MsgBeginRedelegate = types.MsgBeginRedelegate
type MsgCompleteRedelegate = types.MsgCompleteRedelegate
type GenesisState = types.GenesisState
var (
GetValidatorKey = keeper.GetValidatorKey
GetValidatorByPubKeyIndexKey = keeper.GetValidatorByPubKeyIndexKey
GetValidatorsBondedIndexKey = keeper.GetValidatorsBondedIndexKey
GetValidatorsByPowerIndexKey = keeper.GetValidatorsByPowerIndexKey
GetTendermintUpdatesKey = keeper.GetTendermintUpdatesKey
GetDelegationKey = keeper.GetDelegationKey
GetDelegationsKey = keeper.GetDelegationsKey
ParamKey = keeper.ParamKey
PoolKey = keeper.PoolKey
ValidatorsKey = keeper.ValidatorsKey
ValidatorsByPubKeyIndexKey = keeper.ValidatorsByPubKeyIndexKey
ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey
ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey
ValidatorCliffIndexKey = keeper.ValidatorCliffIndexKey
ValidatorPowerCliffKey = keeper.ValidatorPowerCliffKey
TendermintUpdatesKey = keeper.TendermintUpdatesKey
DelegationKey = keeper.DelegationKey
IntraTxCounterKey = keeper.IntraTxCounterKey
GetUBDKey = keeper.GetUBDKey
GetUBDByValIndexKey = keeper.GetUBDByValIndexKey
GetUBDsKey = keeper.GetUBDsKey
GetUBDsByValIndexKey = keeper.GetUBDsByValIndexKey
GetREDKey = keeper.GetREDKey
GetREDByValSrcIndexKey = keeper.GetREDByValSrcIndexKey
GetREDByValDstIndexKey = keeper.GetREDByValDstIndexKey
GetREDsKey = keeper.GetREDsKey
GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey
GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey
GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey
DefaultParams = types.DefaultParams
InitialPool = types.InitialPool
NewUnbondedShares = types.NewUnbondedShares
NewUnbondingShares = types.NewUnbondingShares
NewBondedShares = types.NewBondedShares
NewValidator = types.NewValidator
NewDescription = types.NewDescription
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
RegisterWire = types.RegisterWire
// messages
NewMsgCreateValidator = types.NewMsgCreateValidator
NewMsgEditValidator = types.NewMsgEditValidator
NewMsgDelegate = types.NewMsgDelegate
NewMsgBeginUnbonding = types.NewMsgBeginUnbonding
NewMsgCompleteUnbonding = types.NewMsgCompleteUnbonding
NewMsgBeginRedelegate = types.NewMsgBeginRedelegate
NewMsgCompleteRedelegate = types.NewMsgCompleteRedelegate
)
// errors
const (
DefaultCodespace = types.DefaultCodespace
CodeInvalidValidator = types.CodeInvalidValidator
CodeInvalidDelegation = types.CodeInvalidDelegation
CodeInvalidInput = types.CodeInvalidInput
CodeValidatorJailed = types.CodeValidatorJailed
CodeUnauthorized = types.CodeUnauthorized
CodeInternal = types.CodeInternal
CodeUnknownRequest = types.CodeUnknownRequest
)
var (
ErrNilValidatorAddr = types.ErrNilValidatorAddr
ErrNoValidatorFound = types.ErrNoValidatorFound
ErrValidatorAlreadyExists = types.ErrValidatorAlreadyExists
ErrValidatorRevoked = types.ErrValidatorRevoked
ErrBadRemoveValidator = types.ErrBadRemoveValidator
ErrDescriptionLength = types.ErrDescriptionLength
ErrCommissionNegative = types.ErrCommissionNegative
ErrCommissionHuge = types.ErrCommissionHuge
ErrNilDelegatorAddr = types.ErrNilDelegatorAddr
ErrBadDenom = types.ErrBadDenom
ErrBadDelegationAmount = types.ErrBadDelegationAmount
ErrNoDelegation = types.ErrNoDelegation
ErrBadDelegatorAddr = types.ErrBadDelegatorAddr
ErrNoDelegatorForAddress = types.ErrNoDelegatorForAddress
ErrInsufficientShares = types.ErrInsufficientShares
ErrDelegationValidatorEmpty = types.ErrDelegationValidatorEmpty
ErrNotEnoughDelegationShares = types.ErrNotEnoughDelegationShares
ErrBadSharesAmount = types.ErrBadSharesAmount
ErrBadSharesPercent = types.ErrBadSharesPercent
ErrNotMature = types.ErrNotMature
ErrNoUnbondingDelegation = types.ErrNoUnbondingDelegation
ErrNoRedelegation = types.ErrNoRedelegation
ErrBadRedelegationDst = types.ErrBadRedelegationDst
ErrBothShareMsgsGiven = types.ErrBothShareMsgsGiven
ErrNeitherShareMsgsGiven = types.ErrNeitherShareMsgsGiven
ErrMissingSignature = types.ErrMissingSignature
)
// tags
var (
ActionCreateValidator = tags.ActionCreateValidator
ActionEditValidator = tags.ActionEditValidator
ActionDelegate = tags.ActionDelegate
ActionBeginUnbonding = tags.ActionBeginUnbonding
ActionCompleteUnbonding = tags.ActionCompleteUnbonding
ActionBeginRedelegation = tags.ActionBeginRedelegation
ActionCompleteRedelegation = tags.ActionCompleteRedelegation
TagAction = tags.Action
TagSrcValidator = tags.SrcValidator
TagDstValidator = tags.DstValidator
TagDelegator = tags.Delegator
TagMoniker = tags.Moniker
TagIdentity = tags.Identity
)

23
x/stake/tags/tags.go Normal file
View File

@ -0,0 +1,23 @@
// nolint
package tags
import (
"github.com/cosmos/cosmos-sdk/types"
)
var (
ActionCreateValidator = []byte("create-validator")
ActionEditValidator = []byte("edit-validator")
ActionDelegate = []byte("delegate")
ActionBeginUnbonding = []byte("begin-unbonding")
ActionCompleteUnbonding = []byte("complete-unbonding")
ActionBeginRedelegation = []byte("begin-redelegation")
ActionCompleteRedelegation = []byte("complete-redelegation")
Action = types.TagAction
SrcValidator = types.TagSrcValidator
DstValidator = types.TagDstValidator
Delegator = types.TagDelegator
Moniker = "moniker"
Identity = "Identity"
)

140
x/stake/types/delegation.go Normal file
View File

@ -0,0 +1,140 @@
package types
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Delegation represents the bond with tokens held by an account. It is
// owned by one delegator, and is associated with the voting power of one
// pubKey.
type Delegation struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
Shares sdk.Rat `json:"shares"`
Height int64 `json:"height"` // Last height bond updated
}
// two are equal
func (d Delegation) Equal(d2 Delegation) bool {
return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) &&
bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) &&
d.Height == d2.Height &&
d.Shares.Equal(d2.Shares)
}
// ensure fulfills the sdk validator types
var _ sdk.Delegation = Delegation{}
// nolint - for sdk.Delegation
func (d Delegation) GetDelegator() sdk.Address { return d.DelegatorAddr }
func (d Delegation) GetValidator() sdk.Address { return d.ValidatorAddr }
func (d Delegation) GetBondShares() sdk.Rat { return d.Shares }
//Human Friendly pretty printer
func (d Delegation) HumanReadableString() (string, error) {
bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr)
if err != nil {
return "", err
}
bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr)
if err != nil {
return "", err
}
resp := "Delegation \n"
resp += fmt.Sprintf("Delegator: %s\n", bechAcc)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Shares: %s", d.Shares.String())
resp += fmt.Sprintf("Height: %d", d.Height)
return resp, nil
}
//__________________________________________________________________
// element stored to represent the passive unbonding queue
type UnbondingDelegation struct {
DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator
ValidatorAddr sdk.Address `json:"validator_addr"` // validator unbonding from owner addr
CreationHeight int64 `json:"creation_height"` // height which the unbonding took place
MinTime int64 `json:"min_time"` // unix time for unbonding completion
Balance sdk.Coin `json:"balance"` // atoms to receive at completion
}
// nolint
func (d UnbondingDelegation) Equal(d2 UnbondingDelegation) bool {
bz1 := MsgCdc.MustMarshalBinary(&d)
bz2 := MsgCdc.MustMarshalBinary(&d2)
return bytes.Equal(bz1, bz2)
}
//Human Friendly pretty printer
func (d UnbondingDelegation) HumanReadableString() (string, error) {
bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr)
if err != nil {
return "", err
}
bechVal, err := sdk.Bech32ifyAcc(d.ValidatorAddr)
if err != nil {
return "", err
}
resp := "Unbonding Delegation \n"
resp += fmt.Sprintf("Delegator: %s\n", bechAcc)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime)
resp += fmt.Sprintf("Expected balance: %s", d.Balance.String())
return resp, nil
}
//__________________________________________________________________
// element stored to represent the passive redelegation queue
type Redelegation struct {
DelegatorAddr sdk.Address `json:"delegator_addr"` // delegator
ValidatorSrcAddr sdk.Address `json:"validator_src_addr"` // validator redelegation source owner addr
ValidatorDstAddr sdk.Address `json:"validator_dst_addr"` // validator redelegation destination owner addr
CreationHeight int64 `json:"creation_height"` // height which the redelegation took place
MinTime int64 `json:"min_time"` // unix time for redelegation completion
SharesSrc sdk.Rat `json:"shares` // amount of source shares redelegating
SharesDst sdk.Rat `json:"shares` // amount of destination shares redelegating
}
// nolint
func (d Redelegation) Equal(d2 Redelegation) bool {
bz1 := MsgCdc.MustMarshalBinary(&d)
bz2 := MsgCdc.MustMarshalBinary(&d2)
return bytes.Equal(bz1, bz2)
}
//Human Friendly pretty printer
func (d Redelegation) HumanReadableString() (string, error) {
bechAcc, err := sdk.Bech32ifyAcc(d.DelegatorAddr)
if err != nil {
return "", err
}
bechValSrc, err := sdk.Bech32ifyAcc(d.ValidatorSrcAddr)
if err != nil {
return "", err
}
bechValDst, err := sdk.Bech32ifyAcc(d.ValidatorDstAddr)
if err != nil {
return "", err
}
resp := "Redelegation \n"
resp += fmt.Sprintf("Delegator: %s\n", bechAcc)
resp += fmt.Sprintf("Source Validator: %s\n", bechValSrc)
resp += fmt.Sprintf("Destination Validator: %s\n", bechValDst)
resp += fmt.Sprintf("Creation height: %v\n", d.CreationHeight)
resp += fmt.Sprintf("Min time to unbond (unix): %v\n", d.MinTime)
resp += fmt.Sprintf("Source shares: %s", d.SharesSrc.String())
resp += fmt.Sprintf("Destination shares: %s", d.SharesDst.String())
return resp, nil
}

115
x/stake/types/errors.go Normal file
View File

@ -0,0 +1,115 @@
// nolint
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
type CodeType = sdk.CodeType
const (
DefaultCodespace sdk.CodespaceType = 4
CodeInvalidValidator CodeType = 101
CodeInvalidDelegation CodeType = 102
CodeInvalidInput CodeType = 103
CodeValidatorJailed CodeType = 104
CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)
//validator
func ErrNilValidatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "validator address is nil")
}
func ErrNoValidatorFound(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator does not exist for that address")
}
func ErrValidatorAlreadyExists(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator already exist, cannot re-create validator")
}
func ErrValidatorRevoked(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "validator for this address is currently revoked")
}
func ErrBadRemoveValidator(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "error removing validator")
}
func ErrDescriptionLength(codespace sdk.CodespaceType, descriptor string, got, max int) sdk.Error {
msg := fmt.Sprintf("bad description length for %v, got length %v, max is %v", descriptor, got, max)
return sdk.NewError(codespace, CodeInvalidValidator, msg)
}
func ErrCommissionNegative(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "commission must be positive")
}
func ErrCommissionHuge(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "commission cannot be more than 100%")
}
// delegation
func ErrNilDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "delegator address is nil")
}
func ErrBadDenom(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "invalid coin denomination")
}
func ErrBadDelegationAmount(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "amount must be > 0")
}
func ErrNoDelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "no delegation for this (address, validator) pair")
}
func ErrBadDelegatorAddr(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not exist for that address")
}
func ErrNoDelegatorForAddress(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "delegator does not contain this delegation")
}
func ErrInsufficientShares(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "insufficient delegation shares")
}
func ErrDelegationValidatorEmpty(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "cannot delegate to an empty validator")
}
func ErrNotEnoughDelegationShares(codespace sdk.CodespaceType, shares string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, fmt.Sprintf("not enough shares only have %v", shares))
}
func ErrBadSharesAmount(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "shares must be > 0")
}
func ErrBadSharesPercent(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "shares percent must be >0 and <=1")
}
// redelegation
func ErrNotMature(codespace sdk.CodespaceType, operation, descriptor string, got, min int64) sdk.Error {
msg := fmt.Sprintf("%v is not mature requires a min %v of %v, currently it is %v",
operation, descriptor, got, min)
return sdk.NewError(codespace, CodeUnauthorized, msg)
}
func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found")
}
func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found")
}
func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found")
}
func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidDelegation,
"redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation")
}
// messages
func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided")
}
func ErrNeitherShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInput, "neither shares amount nor shares percent provided")
}
func ErrMissingSignature(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidValidator, "missing signature")
}

26
x/stake/types/genesis.go Normal file
View File

@ -0,0 +1,26 @@
package types
// GenesisState - all staking state that must be provided at genesis
type GenesisState struct {
Pool Pool `json:"pool"`
Params Params `json:"params"`
Validators []Validator `json:"validators"`
Bonds []Delegation `json:"bonds"`
}
func NewGenesisState(pool Pool, params Params, validators []Validator, bonds []Delegation) GenesisState {
return GenesisState{
Pool: pool,
Params: params,
Validators: validators,
Bonds: bonds,
}
}
// get raw genesis raw message for testing
func DefaultGenesisState() GenesisState {
return GenesisState{
Pool: InitialPool(),
Params: DefaultParams(),
}
}

387
x/stake/types/msg.go Normal file
View File

@ -0,0 +1,387 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// name to idetify transaction types
const MsgType = "stake"
//Verify interface at compile time
var _, _, _ sdk.Msg = &MsgCreateValidator{}, &MsgEditValidator{}, &MsgDelegate{}
var _, _ sdk.Msg = &MsgBeginUnbonding{}, &MsgCompleteUnbonding{}
var _, _ sdk.Msg = &MsgBeginRedelegate{}, &MsgCompleteRedelegate{}
//______________________________________________________________________
// MsgCreateValidator - struct for unbonding transactions
type MsgCreateValidator struct {
Description
ValidatorAddr sdk.Address `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
SelfDelegation sdk.Coin `json:"self_delegation"`
}
func NewMsgCreateValidator(validatorAddr sdk.Address, pubkey crypto.PubKey,
selfDelegation sdk.Coin, description Description) MsgCreateValidator {
return MsgCreateValidator{
Description: description,
ValidatorAddr: validatorAddr,
PubKey: pubkey,
SelfDelegation: selfDelegation,
}
}
//nolint
func (msg MsgCreateValidator) Type() string { return MsgType }
func (msg MsgCreateValidator) GetSigners() []sdk.Address {
return []sdk.Address{msg.ValidatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgCreateValidator) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
Description
ValidatorAddr string `json:"address"`
PubKey string `json:"pubkey"`
Bond sdk.Coin `json:"bond"`
}{
Description: msg.Description,
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
PubKey: sdk.MustBech32ifyValPub(msg.PubKey),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgCreateValidator) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if !(msg.SelfDelegation.Amount.GT(sdk.ZeroInt())) {
return ErrBadDelegationAmount(DefaultCodespace)
}
empty := Description{}
if msg.Description == empty {
return sdk.NewError(DefaultCodespace, CodeInvalidInput, "description must be included")
}
return nil
}
//______________________________________________________________________
// MsgEditValidator - struct for editing a validator
type MsgEditValidator struct {
Description
ValidatorAddr sdk.Address `json:"address"`
}
func NewMsgEditValidator(validatorAddr sdk.Address, description Description) MsgEditValidator {
return MsgEditValidator{
Description: description,
ValidatorAddr: validatorAddr,
}
}
//nolint
func (msg MsgEditValidator) Type() string { return MsgType }
func (msg MsgEditValidator) GetSigners() []sdk.Address {
return []sdk.Address{msg.ValidatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgEditValidator) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
Description
ValidatorAddr string `json:"address"`
}{
Description: msg.Description,
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgEditValidator) ValidateBasic() sdk.Error {
if msg.ValidatorAddr == nil {
return sdk.NewError(DefaultCodespace, CodeInvalidInput, "nil validator address")
}
empty := Description{}
if msg.Description == empty {
return sdk.NewError(DefaultCodespace, CodeInvalidInput, "transaction must include some information to modify")
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgDelegate struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgDelegate(delegatorAddr, validatorAddr sdk.Address, bond sdk.Coin) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
Bond: bond,
}
}
//nolint
func (msg MsgDelegate) Type() string { return MsgType }
func (msg MsgDelegate) GetSigners() []sdk.Address {
return []sdk.Address{msg.DelegatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgDelegate) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorAddr string `json:"validator_addr"`
Bond sdk.Coin `json:"bond"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
Bond: msg.Bond,
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if !(msg.Bond.Amount.GT(sdk.ZeroInt())) {
return ErrBadDelegationAmount(DefaultCodespace)
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgBeginRedelegate struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorSrcAddr sdk.Address `json:"validator_src_addr"`
ValidatorDstAddr sdk.Address `json:"validator_dst_addr"`
SharesAmount sdk.Rat `json:"shares_amount"`
}
func NewMsgBeginRedelegate(delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginRedelegate {
return MsgBeginRedelegate{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
SharesAmount: sharesAmount,
}
}
//nolint
func (msg MsgBeginRedelegate) Type() string { return MsgType }
func (msg MsgBeginRedelegate) GetSigners() []sdk.Address {
return []sdk.Address{msg.DelegatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgBeginRedelegate) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorSrcAddr string `json:"validator_src_addr"`
ValidatorDstAddr string `json:"validator_dst_addr"`
SharesAmount string `json:"shares"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr),
ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr),
SharesAmount: msg.SharesAmount.String(),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgBeginRedelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorSrcAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if msg.ValidatorDstAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if msg.SharesAmount.LTE(sdk.ZeroRat()) {
return ErrBadSharesAmount(DefaultCodespace)
}
return nil
}
// MsgDelegate - struct for bonding transactions
type MsgCompleteRedelegate struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorSrcAddr sdk.Address `json:"validator_source_addr"`
ValidatorDstAddr sdk.Address `json:"validator_destination_addr"`
}
func NewMsgCompleteRedelegate(delegatorAddr, validatorSrcAddr,
validatorDstAddr sdk.Address) MsgCompleteRedelegate {
return MsgCompleteRedelegate{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
}
}
//nolint
func (msg MsgCompleteRedelegate) Type() string { return MsgType }
func (msg MsgCompleteRedelegate) GetSigners() []sdk.Address {
return []sdk.Address{msg.DelegatorAddr}
}
// get the bytes for the message signer to sign on
func (msg MsgCompleteRedelegate) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorSrcAddr string `json:"validator_src_addr"`
ValidatorDstAddr string `json:"validator_dst_addr"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorSrcAddr: sdk.MustBech32ifyVal(msg.ValidatorSrcAddr),
ValidatorDstAddr: sdk.MustBech32ifyVal(msg.ValidatorDstAddr),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgCompleteRedelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorSrcAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if msg.ValidatorDstAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
return nil
}
//______________________________________________________________________
// MsgBeginUnbonding - struct for unbonding transactions
type MsgBeginUnbonding struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
SharesAmount sdk.Rat `json:"shares_amount"`
}
func NewMsgBeginUnbonding(delegatorAddr, validatorAddr sdk.Address, sharesAmount sdk.Rat) MsgBeginUnbonding {
return MsgBeginUnbonding{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
SharesAmount: sharesAmount,
}
}
//nolint
func (msg MsgBeginUnbonding) Type() string { return MsgType }
func (msg MsgBeginUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
// get the bytes for the message signer to sign on
func (msg MsgBeginUnbonding) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorAddr string `json:"validator_addr"`
SharesAmount string `json:"shares_amount"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
SharesAmount: msg.SharesAmount.String(),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgBeginUnbonding) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
if msg.SharesAmount.LTE(sdk.ZeroRat()) {
return ErrBadSharesAmount(DefaultCodespace)
}
return nil
}
// MsgCompleteUnbonding - struct for unbonding transactions
type MsgCompleteUnbonding struct {
DelegatorAddr sdk.Address `json:"delegator_addr"`
ValidatorAddr sdk.Address `json:"validator_addr"`
}
func NewMsgCompleteUnbonding(delegatorAddr, validatorAddr sdk.Address) MsgCompleteUnbonding {
return MsgCompleteUnbonding{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
}
}
//nolint
func (msg MsgCompleteUnbonding) Type() string { return MsgType }
func (msg MsgCompleteUnbonding) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
// get the bytes for the message signer to sign on
func (msg MsgCompleteUnbonding) GetSignBytes() []byte {
b, err := MsgCdc.MarshalJSON(struct {
DelegatorAddr string `json:"delegator_addr"`
ValidatorAddr string `json:"validator_src_addr"`
}{
DelegatorAddr: sdk.MustBech32ifyAcc(msg.DelegatorAddr),
ValidatorAddr: sdk.MustBech32ifyVal(msg.ValidatorAddr),
})
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgCompleteUnbonding) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrNilDelegatorAddr(DefaultCodespace)
}
if msg.ValidatorAddr == nil {
return ErrNilValidatorAddr(DefaultCodespace)
}
return nil
}

226
x/stake/types/msg_test.go Normal file
View File

@ -0,0 +1,226 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
var (
coinPos = sdk.Coin{"steak", sdk.NewInt(1000)}
coinZero = sdk.Coin{"steak", sdk.NewInt(0)}
coinNeg = sdk.Coin{"steak", sdk.NewInt(-10000)}
)
// test ValidateBasic for MsgCreateValidator
func TestMsgCreateValidator(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
validatorAddr sdk.Address
pubkey crypto.PubKey
bond sdk.Coin
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addr1, pk1, coinPos, true},
{"partial description", "", "", "c", "", addr1, pk1, coinPos, true},
{"empty description", "", "", "", "", addr1, pk1, coinPos, false},
{"empty address", "a", "b", "c", "d", emptyAddr, pk1, coinPos, false},
{"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true},
{"empty bond", "a", "b", "c", "d", addr1, pk1, coinZero, false},
{"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false},
{"negative bond", "a", "b", "c", "d", addr1, pk1, coinNeg, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgCreateValidator(tc.validatorAddr, tc.pubkey, tc.bond, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgEditValidator
func TestMsgEditValidator(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
validatorAddr sdk.Address
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addr1, true},
{"partial description", "", "", "c", "", addr1, true},
{"empty description", "", "", "", "", addr1, false},
{"empty address", "a", "b", "c", "d", emptyAddr, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgEditValidator(tc.validatorAddr, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgDelegate
func TestMsgDelegate(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorAddr sdk.Address
bond sdk.Coin
expectPass bool
}{
{"basic good", addr1, addr2, coinPos, true},
{"self bond", addr1, addr1, coinPos, true},
{"empty delegator", emptyAddr, addr1, coinPos, false},
{"empty validator", addr1, emptyAddr, coinPos, false},
{"empty bond", addr1, addr2, coinZero, false},
{"negative bond", addr1, addr2, coinNeg, false},
}
for _, tc := range tests {
msg := NewMsgDelegate(tc.delegatorAddr, tc.validatorAddr, tc.bond)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgBeginRedelegate(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorSrcAddr sdk.Address
validatorDstAddr sdk.Address
sharesAmount sdk.Rat
expectPass bool
}{
{"regular", addr1, addr2, addr3, sdk.NewRat(1, 10), true},
{"negative decimal", addr1, addr2, addr3, sdk.NewRat(-1, 10), false},
{"zero amount", addr1, addr2, addr3, sdk.ZeroRat(), false},
{"empty delegator", emptyAddr, addr1, addr3, sdk.NewRat(1, 10), false},
{"empty source validator", addr1, emptyAddr, addr3, sdk.NewRat(1, 10), false},
{"empty destination validator", addr1, addr2, emptyAddr, sdk.NewRat(1, 10), false},
}
for _, tc := range tests {
msg := NewMsgBeginRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr, tc.sharesAmount)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgCompleteRedelegate(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorSrcAddr sdk.Address
validatorDstAddr sdk.Address
expectPass bool
}{
{"regular", addr1, addr2, addr3, true},
{"empty delegator", emptyAddr, addr1, addr3, false},
{"empty source validator", addr1, emptyAddr, addr3, false},
{"empty destination validator", addr1, addr2, emptyAddr, false},
}
for _, tc := range tests {
msg := NewMsgCompleteRedelegate(tc.delegatorAddr, tc.validatorSrcAddr, tc.validatorDstAddr)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgBeginUnbonding(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorAddr sdk.Address
sharesAmount sdk.Rat
expectPass bool
}{
{"regular", addr1, addr2, sdk.NewRat(1, 10), true},
{"negative decimal", addr1, addr2, sdk.NewRat(-1, 10), false},
{"zero amount", addr1, addr2, sdk.ZeroRat(), false},
{"empty delegator", emptyAddr, addr1, sdk.NewRat(1, 10), false},
{"empty validator", addr1, emptyAddr, sdk.NewRat(1, 10), false},
}
for _, tc := range tests {
msg := NewMsgBeginUnbonding(tc.delegatorAddr, tc.validatorAddr, tc.sharesAmount)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgCompleteUnbonding(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
validatorAddr sdk.Address
expectPass bool
}{
{"regular", addr1, addr2, true},
{"empty delegator", emptyAddr, addr1, false},
{"empty validator", addr1, emptyAddr, false},
}
for _, tc := range tests {
msg := NewMsgCompleteUnbonding(tc.delegatorAddr, tc.validatorAddr)
if tc.expectPass {
require.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
require.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// TODO introduce with go-amino
//func TestSerializeMsg(t *testing.T) {
//// make sure all types construct properly
//bondAmt := 1234321
//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)}
//tests := []struct {
//tx sdk.Msg
//}{
//{NewMsgCreateValidator(addr1, pk1, bond, Description{})},
//{NewMsgEditValidator(addr1, Description{})},
//{NewMsgDelegate(addr1, addr2, bond)},
//{NewMsgUnbond(addr1, addr2, strconv.Itoa(bondAmt))},
//}
//for i, tc := range tests {
//var tx sdk.Tx
//bs := wire.BinaryBytes(tc.tx)
//err := wire.ReadBinaryBytes(bs, &tx)
//if assert.NoError(t, err, "%d", i) {
//assert.Equal(t, tc.tx, tx, "%d", i)
//}
//}
//}

View File

@ -1,4 +1,4 @@
package stake
package types
import (
"bytes"
@ -13,13 +13,16 @@ type Params struct {
InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate
GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms
UnbondingTime int64 `json:"unbonding_time"`
MaxValidators uint16 `json:"max_validators"` // maximum number of validators
BondDenom string `json:"bond_denom"` // bondable coin denomination
}
func (p Params) equal(p2 Params) bool {
bz1 := msgCdc.MustMarshalBinary(&p)
bz2 := msgCdc.MustMarshalBinary(&p2)
// nolint
func (p Params) Equal(p2 Params) bool {
bz1 := MsgCdc.MustMarshalBinary(&p)
bz2 := MsgCdc.MustMarshalBinary(&p2)
return bytes.Equal(bz1, bz2)
}
@ -30,6 +33,7 @@ func DefaultParams() Params {
InflationMax: sdk.NewRat(20, 100),
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
UnbondingTime: 60 * 60 * 24 * 3, // 3 weeks in seconds
MaxValidators: 100,
BondDenom: "steak",
}

159
x/stake/types/pool.go Normal file
View File

@ -0,0 +1,159 @@
package types
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Pool - dynamic parameters of the current state
type Pool struct {
LooseTokens int64 `json:"loose_tokens"` // tokens not associated with any validator
UnbondedTokens int64 `json:"unbonded_tokens"` // reserve of unbonded tokens held with validators
UnbondingTokens int64 `json:"unbonding_tokens"` // tokens moving from bonded to unbonded pool
BondedTokens int64 `json:"bonded_tokens"` // reserve of bonded tokens
UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool
UnbondingShares sdk.Rat `json:"unbonding_shares"` // shares moving from Bonded to Unbonded Pool
BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool
InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time
Inflation sdk.Rat `json:"inflation"` // current annual inflation rate
DateLastCommissionReset int64 `json:"date_last_commission_reset"` // unix timestamp for last commission accounting reset (daily)
// Fee Related
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // last recorded bonded shares - for fee calculations
}
// nolint
func (p Pool) Equal(p2 Pool) bool {
bz1 := MsgCdc.MustMarshalBinary(&p)
bz2 := MsgCdc.MustMarshalBinary(&p2)
return bytes.Equal(bz1, bz2)
}
// initial pool for testing
func InitialPool() Pool {
return Pool{
LooseTokens: 0,
BondedTokens: 0,
UnbondingTokens: 0,
UnbondedTokens: 0,
BondedShares: sdk.ZeroRat(),
UnbondingShares: sdk.ZeroRat(),
UnbondedShares: sdk.ZeroRat(),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
DateLastCommissionReset: 0,
PrevBondedShares: sdk.ZeroRat(),
}
}
//____________________________________________________________________
// Sum total of all staking tokens in the pool
func (p Pool) TokenSupply() int64 {
return p.LooseTokens + p.UnbondedTokens + p.UnbondingTokens + p.BondedTokens
}
//____________________________________________________________________
// get the bond ratio of the global state
func (p Pool) BondedRatio() sdk.Rat {
if p.TokenSupply() > 0 {
return sdk.NewRat(p.BondedTokens, p.TokenSupply())
}
return sdk.ZeroRat()
}
// get the exchange rate of bonded token per issued share
func (p Pool) BondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRat(p.BondedTokens).Quo(p.BondedShares)
}
// get the exchange rate of unbonding tokens held in validators per issued share
func (p Pool) UnbondingShareExRate() sdk.Rat {
if p.UnbondingShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRat(p.UnbondingTokens).Quo(p.UnbondingShares)
}
// get the exchange rate of unbonded tokens held in validators per issued share
func (p Pool) UnbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat()
}
return sdk.NewRat(p.UnbondedTokens).Quo(p.UnbondedShares)
}
//_______________________________________________________________________
func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondedShareExRate()) // tokens * (shares/tokens)
p.UnbondedShares = p.UnbondedShares.Add(issuedSharesAmount)
p.UnbondedTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return p, NewUnbondedShares(issuedSharesAmount)
}
func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.UnbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.UnbondedShares = p.UnbondedShares.Sub(shares)
p.UnbondedTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: unbonded tokens negative, pool: %v", p))
}
return p, removedTokens
}
func (p Pool) addTokensUnbonding(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.UnbondingShareExRate()) // tokens * (shares/tokens)
p.UnbondingShares = p.UnbondingShares.Add(issuedSharesAmount)
p.UnbondingTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return p, NewUnbondingShares(issuedSharesAmount)
}
func (p Pool) removeSharesUnbonding(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.UnbondingShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.UnbondingShares = p.UnbondingShares.Sub(shares)
p.UnbondingTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: unbonding tokens negative, pool: %v", p))
}
return p, removedTokens
}
func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares PoolShares) {
issuedSharesAmount := sdk.NewRat(amount).Quo(p.BondedShareExRate()) // tokens * (shares/tokens)
p.BondedShares = p.BondedShares.Add(issuedSharesAmount)
p.BondedTokens += amount
p.LooseTokens -= amount
if p.LooseTokens < 0 {
panic(fmt.Sprintf("sanity check: loose tokens negative, pool: %v", p))
}
return p, NewBondedShares(issuedSharesAmount)
}
func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) {
removedTokens = p.BondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.BondedShares = p.BondedShares.Sub(shares)
p.BondedTokens -= removedTokens
p.LooseTokens += removedTokens
if p.UnbondedTokens < 0 {
panic(fmt.Sprintf("sanity check: bonded tokens negative, pool: %v", p))
}
return p, removedTokens
}

128
x/stake/types/pool_test.go Normal file
View File

@ -0,0 +1,128 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestBondedRatio(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 1
pool.BondedTokens = 2
// bonded pool / total supply
require.Equal(t, pool.BondedRatio(), sdk.NewRat(2).Quo(sdk.NewRat(3)))
// avoids divide-by-zero
pool.LooseTokens = 0
pool.BondedTokens = 0
require.Equal(t, pool.BondedRatio(), sdk.ZeroRat())
}
func TestBondedShareExRate(t *testing.T) {
pool := InitialPool()
pool.BondedTokens = 3
pool.BondedShares = sdk.NewRat(10)
// bonded pool / bonded shares
require.Equal(t, pool.BondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.BondedShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.BondedShareExRate(), sdk.OneRat())
}
func TestUnbondingShareExRate(t *testing.T) {
pool := InitialPool()
pool.UnbondingTokens = 3
pool.UnbondingShares = sdk.NewRat(10)
// unbonding pool / unbonding shares
require.Equal(t, pool.UnbondingShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondingShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.UnbondingShareExRate(), sdk.OneRat())
}
func TestUnbondedShareExRate(t *testing.T) {
pool := InitialPool()
pool.UnbondedTokens = 3
pool.UnbondedShares = sdk.NewRat(10)
// unbonded pool / unbonded shares
require.Equal(t, pool.UnbondedShareExRate(), sdk.NewRat(3).Quo(sdk.NewRat(10)))
pool.UnbondedShares = sdk.ZeroRat()
// avoids divide-by-zero
require.Equal(t, pool.UnbondedShareExRate(), sdk.OneRat())
}
func TestAddTokensBonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensBonded(10)
assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB.Amount))
assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens+10)
// same number of bonded shares / tokens when exchange rate is one
assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens)))
}
func TestRemoveSharesBonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10))
assert.Equal(t, poolB.BondedShareExRate(), sdk.OneRat())
// correct changes to bonded shares and bonded pool
assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.BondedTokens, poolA.BondedTokens-tokensB)
// same number of bonded shares / tokens when exchange rate is one
assert.True(t, poolB.BondedShares.Equal(sdk.NewRat(poolB.BondedTokens)))
}
func TestAddTokensUnbonded(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
poolB, sharesB := poolA.addTokensUnbonded(10)
assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and unbonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB.Amount))
assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens+10)
// same number of unbonded shares / tokens when exchange rate is one
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens)))
}
func TestRemoveSharesUnbonded(t *testing.T) {
poolA := InitialPool()
poolA.UnbondedTokens = 10
poolA.UnbondedShares = sdk.NewRat(10)
assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10))
assert.Equal(t, poolB.UnbondedShareExRate(), sdk.OneRat())
// correct changes to unbonded shares and bonded pool
assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10)))
assert.Equal(t, poolB.UnbondedTokens, poolA.UnbondedTokens-tokensB)
// same number of unbonded shares / tokens when exchange rate is one
assert.True(t, poolB.UnbondedShares.Equal(sdk.NewRat(poolB.UnbondedTokens)))
}

View File

@ -1,4 +1,4 @@
package stake
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
@ -70,10 +70,10 @@ func (s PoolShares) ToUnbonded(p Pool) PoolShares {
var amount sdk.Rat
switch s.Status {
case sdk.Bonded:
exRate := p.bondedShareExRate().Quo(p.unbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr
exRate := p.BondedShareExRate().Quo(p.UnbondedShareExRate()) // (tok/bondedshr)/(tok/unbondedshr) = unbondedshr/bondedshr
amount = s.Amount.Mul(exRate) // bondedshr*unbondedshr/bondedshr = unbondedshr
case sdk.Unbonding:
exRate := p.unbondingShareExRate().Quo(p.unbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr
exRate := p.UnbondingShareExRate().Quo(p.UnbondedShareExRate()) // (tok/unbondingshr)/(tok/unbondedshr) = unbondedshr/unbondingshr
amount = s.Amount.Mul(exRate) // unbondingshr*unbondedshr/unbondingshr = unbondedshr
case sdk.Unbonded:
amount = s.Amount
@ -86,12 +86,12 @@ func (s PoolShares) ToUnbonding(p Pool) PoolShares {
var amount sdk.Rat
switch s.Status {
case sdk.Bonded:
exRate := p.bondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr
exRate := p.BondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/bondedshr)/(tok/unbondingshr) = unbondingshr/bondedshr
amount = s.Amount.Mul(exRate) // bondedshr*unbondingshr/bondedshr = unbondingshr
case sdk.Unbonding:
amount = s.Amount
case sdk.Unbonded:
exRate := p.unbondedShareExRate().Quo(p.unbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr
exRate := p.UnbondedShareExRate().Quo(p.UnbondingShareExRate()) // (tok/unbondedshr)/(tok/unbondingshr) = unbondingshr/unbondedshr
amount = s.Amount.Mul(exRate) // unbondedshr*unbondingshr/unbondedshr = unbondingshr
}
return NewUnbondingShares(amount)
@ -104,10 +104,10 @@ func (s PoolShares) ToBonded(p Pool) PoolShares {
case sdk.Bonded:
amount = s.Amount
case sdk.Unbonding:
exRate := p.unbondingShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr
exRate := p.UnbondingShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr
amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr
case sdk.Unbonded:
exRate := p.unbondedShareExRate().Quo(p.bondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr
exRate := p.UnbondedShareExRate().Quo(p.BondedShareExRate()) // (tok/ubshr)/(tok/bshr) = bshr/ubshr
amount = s.Amount.Mul(exRate) // ubshr*bshr/ubshr = bshr
}
return NewUnbondedShares(amount)
@ -120,11 +120,11 @@ func (s PoolShares) ToBonded(p Pool) PoolShares {
func (s PoolShares) Tokens(p Pool) sdk.Rat {
switch s.Status {
case sdk.Bonded:
return p.bondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares
return p.BondedShareExRate().Mul(s.Amount) // (tokens/shares) * shares
case sdk.Unbonding:
return p.unbondingShareExRate().Mul(s.Amount)
return p.UnbondingShareExRate().Mul(s.Amount)
case sdk.Unbonded:
return p.unbondedShareExRate().Mul(s.Amount)
return p.UnbondedShareExRate().Mul(s.Amount)
default:
panic("unknown share kind")
}

View File

@ -0,0 +1,201 @@
package types
import (
"fmt"
"math/rand"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
)
var (
// dummy pubkeys/addresses
pk1 = crypto.GenPrivKeyEd25519().PubKey()
pk2 = crypto.GenPrivKeyEd25519().PubKey()
pk3 = crypto.GenPrivKeyEd25519().PubKey()
addr1 = pk1.Address()
addr2 = pk2.Address()
addr3 = pk3.Address()
emptyAddr sdk.Address
emptyPubkey crypto.PubKey
)
//______________________________________________________________
// any operation that transforms staking state
// takes in RNG instance, pool, validator
// returns updated pool, updated validator, delta tokens, descriptive message
type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, int64, string)
// operation: bond or unbond a validator depending on current status
func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
var msg string
var newStatus sdk.BondStatus
if val.Status() == sdk.Bonded {
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newStatus = sdk.Unbonded
} else if val.Status() == sdk.Unbonded {
msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newStatus = sdk.Bonded
}
val, pool = val.UpdateStatus(pool, newStatus)
return pool, val, 0, msg
}
// operation: add a random number of tokens to a validator
func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
tokens := int64(r.Int31n(1000))
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
val, pool, _ = val.AddTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
return pool, val, -1 * tokens, msg // tokens are removed so for accounting must be negative
}
// operation: remove a random number of shares from a validator
func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, int64, string) {
var shares sdk.Rat
for {
shares = sdk.NewRat(int64(r.Int31n(1000)))
if shares.LT(val.DelegatorShares) {
break
}
}
msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool))
val, pool, tokens := val.RemoveDelShares(pool, shares)
return pool, val, tokens, msg
}
// pick a random staking operation
func RandomOperation(r *rand.Rand) Operation {
operations := []Operation{
OpBondOrUnbond,
OpAddTokens,
OpRemoveShares,
}
r.Shuffle(len(operations), func(i, j int) {
operations[i], operations[j] = operations[j], operations[i]
})
return operations[0]
}
// ensure invariants that should always be true are true
func AssertInvariants(t *testing.T, msg string,
pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator, tokens int64) {
// total tokens conserved
require.Equal(t,
pOrig.UnbondedTokens+pOrig.BondedTokens,
pMod.UnbondedTokens+pMod.BondedTokens+tokens,
"Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n",
msg,
pOrig.BondedShares, pOrig.UnbondedShares,
pMod.BondedShares, pMod.UnbondedShares,
pOrig.UnbondedTokens, pOrig.BondedTokens,
pMod.UnbondedTokens, pMod.BondedTokens, tokens)
// nonnegative bonded shares
require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()),
"Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative unbonded shares
require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()),
"Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative bonded ex rate
require.False(t, pMod.BondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative BondedShareExRate: %d",
msg, pMod.BondedShareExRate().Evaluate())
// nonnegative unbonded ex rate
require.False(t, pMod.UnbondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative UnbondedShareExRate: %d",
msg, pMod.UnbondedShareExRate().Evaluate())
for _, vMod := range vMods {
// nonnegative ex rate
require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)",
msg,
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
// nonnegative poolShares
require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
msg,
vMod.PoolShares.Bonded(),
vMod.DelegatorShares,
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
// nonnegative delShares
require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
msg,
vMod.DelegatorShares,
vMod.PoolShares.Bonded(),
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
}
}
//________________________________________________________________________________
// TODO refactor this random setup
// generate a random validator
func randomValidator(r *rand.Rand, i int) Validator {
poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000)))
delShares := sdk.NewRat(int64(r.Int31n(10000)))
var pShares PoolShares
if r.Float64() < float64(0.5) {
pShares = NewBondedShares(poolSharesAmt)
} else {
pShares = NewUnbondedShares(poolSharesAmt)
}
return Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: pShares,
DelegatorShares: delShares,
}
}
// generate a random staking state
func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) {
pool := InitialPool()
pool.LooseTokens = 100000
validators := make([]Validator, numValidators)
for i := 0; i < numValidators; i++ {
validator := randomValidator(r, i)
if validator.Status() == sdk.Bonded {
pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded())
pool.BondedTokens += validator.PoolShares.Bonded().Evaluate()
} else if validator.Status() == sdk.Unbonded {
pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded())
pool.UnbondedTokens += validator.PoolShares.Unbonded().Evaluate()
}
validators[i] = validator
}
return pool, validators
}

View File

@ -1,14 +1,15 @@
package stake
package types
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
tmtypes "github.com/tendermint/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// Validator defines the total amount of bond shares and their exchange rate to
@ -40,9 +41,6 @@ type Validator struct {
PrevBondedShares sdk.Rat `json:"prev_bonded_shares"` // total shares of a global hold pools
}
// Validators - list of Validators
type Validators []Validator
// NewValidator - initialize a new validator
func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Description) Validator {
return Validator{
@ -64,7 +62,7 @@ func NewValidator(owner sdk.Address, pubKey crypto.PubKey, description Descripti
}
// only the vitals - does not check bond height of IntraTxCounter
func (v Validator) equal(c2 Validator) bool {
func (v Validator) Equal(c2 Validator) bool {
return v.PubKey.Equals(c2.PubKey) &&
bytes.Equal(v.Owner, c2.Owner) &&
v.PoolShares.Equal(c2.PoolShares) &&
@ -97,10 +95,47 @@ func NewDescription(moniker, identity, website, details string) Description {
}
}
//XXX updateDescription function which enforce limit to number of description characters
// update the description based on input
func (d Description) UpdateDescription(d2 Description) (Description, sdk.Error) {
if d.Moniker == "[do-not-modify]" {
d2.Moniker = d.Moniker
}
if d.Identity == "[do-not-modify]" {
d2.Identity = d.Identity
}
if d.Website == "[do-not-modify]" {
d2.Website = d.Website
}
if d.Details == "[do-not-modify]" {
d2.Details = d.Details
}
return Description{
Moniker: d2.Moniker,
Identity: d2.Identity,
Website: d2.Website,
Details: d2.Details,
}.EnsureLength()
}
// ensure the length of the description
func (d Description) EnsureLength() (Description, sdk.Error) {
if len(d.Moniker) > 70 {
return d, ErrDescriptionLength(DefaultCodespace, "moniker", len(d.Moniker), 70)
}
if len(d.Identity) > 3000 {
return d, ErrDescriptionLength(DefaultCodespace, "identity", len(d.Identity), 3000)
}
if len(d.Website) > 140 {
return d, ErrDescriptionLength(DefaultCodespace, "website", len(d.Website), 140)
}
if len(d.Details) > 280 {
return d, ErrDescriptionLength(DefaultCodespace, "details", len(d.Details), 280)
}
return d, nil
}
// abci validator from stake validator type
func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator {
func (v Validator) ABCIValidator(cdc *wire.Codec) abci.Validator {
return abci.Validator{
PubKey: tmtypes.TM2PB.PubKey(v.PubKey),
Power: v.PoolShares.Bonded().Evaluate(),
@ -109,7 +144,7 @@ func (v Validator) abciValidator(cdc *wire.Codec) abci.Validator {
// abci validator from stake validator type
// with zero power used for validator updates
func (v Validator) abciValidatorZero(cdc *wire.Codec) abci.Validator {
func (v Validator) ABCIValidatorZero(cdc *wire.Codec) abci.Validator {
return abci.Validator{
PubKey: tmtypes.TM2PB.PubKey(v.PubKey),
Power: 0,
@ -123,7 +158,7 @@ func (v Validator) Status() sdk.BondStatus {
// update the location of the shares within a validator if its bond status has changed
func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator, Pool) {
var tokens sdk.Int
var tokens int64
switch v.Status() {
case sdk.Unbonded:
@ -159,8 +194,8 @@ func (v Validator) UpdateStatus(pool Pool, NewStatus sdk.BondStatus) (Validator,
// Remove pool shares
// Returns corresponding tokens, which could be burned (e.g. when slashing
// a validator) or redistributed elsewhere
func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, sdk.Int) {
var tokens sdk.Int
func (v Validator) RemovePoolShares(pool Pool, poolShares sdk.Rat) (Validator, Pool, int64) {
var tokens int64
switch v.Status() {
case sdk.Unbonded:
pool, tokens = pool.removeSharesUnbonded(poolShares)
@ -173,7 +208,7 @@ func (v Validator) removePoolShares(pool Pool, poolShares sdk.Rat) (Validator, P
return v, pool, tokens
}
// XXX TEST
// TODO remove should only be tokens
// get the power or potential power for a validator
// if bonded, the power is the BondedShares
// if not bonded, the power is the amount of bonded shares which the
@ -184,10 +219,9 @@ func (v Validator) EquivalentBondedShares(pool Pool) (eqBondedShares sdk.Rat) {
//_________________________________________________________________________________________________________
// XXX Audit this function further to make sure it's correct
// add tokens to a validator
func (v Validator) addTokensFromDel(pool Pool,
amount sdk.Int) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) {
func (v Validator) AddTokensFromDel(pool Pool,
amount int64) (validator2 Validator, p2 Pool, issuedDelegatorShares sdk.Rat) {
exRate := v.DelegatorShareExRate(pool) // bshr/delshr
@ -212,8 +246,8 @@ func (v Validator) addTokensFromDel(pool Pool,
// remove delegator shares from a validator
// NOTE this function assumes the shares have already been updated for the validator status
func (v Validator) removeDelShares(pool Pool,
delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins sdk.Int) {
func (v Validator) RemoveDelShares(pool Pool,
delShares sdk.Rat) (validator2 Validator, p2 Pool, createdCoins int64) {
amount := v.DelegatorShareExRate(pool).Mul(delShares)
eqBondedSharesToRemove := NewBondedShares(amount)

View File

@ -0,0 +1,232 @@
package types
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestAddTokensValidatorBonded(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Bonded)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded()))
}
func TestAddTokensValidatorUnbonding(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding()))
}
func TestAddTokensValidatorUnbonded(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 10
val := NewValidator(addr1, pk1, Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonded)
val, pool, delShares := val.AddTokensFromDel(pool, 10)
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.BondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.UnbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded()))
}
// TODO refactor to make simpler like the AddToken tests above
func TestRemoveDelShares(t *testing.T) {
poolA := InitialPool()
poolA.LooseTokens = 10
valA := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(sdk.NewRat(100)),
DelegatorShares: sdk.NewRat(100),
}
poolA.BondedTokens = valA.PoolShares.Bonded().Evaluate()
poolA.BondedShares = valA.PoolShares.Bonded()
assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat())
assert.Equal(t, poolA.BondedShareExRate(), sdk.OneRat())
assert.Equal(t, poolA.UnbondedShareExRate(), sdk.OneRat())
valB, poolB, coinsB := valA.RemoveDelShares(poolA, sdk.NewRat(10))
// coins were created
assert.Equal(t, coinsB, int64(10))
// pool shares were removed
assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA))))
// conservation of tokens
assert.Equal(t, poolB.UnbondedTokens+poolB.BondedTokens+coinsB, poolA.UnbondedTokens+poolA.BondedTokens)
// specific case from random tests
poolShares := sdk.NewRat(5102)
delShares := sdk.NewRat(115)
val := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(poolShares),
DelegatorShares: delShares,
}
pool := Pool{
BondedShares: sdk.NewRat(248305),
UnbondedShares: sdk.NewRat(232147),
BondedTokens: 248305,
UnbondedTokens: 232147,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
shares := sdk.NewRat(29)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
_, newPool, tokens := val.RemoveDelShares(pool, shares)
require.Equal(t,
tokens+newPool.UnbondedTokens+newPool.BondedTokens,
pool.BondedTokens+pool.UnbondedTokens,
"Tokens were not conserved: %s", msg)
}
func TestUpdateStatus(t *testing.T) {
pool := InitialPool()
pool.LooseTokens = 100
val := NewValidator(addr1, pk1, Description{})
val, pool, _ = val.AddTokensFromDel(pool, 100)
assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(0), pool.BondedTokens)
assert.Equal(t, int64(0), pool.UnbondingTokens)
assert.Equal(t, int64(100), pool.UnbondedTokens)
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(0), pool.BondedTokens)
assert.Equal(t, int64(100), pool.UnbondingTokens)
assert.Equal(t, int64(0), pool.UnbondedTokens)
val, pool = val.UpdateStatus(pool, sdk.Bonded)
assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(100), pool.BondedTokens)
assert.Equal(t, int64(0), pool.UnbondingTokens)
assert.Equal(t, int64(0), pool.UnbondedTokens)
}
func TestPossibleOverflow(t *testing.T) {
poolShares := sdk.NewRat(2159)
delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
val := Validator{
Owner: addr1,
PubKey: pk1,
PoolShares: NewBondedShares(poolShares),
DelegatorShares: delShares,
}
pool := Pool{
LooseTokens: 100,
BondedShares: poolShares,
UnbondedShares: sdk.ZeroRat(),
BondedTokens: poolShares.Evaluate(),
UnbondedTokens: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
tokens := int64(71)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newValidator, _, _ := val.AddTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg)
require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
msg, newValidator.DelegatorShareExRate(pool))
}
// run random operations in a random order on a random single-validator state, assert invariants hold
func TestSingleValidatorIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(41))
for i := 0; i < 10; i++ {
poolOrig, validatorsOrig := RandomSetup(r, 1)
require.Equal(t, 1, len(validatorsOrig))
// sanity check
AssertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, 0)
for j := 0; j < 5; j++ {
poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
require.Equal(t, 1, len(validatorsOrig), "j %v", j)
require.Equal(t, 1, len(validatorsMod), "j %v", j)
validatorsMod[0] = validatorMod
AssertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolOrig = poolMod
validatorsOrig = validatorsMod
}
}
}
// run random operations in a random order on a random multi-validator state, assert invariants hold
func TestMultiValidatorIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(42))
for i := 0; i < 10; i++ {
poolOrig, validatorsOrig := RandomSetup(r, 100)
AssertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, 0)
for j := 0; j < 5; j++ {
index := int(r.Int31n(int32(len(validatorsOrig))))
poolMod, validatorMod, tokens, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
validatorsMod[index] = validatorMod
AssertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolOrig = poolMod
validatorsOrig = validatorsMod
}
}
}

27
x/stake/types/wire.go Normal file
View File

@ -0,0 +1,27 @@
package types
import (
"github.com/cosmos/cosmos-sdk/wire"
)
// Register concrete types on wire codec
func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil)
cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil)
cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil)
cdc.RegisterConcrete(MsgBeginUnbonding{}, "cosmos-sdk/BeginUnbonding", nil)
cdc.RegisterConcrete(MsgCompleteUnbonding{}, "cosmos-sdk/CompleteUnbonding", nil)
cdc.RegisterConcrete(MsgBeginRedelegate{}, "cosmos-sdk/BeginRedelegate", nil)
cdc.RegisterConcrete(MsgCompleteRedelegate{}, "cosmos-sdk/CompleteRedelegate", nil)
}
// generic sealed codec to be used throughout sdk
var MsgCdc *wire.Codec
func init() {
cdc := wire.NewCodec()
RegisterWire(cdc)
wire.RegisterCrypto(cdc)
MsgCdc = cdc
//MsgCdc = cdc.Seal() //TODO use when upgraded to go-amino 0.9.10
}

View File

@ -1,408 +0,0 @@
package stake
import (
"fmt"
"math/rand"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAddTokensValidatorBonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
val := NewValidator(addrs[0], pks[0], Description{})
val, pool = val.UpdateStatus(pool, sdk.Bonded)
val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10))
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Bonded()))
}
func TestAddTokensValidatorUnbonding(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
val := NewValidator(addrs[0], pks[0], Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10))
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonding()))
}
func TestAddTokensValidatorUnbonded(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
val := NewValidator(addrs[0], pks[0], Description{})
val, pool = val.UpdateStatus(pool, sdk.Unbonded)
val, pool, delShares := val.addTokensFromDel(pool, sdk.NewInt(10))
assert.Equal(t, sdk.OneRat(), val.DelegatorShareExRate(pool))
assert.Equal(t, sdk.OneRat(), pool.bondedShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondingShareExRate())
assert.Equal(t, sdk.OneRat(), pool.unbondedShareExRate())
assert.True(sdk.RatEq(t, sdk.NewRat(10), delShares))
assert.True(sdk.RatEq(t, sdk.NewRat(10), val.PoolShares.Unbonded()))
}
// TODO refactor to make simpler like the AddToken tests above
func TestRemoveShares(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
poolA := keeper.GetPool(ctx)
valA := Validator{
Owner: addrs[0],
PubKey: pks[0],
PoolShares: NewBondedShares(sdk.NewRat(9)),
DelegatorShares: sdk.NewRat(9),
}
poolA.BondedTokens = valA.PoolShares.Bonded().EvaluateInt()
poolA.BondedShares = valA.PoolShares.Bonded()
assert.Equal(t, valA.DelegatorShareExRate(poolA), sdk.OneRat())
assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat())
assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat())
valB, poolB, coinsB := valA.removeDelShares(poolA, sdk.NewRat(10))
// coins were created
assert.Equal(t, coinsB.Int64(), int64(10))
// pool shares were removed
assert.Equal(t, valB.PoolShares.Bonded(), valA.PoolShares.Bonded().Sub(sdk.NewRat(10).Mul(valA.DelegatorShareExRate(poolA))))
// conservation of tokens
assert.Equal(t, poolB.UnbondedTokens.Add(poolB.BondedTokens).Add(coinsB), poolA.UnbondedTokens.Add(poolA.BondedTokens))
// specific case from random tests
poolShares := sdk.NewRat(5102)
delShares := sdk.NewRat(115)
val := Validator{
Owner: addrs[0],
PubKey: pks[0],
PoolShares: NewBondedShares(poolShares),
DelegatorShares: delShares,
}
pool := Pool{
BondedShares: sdk.NewRat(248305),
UnbondedShares: sdk.NewRat(232147),
BondedTokens: sdk.NewInt(248305),
UnbondedTokens: sdk.NewInt(232147),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
shares := sdk.NewRat(29)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
msg = fmt.Sprintf("Removed %v shares from %s", shares, msg)
_, newPool, tokens := val.removeDelShares(pool, shares)
require.Equal(t,
tokens.Add(newPool.UnbondedTokens).Add(newPool.BondedTokens),
pool.BondedTokens.Add(pool.UnbondedTokens),
"Tokens were not conserved: %s", msg)
}
func TestUpdateStatus(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
pool := keeper.GetPool(ctx)
val := NewValidator(addrs[0], pks[0], Description{})
val, pool, _ = val.addTokensFromDel(pool, sdk.NewInt(100))
assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(100), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(0), pool.BondedTokens.Int64())
assert.Equal(t, int64(0), pool.UnbondingTokens.Int64())
assert.Equal(t, int64(100), pool.UnbondedTokens.Int64())
val, pool = val.UpdateStatus(pool, sdk.Unbonding)
assert.Equal(t, int64(0), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(100), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(0), pool.BondedTokens.Int64())
assert.Equal(t, int64(100), pool.UnbondingTokens.Int64())
assert.Equal(t, int64(0), pool.UnbondedTokens.Int64())
val, pool = val.UpdateStatus(pool, sdk.Bonded)
assert.Equal(t, int64(100), val.PoolShares.Bonded().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonding().Evaluate())
assert.Equal(t, int64(0), val.PoolShares.Unbonded().Evaluate())
assert.Equal(t, int64(100), pool.BondedTokens.Int64())
assert.Equal(t, int64(0), pool.UnbondingTokens.Int64())
assert.Equal(t, int64(0), pool.UnbondedTokens.Int64())
}
//________________________________________________________________________________
// TODO refactor this random setup
// generate a random validator
func randomValidator(r *rand.Rand, i int) Validator {
poolSharesAmt := sdk.NewRat(int64(r.Int31n(10000)))
delShares := sdk.NewRat(int64(r.Int31n(10000)))
var pShares PoolShares
if r.Float64() < float64(0.5) {
pShares = NewBondedShares(poolSharesAmt)
} else {
pShares = NewUnbondedShares(poolSharesAmt)
}
return Validator{
Owner: addrs[i],
PubKey: pks[i],
PoolShares: pShares,
DelegatorShares: delShares,
}
}
// generate a random staking state
func randomSetup(r *rand.Rand, numValidators int) (Pool, Validators) {
pool := InitialPool()
validators := make([]Validator, numValidators)
for i := 0; i < numValidators; i++ {
validator := randomValidator(r, i)
if validator.Status() == sdk.Bonded {
pool.BondedShares = pool.BondedShares.Add(validator.PoolShares.Bonded())
pool.BondedTokens = pool.BondedTokens.Add(validator.PoolShares.Bonded().EvaluateInt())
} else if validator.Status() == sdk.Unbonded {
pool.UnbondedShares = pool.UnbondedShares.Add(validator.PoolShares.Unbonded())
pool.UnbondedTokens = pool.UnbondedTokens.Add(validator.PoolShares.Unbonded().EvaluateInt())
}
validators[i] = validator
}
return pool, validators
}
// any operation that transforms staking state
// takes in RNG instance, pool, validator
// returns updated pool, updated validator, delta tokens, descriptive message
type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, sdk.Int, string)
// operation: bond or unbond a validator depending on current status
func OpBondOrUnbond(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) {
var msg string
var newStatus sdk.BondStatus
if val.Status() == sdk.Bonded {
msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newStatus = sdk.Unbonded
} else if val.Status() == sdk.Unbonded {
msg = fmt.Sprintf("sdk.Bonded previously unbonded validator %s (poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newStatus = sdk.Bonded
}
val, pool = val.UpdateStatus(pool, newStatus)
return pool, val, sdk.ZeroInt(), msg
}
// operation: add a random number of tokens to a validator
func OpAddTokens(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) {
tokens := sdk.NewInt(int64(r.Int31n(1000)))
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
val, pool, _ = val.addTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg)
return pool, val, tokens.Neg(), msg // tokens are removed so for accounting must be negative
}
// operation: remove a random number of shares from a validator
func OpRemoveShares(r *rand.Rand, pool Pool, val Validator) (Pool, Validator, sdk.Int, string) {
var shares sdk.Rat
for {
shares = sdk.NewRat(int64(r.Int31n(1000)))
if shares.LT(val.DelegatorShares) {
break
}
}
msg := fmt.Sprintf("Removed %v shares from validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
shares, val.Owner, val.Status(), val.PoolShares, val.DelegatorShares, val.DelegatorShareExRate(pool))
val, pool, tokens := val.removeDelShares(pool, shares)
return pool, val, tokens, msg
}
// pick a random staking operation
func randomOperation(r *rand.Rand) Operation {
operations := []Operation{
OpBondOrUnbond,
OpAddTokens,
OpRemoveShares,
}
r.Shuffle(len(operations), func(i, j int) {
operations[i], operations[j] = operations[j], operations[i]
})
return operations[0]
}
// ensure invariants that should always be true are true
func assertInvariants(t *testing.T, msg string,
pOrig Pool, cOrig Validators, pMod Pool, vMods Validators, tokens sdk.Int) {
// total tokens conserved
require.Equal(t,
pOrig.UnbondedTokens.Add(pOrig.BondedTokens),
pMod.UnbondedTokens.Add(pMod.BondedTokens).Add(tokens),
"Tokens not conserved - msg: %v\n, pOrig.PoolShares.Bonded(): %v, pOrig.PoolShares.Unbonded(): %v, pMod.PoolShares.Bonded(): %v, pMod.PoolShares.Unbonded(): %v, pOrig.UnbondedTokens: %v, pOrig.BondedTokens: %v, pMod.UnbondedTokens: %v, pMod.BondedTokens: %v, tokens: %v\n",
msg,
pOrig.BondedShares, pOrig.UnbondedShares,
pMod.BondedShares, pMod.UnbondedShares,
pOrig.UnbondedTokens, pOrig.BondedTokens,
pMod.UnbondedTokens, pMod.BondedTokens, tokens)
// nonnegative bonded shares
require.False(t, pMod.BondedShares.LT(sdk.ZeroRat()),
"Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative unbonded shares
require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat()),
"Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\ntokens: %v\n",
msg, pOrig, pMod, tokens)
// nonnegative bonded ex rate
require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative bondedShareExRate: %d",
msg, pMod.bondedShareExRate().Evaluate())
// nonnegative unbonded ex rate
require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative unbondedShareExRate: %d",
msg, pMod.unbondedShareExRate().Evaluate())
for _, vMod := range vMods {
// nonnegative ex rate
require.False(t, vMod.DelegatorShareExRate(pMod).LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)",
msg,
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
// nonnegative poolShares
require.False(t, vMod.PoolShares.Bonded().LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.PoolShares.Bonded(): %v (validator.DelegatorShares: %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
msg,
vMod.PoolShares.Bonded(),
vMod.DelegatorShares,
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
// nonnegative delShares
require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative validator.DelegatorShares: %v (validator.PoolShares.Bonded(): %v, validator.DelegatorShareExRate: %v, validator.Owner: %s)",
msg,
vMod.DelegatorShares,
vMod.PoolShares.Bonded(),
vMod.DelegatorShareExRate(pMod),
vMod.Owner,
)
}
}
func TestPossibleOverflow(t *testing.T) {
poolShares := sdk.NewRat(2159)
delShares := sdk.NewRat(391432570689183511).Quo(sdk.NewRat(40113011844664))
val := Validator{
Owner: addrs[0],
PubKey: pks[0],
PoolShares: NewBondedShares(poolShares),
DelegatorShares: delShares,
}
pool := Pool{
BondedShares: poolShares,
UnbondedShares: sdk.ZeroRat(),
BondedTokens: poolShares.EvaluateInt(),
UnbondedTokens: sdk.ZeroInt(),
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
tokens := sdk.NewInt(71)
msg := fmt.Sprintf("validator %s (status: %d, poolShares: %v, delShares: %v, DelegatorShareExRate: %v)",
val.Owner, val.Status(), val.PoolShares.Bonded(), val.DelegatorShares, val.DelegatorShareExRate(pool))
newValidator, _, _ := val.addTokensFromDel(pool, tokens)
msg = fmt.Sprintf("Added %v tokens to %s", tokens, msg)
require.False(t, newValidator.DelegatorShareExRate(pool).LT(sdk.ZeroRat()),
"Applying operation \"%s\" resulted in negative DelegatorShareExRate(): %v",
msg, newValidator.DelegatorShareExRate(pool))
}
// run random operations in a random order on a random single-validator state, assert invariants hold
func TestSingleValidatorIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(41))
for i := 0; i < 10; i++ {
poolOrig, validatorsOrig := randomSetup(r, 1)
require.Equal(t, 1, len(validatorsOrig))
// sanity check
assertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, sdk.ZeroInt())
for j := 0; j < 5; j++ {
poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[0])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
require.Equal(t, 1, len(validatorsOrig), "j %v", j)
require.Equal(t, 1, len(validatorsMod), "j %v", j)
validatorsMod[0] = validatorMod
assertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolOrig = poolMod
validatorsOrig = validatorsMod
}
}
}
// run random operations in a random order on a random multi-validator state, assert invariants hold
func TestMultiValidatorIntegrationInvariants(t *testing.T) {
r := rand.New(rand.NewSource(42))
for i := 0; i < 10; i++ {
poolOrig, validatorsOrig := randomSetup(r, 100)
assertInvariants(t, "no operation",
poolOrig, validatorsOrig,
poolOrig, validatorsOrig, sdk.ZeroInt())
for j := 0; j < 5; j++ {
index := int(r.Int31n(int32(len(validatorsOrig))))
poolMod, validatorMod, tokens, msg := randomOperation(r)(r, poolOrig, validatorsOrig[index])
validatorsMod := make([]Validator, len(validatorsOrig))
copy(validatorsMod[:], validatorsOrig[:])
validatorsMod[index] = validatorMod
assertInvariants(t, msg,
poolOrig, validatorsOrig,
poolMod, validatorsMod, tokens)
poolOrig = poolMod
validatorsOrig = validatorsMod
}
}
}

View File

@ -1,29 +0,0 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// keeper to view information & slash validators
// will be used by governance module
type ViewSlashKeeper struct {
keeper Keeper
}
// NewViewSlashKeeper creates a keeper restricted to
// viewing information & slashing validators
func NewViewSlashKeeper(k Keeper) ViewSlashKeeper {
return ViewSlashKeeper{k}
}
// load a delegator bond
func (v ViewSlashKeeper) GetDelegation(ctx sdk.Context,
delegatorAddr sdk.Address, validatorAddr sdk.Address) (bond Delegation, found bool) {
return v.keeper.GetDelegation(ctx, delegatorAddr, validatorAddr)
}
// load n delegator bonds
func (v ViewSlashKeeper) GetDelegations(ctx sdk.Context,
delegator sdk.Address, maxRetrieve int16) (bonds []Delegation) {
return v.keeper.GetDelegations(ctx, delegator, maxRetrieve)
}

View File

@ -1,86 +0,0 @@
package stake
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// tests GetDelegation, GetDelegations
func TestViewSlashBond(t *testing.T) {
ctx, _, keeper := createTestInput(t, false, sdk.NewInt(0))
//construct the validators
amts := []int64{9, 8, 7}
var validators [3]Validator
for i, amt := range amts {
validators[i] = Validator{
Owner: addrVals[i],
PubKey: pks[i],
PoolShares: NewUnbondedShares(sdk.NewRat(amt)),
DelegatorShares: sdk.NewRat(amt),
}
}
// first add a validators[0] to delegate too
keeper.updateValidator(ctx, validators[0])
bond1to1 := Delegation{
DelegatorAddr: addrDels[0],
ValidatorAddr: addrVals[0],
Shares: sdk.NewRat(9),
}
viewSlashKeeper := NewViewSlashKeeper(keeper)
// check the empty keeper first
_, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.False(t, found)
// set and retrieve a record
keeper.setDelegation(ctx, bond1to1)
resBond, found := viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.equal(resBond))
// modify a records, save, and retrieve
bond1to1.Shares = sdk.NewRat(99)
keeper.setDelegation(ctx, bond1to1)
resBond, found = viewSlashKeeper.GetDelegation(ctx, addrDels[0], addrVals[0])
assert.True(t, found)
assert.True(t, bond1to1.equal(resBond))
// add some more records
keeper.updateValidator(ctx, validators[1])
keeper.updateValidator(ctx, validators[2])
bond1to2 := Delegation{addrDels[0], addrVals[1], sdk.NewRat(9), 0}
bond1to3 := Delegation{addrDels[0], addrVals[2], sdk.NewRat(9), 1}
bond2to1 := Delegation{addrDels[1], addrVals[0], sdk.NewRat(9), 2}
bond2to2 := Delegation{addrDels[1], addrVals[1], sdk.NewRat(9), 3}
bond2to3 := Delegation{addrDels[1], addrVals[2], sdk.NewRat(9), 4}
keeper.setDelegation(ctx, bond1to2)
keeper.setDelegation(ctx, bond1to3)
keeper.setDelegation(ctx, bond2to1)
keeper.setDelegation(ctx, bond2to2)
keeper.setDelegation(ctx, bond2to3)
// test all bond retrieve capabilities
resBonds := viewSlashKeeper.GetDelegations(ctx, addrDels[0], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond1to1.equal(resBonds[0]))
assert.True(t, bond1to2.equal(resBonds[1]))
assert.True(t, bond1to3.equal(resBonds[2]))
resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 3)
require.Equal(t, 3, len(resBonds))
resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[0], 2)
require.Equal(t, 2, len(resBonds))
resBonds = viewSlashKeeper.GetDelegations(ctx, addrDels[1], 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bond2to1.equal(resBonds[0]))
assert.True(t, bond2to2.equal(resBonds[1]))
assert.True(t, bond2to3.equal(resBonds[2]))
}

View File

@ -1,20 +0,0 @@
package stake
import (
"github.com/cosmos/cosmos-sdk/wire"
)
// Register concrete types on wire codec
func RegisterWire(cdc *wire.Codec) {
cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil)
cdc.RegisterConcrete(MsgEditValidator{}, "cosmos-sdk/MsgEditValidator", nil)
cdc.RegisterConcrete(MsgDelegate{}, "cosmos-sdk/MsgDelegate", nil)
cdc.RegisterConcrete(MsgUnbond{}, "cosmos-sdk/MsgUnbond", nil)
}
var msgCdc = wire.NewCodec()
func init() {
RegisterWire(msgCdc)
wire.RegisterCrypto(msgCdc)
}