Merge branch 'develop' into bucky/gaiadebug-bech32

This commit is contained in:
Rigel 2018-06-27 19:32:24 -04:00 committed by GitHub
commit 9dbdb1a55f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
98 changed files with 6886 additions and 3887 deletions

View File

@ -11,6 +11,15 @@ BREAKING CHANGES
* Signers of a transaction now only sign over their account and sequence number
* 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
@ -20,18 +29,41 @@ FEATURES
* Supported proposal types: just binary (pass/fail) TextProposals for now
* Proposals need deposits to be votable; deposits are burned if proposal fails
* Delegators delegate votes to validator by default but can override (for their stake)
* Add benchmarks for signing and delivering a block with a single bank transaction
* Run with `cd x/bank && go test --bench=.`
* [tools] make get_tools installs tendermint's linter, and gometalinter
* [tools] Switch gometalinter to the stable version
* [tools] Add checking for misspellings and for incorrectly formatted files in circle CI
* [server] Default config now creates a profiler at port 6060, and increase p2p send/recv rates
* [tests] Add WaitForNextNBlocksTM helper method
* [types] Switches internal representation of Int/Uint/Rat to use pointers
* [gaiad] unsafe_reset_all now resets addrbook.json
* [democoin] add x/oracle, x/assoc
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
* Fixed bug where chain ID wasn't passed properly in x/bank REST handler
* Fixed bug where chain ID wasn't passed properly in x/bank REST handler, removed Viper hack from ante handler
* Fixed bug where `democli account` didn't decode the account data correctly
* \#1343 - fixed unnecessary parallelism in CI
* \#1367 - set ChainID in InitChain
* \#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

12
Gopkg.lock generated
View File

@ -256,7 +256,7 @@
"leveldb/table",
"leveldb/util"
]
revision = "e2150783cd35f5b607daca48afd8c57ec54cc995"
revision = "0d5a0ceb10cf9ab89fdd744cc8c50a83134f6697"
[[projects]]
name = "github.com/tendermint/abci"
@ -350,7 +350,6 @@
revision = "696e8c6f9e950eec15f150f314d2dd9ddf6bc601"
[[projects]]
branch = "develop"
name = "github.com/tendermint/tmlibs"
packages = [
"autofile",
@ -373,6 +372,7 @@
packages = [
"blowfish",
"curve25519",
"internal/subtle",
"nacl/box",
"nacl/secretbox",
"openpgp/armor",
@ -381,7 +381,7 @@
"ripemd160",
"salsa20/salsa"
]
revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
[[projects]]
branch = "master"
@ -395,13 +395,13 @@
"internal/timeseries",
"trace"
]
revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
revision = "afe8f62b1d6bbd81f31868121a50b06d8188e1f9"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "bff228c7b664c5fce602223a05fb708fd8654986"
revision = "a200a19cb90b19de298170992778b1fda7217bd6"
[[projects]]
name = "golang.org/x/text"
@ -462,6 +462,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "d02a24bcfd8bded901e1b154e19b81ff797d3921046ede19d1d11eed61e871e7"
inputs-digest = "8ad5e4b90e57805024944a6ee5eed246f109c18724d453093e82bac1469dbd9e"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -58,7 +58,7 @@
[[constraint]]
name = "github.com/tendermint/go-crypto"
version = "~0.6.2"
version = "=0.6.2"
[[constraint]]
name = "github.com/tendermint/go-amino"
@ -66,7 +66,7 @@
[[constraint]]
name = "github.com/tendermint/iavl"
version = "0.8.0-rc0"
version = "=0.8.0-rc0"
[[constraint]]
name = "github.com/tendermint/tendermint"
@ -74,7 +74,7 @@
[[override]]
name = "github.com/tendermint/tmlibs"
branch = "develop"
revision = "0c98d10b4ffbd87978d79c160e835b3d3df241ec"
# this got updated and broke, so locked to an old working commit ...
[[override]]

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

@ -224,9 +224,6 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
}
*/
// initialize Check state
app.setCheckState(abci.Header{})
return nil
}
@ -287,12 +284,13 @@ func (app *BaseApp) SetOption(req abci.RequestSetOption) (res abci.ResponseSetOp
// Implements ABCI
// InitChain runs the initialization logic directly on the CommitMultiStore and commits it.
func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitChain) {
// Initialize the deliver state and check state with ChainID and run initChain
app.setDeliverState(abci.Header{ChainID: req.ChainId})
app.setCheckState(abci.Header{ChainID: req.ChainId})
if app.initChainer == nil {
return
}
// Initialize the deliver state and run initChain
app.setDeliverState(abci.Header{})
app.initChainer(app.deliverState.ctx, req) // no error
// NOTE: we don't commit, but BeginBlock for block 1
@ -379,10 +377,15 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
// Initialize the DeliverTx state.
// If this is the first block, it should already
// be initialized in InitChain. It may also be nil
// if this is a test and InitChain was never called.
// be initialized in InitChain.
// Otherwise app.deliverState will be nil, since it
// is reset on Commit.
if app.deliverState == nil {
app.setDeliverState(req.Header)
} else {
// In the first block, app.deliverState.ctx will already be initialized
// by InitChain. Context is now updated with Header information.
app.deliverState.ctx = app.deliverState.ctx.WithBlockHeader(req.Header)
}
if app.beginBlocker != nil {
res = app.beginBlocker(app.deliverState.ctx, req)
@ -598,6 +601,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

@ -3,7 +3,6 @@ package baseapp
import (
"encoding/json"
"fmt"
"github.com/cosmos/cosmos-sdk/x/bank"
"os"
"testing"
@ -20,6 +19,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
)
func defaultLogger() log.Logger {
@ -202,7 +202,15 @@ func TestInitChainer(t *testing.T) {
// set initChainer and try again - should see the value
app.SetInitChainer(initChainer)
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}"), ChainId: "test-chain-id"}) // must have valid JSON genesis file, even if empty
// assert that chainID is set correctly in InitChain
chainID := app.deliverState.ctx.ChainID()
assert.Equal(t, "test-chain-id", chainID, "ChainID in deliverState not set correctly in InitChain")
chainID = app.checkState.ctx.ChainID()
assert.Equal(t, "test-chain-id", chainID, "ChainID in checkState not set correctly in InitChain")
app.Commit()
res = app.Query(query)
assert.Equal(t, value, res.Value)
@ -378,13 +386,15 @@ func TestSimulateTx(t *testing.T) {
return ttx, nil
})
app.InitChain(abci.RequestInitChain{})
nBlocks := 3
for blockN := 0; blockN < nBlocks; blockN++ {
// block1
header.Height = int64(blockN + 1)
app.BeginBlock(abci.RequestBeginBlock{Header: header})
result := app.Simulate(tx)
require.Equal(t, result.Code, sdk.ABCICodeOK)
require.Equal(t, result.Code, sdk.ABCICodeOK, result.Log)
require.Equal(t, int64(80), result.GasUsed)
counter--
encoded, err := json.Marshal(tx)
@ -397,8 +407,8 @@ func TestSimulateTx(t *testing.T) {
require.Equal(t, queryResult.Code, uint32(sdk.ABCICodeOK))
var res sdk.Result
app.cdc.MustUnmarshalBinary(queryResult.Value, &res)
require.Equal(t, sdk.ABCICodeOK, res.Code)
require.Equal(t, int64(160), res.GasUsed)
require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log)
require.Equal(t, int64(160), res.GasUsed, res.Log)
app.EndBlock(abci.RequestEndBlock{})
app.Commit()
}

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

@ -57,7 +57,7 @@ Start Nodes
Now that we've initialized the chains, we can start both nodes:
NOTE: each command below must be started in seperate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided.
NOTE: each command below must be started in separate terminal windows. Alternatively, to run this testnet across multiple machines, you'd replace the ``seeds = "0.0.0.0"`` in ``~/.gaia2.config.toml`` with the IP of the first node, and could skip the modifications we made to the config file above because port conflicts would be avoided.
::

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

@ -167,7 +167,7 @@ startUnbonding(tx TxStartUnbonding):
### TxCompleteUnbonding
Complete the unbonding and transfer the coins to the delegate. Perform any
slashing that occured during the unbonding period.
slashing that occurred during the unbonding period.
```golang
type TxUnbondingComplete struct {

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

@ -2,7 +2,9 @@ package app
import (
"os"
"fmt"
"testing"
"encoding/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -12,6 +14,7 @@ import (
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/stake"
gen "github.com/cosmos/cosmos-sdk/x/stake/types"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
@ -71,6 +74,16 @@ func TestGenesis(t *testing.T) {
// reload app and ensure the account is still there
bapp = NewBasecoinApp(logger, db)
// Initialize stake data with default genesis state
stakedata := gen.DefaultGenesisState()
genState, err := json.Marshal(stakedata)
if err != nil {
panic(err)
}
// InitChain with default stake data. Initializes deliverState and checkState context
bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte(fmt.Sprintf("{\"stake\": %s}", string(genState)))})
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)

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

@ -55,6 +55,7 @@ func TestGenesis(t *testing.T) {
// reload app and ensure the account is still there
bapp = NewDemocoinApp(logger, db)
bapp.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")})
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)

View File

@ -0,0 +1,122 @@
package mock
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/go-crypto"
)
// Validator implements sdk.Validator
type Validator struct {
Address sdk.Address
Power sdk.Rat
}
// Implements sdk.Validator
func (v Validator) GetStatus() sdk.BondStatus {
return sdk.Bonded
}
// Implements sdk.Validator
func (v Validator) GetOwner() sdk.Address {
return v.Address
}
// Implements sdk.Validator
func (v Validator) GetPubKey() crypto.PubKey {
return nil
}
// Implements sdk.Validator
func (v Validator) GetPower() sdk.Rat {
return v.Power
}
// Implements sdk.Validator
func (v Validator) GetDelegatorShares() sdk.Rat {
return sdk.ZeroRat()
}
// Implements sdk.Validator
func (v Validator) GetBondHeight() int64 {
return 0
}
// Implements sdk.Validator
func (v Validator) GetMoniker() string {
return ""
}
// Implements sdk.Validator
type ValidatorSet struct {
Validators []Validator
}
// IterateValidators implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidators(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
for i, val := range vs.Validators {
if fn(int64(i), val) {
break
}
}
}
// IterateValidatorsBonded implements sdk.ValidatorSet
func (vs *ValidatorSet) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, Validator sdk.Validator) bool) {
vs.IterateValidators(ctx, fn)
}
// Validator implements sdk.ValidatorSet
func (vs *ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) sdk.Validator {
for _, val := range vs.Validators {
if bytes.Equal(val.Address, addr) {
return val
}
}
return nil
}
// TotalPower implements sdk.ValidatorSet
func (vs *ValidatorSet) TotalPower(ctx sdk.Context) sdk.Rat {
res := sdk.ZeroRat()
for _, val := range vs.Validators {
res = res.Add(val.Power)
}
return res
}
// Helper function for adding new validator
func (vs *ValidatorSet) AddValidator(val Validator) {
vs.Validators = append(vs.Validators, val)
}
// Helper function for removing exsting validator
func (vs *ValidatorSet) RemoveValidator(addr sdk.Address) {
pos := -1
for i, val := range vs.Validators {
if bytes.Equal(val.Address, addr) {
pos = i
break
}
}
if pos == -1 {
return
}
vs.Validators = append(vs.Validators[:pos], vs.Validators[pos+1:]...)
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Slash(ctx sdk.Context, pubkey crypto.PubKey, height int64, amt sdk.Rat) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Revoke(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}
// Implements sdk.ValidatorSet
func (vs *ValidatorSet) Unrevoke(ctx sdk.Context, pubkey crypto.PubKey) {
panic("not implemented")
}

View File

@ -0,0 +1,107 @@
package assoc
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// ValidatorSet defines
type ValidatorSet struct {
sdk.ValidatorSet
key sdk.KVStoreGetter
cdc *wire.Codec
maxAssoc int
addrLen int
}
var _ sdk.ValidatorSet = ValidatorSet{}
// NewValidatorSet returns new ValidatorSet with underlying ValidatorSet
func NewValidatorSet(cdc *wire.Codec, key sdk.KVStoreGetter, valset sdk.ValidatorSet, maxAssoc int, addrLen int) ValidatorSet {
if maxAssoc < 0 || addrLen < 0 {
panic("Cannot use negative integer for NewValidatorSet")
}
return ValidatorSet{
ValidatorSet: valset,
key: key,
cdc: cdc,
maxAssoc: maxAssoc,
addrLen: addrLen,
}
}
// Implements sdk.ValidatorSet
func (valset ValidatorSet) Validator(ctx sdk.Context, addr sdk.Address) (res sdk.Validator) {
store := valset.key.KVStore(ctx)
base := store.Get(GetBaseKey(addr))
res = valset.ValidatorSet.Validator(ctx, base)
if res == nil {
res = valset.ValidatorSet.Validator(ctx, addr)
}
return
}
// GetBaseKey :: sdk.Address -> sdk.Address
func GetBaseKey(addr sdk.Address) []byte {
return append([]byte{0x00}, addr...)
}
// GetAssocPrefix :: sdk.Address -> (sdk.Address -> byte)
func GetAssocPrefix(base sdk.Address) []byte {
return append([]byte{0x01}, base...)
}
// GetAssocKey :: (sdk.Address, sdk.Address) -> byte
func GetAssocKey(base sdk.Address, assoc sdk.Address) []byte {
return append(append([]byte{0x01}, base...), assoc...)
}
// Associate associates new address with validator address
func (valset ValidatorSet) Associate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
store := valset.key.KVStore(ctx)
// If someone already owns the associated address
if store.Get(GetBaseKey(assoc)) != nil {
return false
}
store.Set(GetBaseKey(assoc), base)
store.Set(GetAssocKey(base, assoc), []byte{0x00})
return true
}
// Dissociate removes association between addresses
func (valset ValidatorSet) Dissociate(ctx sdk.Context, base sdk.Address, assoc sdk.Address) bool {
if len(base) != valset.addrLen || len(assoc) != valset.addrLen {
return false
}
store := valset.key.KVStore(ctx)
// No associated address found for given validator
if !bytes.Equal(store.Get(GetBaseKey(assoc)), base) {
return false
}
store.Delete(GetBaseKey(assoc))
store.Delete(GetAssocKey(base, assoc))
return true
}
// Associations returns all associated addresses with a validator
func (valset ValidatorSet) Associations(ctx sdk.Context, base sdk.Address) (res []sdk.Address) {
store := valset.key.KVStore(ctx)
res = make([]sdk.Address, valset.maxAssoc)
iter := sdk.KVStorePrefixIterator(store, GetAssocPrefix(base))
i := 0
for ; iter.Valid(); iter.Next() {
key := iter.Key()
res[i] = key[len(key)-valset.addrLen:]
i++
}
return res[:i]
}

View File

@ -0,0 +1,71 @@
package assoc
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
func defaultContext(key sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
func TestValidatorSet(t *testing.T) {
key := sdk.NewKVStoreKey("test")
ctx := defaultContext(key)
addr1 := []byte("addr1")
addr2 := []byte("addr2")
base := &mock.ValidatorSet{[]mock.Validator{
{addr1, sdk.NewRat(1)},
{addr2, sdk.NewRat(2)},
}}
valset := NewValidatorSet(wire.NewCodec(), sdk.NewPrefixStoreGetter(key, []byte("assoc")), base, 1, 5)
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assoc1 := []byte("asso1")
assoc2 := []byte("asso2")
assert.True(t, valset.Associate(ctx, addr1, assoc1))
assert.True(t, valset.Associate(ctx, addr2, assoc2))
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, assoc1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, assoc2))
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assocs := valset.Associations(ctx, addr1)
assert.Equal(t, 1, len(assocs))
assert.True(t, bytes.Equal(assoc1, assocs[0]))
assert.False(t, valset.Associate(ctx, addr1, assoc2))
assert.False(t, valset.Associate(ctx, addr2, assoc1))
valset.Dissociate(ctx, addr1, assoc1)
valset.Dissociate(ctx, addr2, assoc2)
assert.Equal(t, base.Validator(ctx, addr1), valset.Validator(ctx, addr1))
assert.Equal(t, base.Validator(ctx, addr2), valset.Validator(ctx, addr2))
assert.Nil(t, valset.Validator(ctx, assoc1))
assert.Nil(t, valset.Validator(ctx, assoc2))
}

View File

@ -0,0 +1,58 @@
# Oracle Module
`x/oracle` provides a way to receive external information(real world price, events from other chains, etc.) with validators' vote. Each validator make transaction which contains those informations, and Oracle aggregates them until the supermajority signed on it. After then, Oracle sends the information to the actual module that processes the information, and prune the votes from the state.
## Integration
See `x/oracle/oracle_test.go` for the code that using Oracle
To use Oracle in your module, first define a `payload`. It should implement `oracle.Payload` and contain nessesary information for your module. Including nonce is recommended.
```go
type MyPayload struct {
Data int
Nonce int
}
```
When you write a payload, its `.Type()` should return same name with your module is registered on the router. It is because `oracle.Msg` inherits `.Type()` from its embedded payload and it should be handled on the user modules.
Then route every incoming `oracle.Msg` to `oracle.Keeper.Handler()` with the function that implements `oracle.Handler`.
```go
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case oracle.Msg:
return keeper.oracle.Handle(ctx sdk.Context, p oracle.Payload) sdk.Error {
switch p := p.(type) {
case MyPayload:
return handleMyPayload(ctx, keeper, p)
}
}
}
}
}
```
In the previous example, the keeper has an `oracle.Keeper`. `oracle.Keeper`s are generated by `NewKeeper`.
```go
func NewKeeper(key sdk.StoreKey, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper {
return Keeper {
cdc: cdc,
key: key,
// ValidatorSet to get validators infor
valset: valset,
// The keeper will pass payload
// when more than 2/3 signed on it
supermaj: supermaj,
// The keeper will prune votes after 100 blocks from last sign
timeout: timeout,
}
}
```
Now the validators can send `oracle.Msg`s with `MyPayload` when they want to witness external events.

View File

@ -0,0 +1,31 @@
package oracle
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Oracle errors reserve 1101-1199
const (
CodeNotValidator sdk.CodeType = 1101
CodeAlreadyProcessed sdk.CodeType = 1102
CodeAlreadySigned sdk.CodeType = 1103
CodeUnknownRequest sdk.CodeType = sdk.CodeUnknownRequest
)
// ----------------------------------------
// Error constructors
// ErrNotValidator called when the signer of a Msg is not a validator
func ErrNotValidator(codespace sdk.CodespaceType, address sdk.Address) sdk.Error {
return sdk.NewError(codespace, CodeNotValidator, address.String())
}
// ErrAlreadyProcessed called when a payload is already processed
func ErrAlreadyProcessed(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadyProcessed, "")
}
// ErrAlreadySigned called when the signer is trying to double signing
func ErrAlreadySigned(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeAlreadySigned, "")
}

View File

@ -0,0 +1,105 @@
package oracle
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Handler handles payload after it passes voting process
type Handler func(ctx sdk.Context, p Payload) sdk.Error
func (keeper Keeper) update(ctx sdk.Context, val sdk.Validator, valset sdk.ValidatorSet, p Payload, info Info) Info {
info.Power = info.Power.Add(val.GetPower())
// Return if the voted power is not bigger than required power
totalPower := valset.TotalPower(ctx)
requiredPower := totalPower.Mul(keeper.supermaj)
if !info.Power.GT(requiredPower) {
return info
}
// Check if the validators hash has been changed during the vote process
// and recalculate voted power
hash := ctx.BlockHeader().ValidatorsHash
if !bytes.Equal(hash, info.Hash) {
info.Power = sdk.ZeroRat()
info.Hash = hash
prefix := GetSignPrefix(p, keeper.cdc)
store := keeper.key.KVStore(ctx)
iter := sdk.KVStorePrefixIterator(store, prefix)
for ; iter.Valid(); iter.Next() {
if valset.Validator(ctx, iter.Value()) != nil {
store.Delete(iter.Key())
continue
}
info.Power = info.Power.Add(val.GetPower())
}
if !info.Power.GT(totalPower.Mul(keeper.supermaj)) {
return info
}
}
info.Status = Processed
return info
}
// Handle is used by other modules to handle Msg
func (keeper Keeper) Handle(h Handler, ctx sdk.Context, o Msg, codespace sdk.CodespaceType) sdk.Result {
valset := keeper.valset
signer := o.Signer
payload := o.Payload
// Check the oracle is not in process
info := keeper.Info(ctx, payload)
if info.Status != Pending {
return ErrAlreadyProcessed(codespace).Result()
}
// Check if it is reporting timeout
now := ctx.BlockHeight()
if now > info.LastSigned+keeper.timeout {
info = Info{Status: Timeout}
keeper.setInfo(ctx, payload, info)
keeper.clearSigns(ctx, payload)
return sdk.Result{}
}
info.LastSigned = ctx.BlockHeight()
// Check the signer is a validater
val := valset.Validator(ctx, signer)
if val == nil {
return ErrNotValidator(codespace, signer).Result()
}
// Check double signing
if keeper.signed(ctx, payload, signer) {
return ErrAlreadySigned(codespace).Result()
}
keeper.sign(ctx, payload, signer)
info = keeper.update(ctx, val, valset, payload, info)
if info.Status == Processed {
info = Info{Status: Processed}
}
keeper.setInfo(ctx, payload, info)
if info.Status == Processed {
keeper.clearSigns(ctx, payload)
cctx, write := ctx.CacheContext()
err := h(cctx, payload)
if err != nil {
return sdk.Result{
Code: sdk.ABCICodeOK,
Log: err.ABCILog(),
}
}
write()
}
return sdk.Result{}
}

View File

@ -0,0 +1,111 @@
package oracle
import (
"github.com/cosmos/cosmos-sdk/wire"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keeper of the oracle store
type Keeper struct {
key sdk.KVStoreGetter
cdc *wire.Codec
valset sdk.ValidatorSet
supermaj sdk.Rat
timeout int64
}
// NewKeeper constructs a new keeper
func NewKeeper(key sdk.KVStoreGetter, cdc *wire.Codec, valset sdk.ValidatorSet, supermaj sdk.Rat, timeout int64) Keeper {
if timeout < 0 {
panic("Timeout should not be negative")
}
return Keeper{
key: key,
cdc: cdc,
valset: valset,
supermaj: supermaj,
timeout: timeout,
}
}
// InfoStatus - current status of an Info
type InfoStatus int8
// Define InfoStatus
const (
Pending = InfoStatus(iota)
Processed
Timeout
)
// Info for each payload
type Info struct {
Power sdk.Rat
Hash []byte
LastSigned int64
Status InfoStatus
}
// EmptyInfo construct an empty Info
func EmptyInfo(ctx sdk.Context) Info {
return Info{
Power: sdk.ZeroRat(),
Hash: ctx.BlockHeader().ValidatorsHash,
LastSigned: ctx.BlockHeight(),
Status: Pending,
}
}
// Info returns the information about a payload
func (keeper Keeper) Info(ctx sdk.Context, p Payload) (res Info) {
store := keeper.key.KVStore(ctx)
key := GetInfoKey(p, keeper.cdc)
bz := store.Get(key)
if bz == nil {
return EmptyInfo(ctx)
}
keeper.cdc.MustUnmarshalBinary(bz, &res)
return
}
func (keeper Keeper) setInfo(ctx sdk.Context, p Payload, info Info) {
store := keeper.key.KVStore(ctx)
key := GetInfoKey(p, keeper.cdc)
bz := keeper.cdc.MustMarshalBinary(info)
store.Set(key, bz)
}
func (keeper Keeper) sign(ctx sdk.Context, p Payload, signer sdk.Address) {
store := keeper.key.KVStore(ctx)
key := GetSignKey(p, signer, keeper.cdc)
store.Set(key, signer)
}
func (keeper Keeper) signed(ctx sdk.Context, p Payload, signer sdk.Address) bool {
store := keeper.key.KVStore(ctx)
key := GetSignKey(p, signer, keeper.cdc)
return store.Has(key)
}
func (keeper Keeper) clearSigns(ctx sdk.Context, p Payload) {
store := keeper.key.KVStore(ctx)
prefix := GetSignPrefix(p, keeper.cdc)
iter := sdk.KVStorePrefixIterator(store, prefix)
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
iter.Close()
}

View File

@ -0,0 +1,23 @@
package oracle
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// GetInfoKey returns the key for OracleInfo
func GetInfoKey(p Payload, cdc *wire.Codec) []byte {
bz := cdc.MustMarshalBinary(p)
return append([]byte{0x00}, bz...)
}
// GetSignPrefix returns the prefix for signs
func GetSignPrefix(p Payload, cdc *wire.Codec) []byte {
bz := cdc.MustMarshalBinary(p)
return append([]byte{0x01}, bz...)
}
// GetSignKey returns the key for sign
func GetSignKey(p Payload, signer sdk.Address, cdc *wire.Codec) []byte {
return append(GetSignPrefix(p, cdc), signer...)
}

View File

@ -0,0 +1,221 @@
package oracle
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/democoin/mock"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
func defaultContext(keys ...sdk.StoreKey) sdk.Context {
db := dbm.NewMemDB()
cms := store.NewCommitMultiStore(db)
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
}
cms.LoadLatestVersion()
ctx := sdk.NewContext(cms, abci.Header{}, false, nil)
return ctx
}
type seqOracle struct {
Seq int
Nonce int
}
func (o seqOracle) Type() string {
return "seq"
}
func (o seqOracle) ValidateBasic() sdk.Error {
return nil
}
func makeCodec() *wire.Codec {
var cdc = wire.NewCodec()
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
cdc.RegisterConcrete(Msg{}, "test/Oracle", nil)
cdc.RegisterInterface((*Payload)(nil), nil)
cdc.RegisterConcrete(seqOracle{}, "test/oracle/seqOracle", nil)
return cdc
}
func seqHandler(ork Keeper, key sdk.StoreKey, codespace sdk.CodespaceType) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case Msg:
return ork.Handle(func(ctx sdk.Context, p Payload) sdk.Error {
switch p := p.(type) {
case seqOracle:
return handleSeqOracle(ctx, key, p)
default:
return sdk.ErrUnknownRequest("")
}
}, ctx, msg, codespace)
default:
return sdk.ErrUnknownRequest("").Result()
}
}
}
func getSequence(ctx sdk.Context, key sdk.StoreKey) int {
store := ctx.KVStore(key)
seqbz := store.Get([]byte("seq"))
var seq int
if seqbz == nil {
seq = 0
} else {
wire.NewCodec().MustUnmarshalBinary(seqbz, &seq)
}
return seq
}
func handleSeqOracle(ctx sdk.Context, key sdk.StoreKey, o seqOracle) sdk.Error {
store := ctx.KVStore(key)
seq := getSequence(ctx, key)
if seq != o.Seq {
return sdk.NewError(sdk.CodespaceUndefined, 1, "")
}
bz := wire.NewCodec().MustMarshalBinary(seq + 1)
store.Set([]byte("seq"), bz)
return nil
}
func TestOracle(t *testing.T) {
cdc := makeCodec()
addr1 := []byte("addr1")
addr2 := []byte("addr2")
addr3 := []byte("addr3")
addr4 := []byte("addr4")
valset := &mock.ValidatorSet{[]mock.Validator{
mock.Validator{addr1, sdk.NewRat(7)},
mock.Validator{addr2, sdk.NewRat(7)},
mock.Validator{addr3, sdk.NewRat(1)},
}}
key := sdk.NewKVStoreKey("testkey")
ctx := defaultContext(key)
bz, err := json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
ork := NewKeeper(sdk.NewPrefixStoreGetter(key, []byte("oracle")), cdc, valset, sdk.NewRat(2, 3), 100)
h := seqHandler(ork, key, sdk.CodespaceUndefined)
// Nonmock.Validator signed, transaction failed
msg := Msg{seqOracle{0, 0}, []byte("randomguy")}
res := h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// Double signed, transaction failed
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 0, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{100, 1}, addr1}
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed but payload is invalid
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.NotEqual(t, "", res.Log)
assert.Equal(t, 1, getSequence(ctx, key))
// Already processed, transaction failed
msg.Signer = addr3
res = h(ctx, msg)
assert.False(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Should handle mock.Validator set change
valset.AddValidator(mock.Validator{addr4, sdk.NewRat(12)})
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg = Msg{seqOracle{1, 2}, addr1}
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// Less than 2/3 signed, msg not processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 1, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr4
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// Should handle mock.Validator set change while oracle process is happening
msg = Msg{seqOracle{2, 3}, addr4}
// Less than 2/3 signed, msg not processed
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// Signed mock.Validator is kicked out
valset.RemoveValidator(addr4)
bz, err = json.Marshal(valset)
require.Nil(t, err)
ctx = ctx.WithBlockHeader(abci.Header{ValidatorsHash: bz})
// Less than 2/3 signed, msg not processed
msg.Signer = addr1
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 2, getSequence(ctx, key))
// More than 2/3 signed, msg processed
msg.Signer = addr2
res = h(ctx, msg)
assert.True(t, res.IsOK())
assert.Equal(t, 3, getSequence(ctx, key))
}

View File

@ -0,0 +1,33 @@
package oracle
import (
"encoding/json"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Msg - struct for voting on payloads
type Msg struct {
Payload
Signer sdk.Address
}
// GetSignBytes implements sdk.Msg
func (msg Msg) GetSignBytes() []byte {
bz, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return bz
}
// GetSigners implements sdk.Msg
func (msg Msg) GetSigners() []sdk.Address {
return []sdk.Address{msg.Signer}
}
// Payload defines inner data for actual execution
type Payload interface {
Type() string
ValidateBasic() sdk.Error
}

View File

@ -46,14 +46,24 @@ func (s prefixStore) Prefix(prefix []byte) KVStore {
// Implements KVStore
func (s prefixStore) Iterator(start, end []byte) Iterator {
if end == nil {
end = sdk.PrefixEndBytes(s.prefix)
} else {
end = append(s.prefix, end...)
}
return prefixIterator{
prefix: s.prefix,
iter: s.store.Iterator(start, end),
iter: s.store.Iterator(append(s.prefix, start...), end),
}
}
// Implements KVStore
func (s prefixStore) ReverseIterator(start, end []byte) Iterator {
if end == nil {
end = sdk.PrefixEndBytes(s.prefix)
} else {
end = append(s.prefix, end...)
}
return prefixIterator{
prefix: s.prefix,
iter: s.store.ReverseIterator(start, end),

View File

@ -15,13 +15,19 @@ import (
// Wait for the next tendermint block from the Tendermint RPC
// on localhost
func WaitForNextHeightTM(port string) {
WaitForNextNBlocksTM(1, port)
}
// Wait for N tendermint blocks to pass using the Tendermint RPC
// on localhost
func WaitForNextNBlocksTM(n int64, port string) {
url := fmt.Sprintf("http://localhost:%v", port)
cl := tmclient.NewHTTP(url, "/websocket")
resBlock, err := cl.Block(nil)
if err != nil {
panic(err)
}
waitForHeightTM(resBlock.Block.Height+1, url)
waitForHeightTM(resBlock.Block.Height+n, url)
}
// Wait for the given height from the Tendermint RPC

View File

@ -80,7 +80,7 @@ func MustBech32ifyValPub(pub crypto.PubKey) string {
// create an Address from a string
func GetAccAddressHex(address string) (addr Address, err error) {
if len(address) == 0 {
return addr, errors.New("must use provide address")
return addr, errors.New("decoding bech32 address failed: must provide an address")
}
bz, err := hex.DecodeString(address)
if err != nil {
@ -116,7 +116,7 @@ func GetAccPubKeyBech32(address string) (pk crypto.PubKey, err error) {
// create an Address from a hex string
func GetValAddressHex(address string) (addr Address, err error) {
if len(address) == 0 {
return addr, errors.New("must use provide address")
return addr, errors.New("decoding bech32 address failed: must provide an address")
}
bz, err := hex.DecodeString(address)
if err != nil {
@ -152,7 +152,7 @@ func GetValPubKeyBech32(pubkey string) (pk crypto.PubKey, err error) {
// decode a bytestring from a bech32-encoded string
func GetFromBech32(bech32str, prefix string) ([]byte, error) {
if len(bech32str) == 0 {
return nil, errors.New("must provide non-empty string")
return nil, errors.New("decoding bech32 address failed: must provide an address")
}
hrp, bz, err := bech32.DecodeAndConvert(bech32str)
if err != nil {

View File

@ -60,17 +60,17 @@ func unmarshalJSON(i *big.Int, bz []byte) error {
// Checks overflow, underflow and division by zero
// Exists in range from -(2^255-1) to 2^255-1
type Int struct {
i big.Int
i *big.Int
}
// BigInt converts Int to big.Int
func (i Int) BigInt() *big.Int {
return new(big.Int).Set(&(i.i))
return new(big.Int).Set(i.i)
}
// NewInt constructs Int from int64
func NewInt(n int64) Int {
return Int{*big.NewInt(n)}
return Int{big.NewInt(n)}
}
// NewIntFromBigInt constructs Int from big.Int
@ -78,7 +78,7 @@ func NewIntFromBigInt(i *big.Int) Int {
if i.BitLen() > 255 {
panic("NewIntFromBigInt() out of bound")
}
return Int{*i}
return Int{i}
}
// NewIntFromString constructs Int from string
@ -92,7 +92,7 @@ func NewIntFromString(s string) (res Int, ok bool) {
ok = false
return
}
return Int{*i}, true
return Int{i}, true
}
// NewIntWithDecimal constructs Int with decimal
@ -103,14 +103,14 @@ func NewIntWithDecimal(n int64, dec int) Int {
if i.BitLen() > 255 {
panic("NewIntWithDecimal() out of bound")
}
return Int{*i}
return Int{i}
}
// ZeroInt returns Int value with zero
func ZeroInt() Int { return Int{*big.NewInt(0)} }
func ZeroInt() Int { return Int{big.NewInt(0)} }
// OneInt returns Int value with one
func OneInt() Int { return Int{*big.NewInt(1)} }
func OneInt() Int { return Int{big.NewInt(1)} }
// Int64 converts Int to int64
// Panics if the value is out of range
@ -133,22 +133,22 @@ func (i Int) Sign() int {
// Equal compares two Ints
func (i Int) Equal(i2 Int) bool {
return equal(&(i.i), &(i2.i))
return equal(i.i, i2.i)
}
// GT returns true if first Int is greater than second
func (i Int) GT(i2 Int) bool {
return gt((&i.i), &(i2.i))
return gt(i.i, i2.i)
}
// LT returns true if first Int is lesser than second
func (i Int) LT(i2 Int) bool {
return lt((&i.i), &(i2.i))
return lt(i.i, i2.i)
}
// Add adds Int from another
func (i Int) Add(i2 Int) (res Int) {
res = Int{*add(&(i.i), &(i2.i))}
res = Int{add(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > 255 {
panic("Int overflow")
@ -163,7 +163,7 @@ func (i Int) AddRaw(i2 int64) Int {
// Sub subtracts Int from another
func (i Int) Sub(i2 Int) (res Int) {
res = Int{*sub(&(i.i), &(i2.i))}
res = Int{sub(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > 255 {
panic("Int overflow")
@ -182,7 +182,7 @@ func (i Int) Mul(i2 Int) (res Int) {
if i.i.BitLen()+i2.i.BitLen()-1 > 255 {
panic("Int overflow")
}
res = Int{*mul(&(i.i), &(i2.i))}
res = Int{mul(i.i, i2.i)}
// Check overflow if sign of both are same
if res.i.BitLen() > 255 {
panic("Int overflow")
@ -201,7 +201,7 @@ func (i Int) Div(i2 Int) (res Int) {
if i2.i.Sign() == 0 {
panic("Division by zero")
}
return Int{*div(&(i.i), &(i2.i))}
return Int{div(i.i, i2.i)}
}
// DivRaw divides Int with int64
@ -211,46 +211,58 @@ func (i Int) DivRaw(i2 int64) Int {
// Neg negates Int
func (i Int) Neg() (res Int) {
return Int{*neg(&(i.i))}
return Int{neg(i.i)}
}
// MarshalAmino defines custom encoding scheme
func (i Int) MarshalAmino() (string, error) {
return marshalAmino(&(i.i))
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return marshalAmino(i.i)
}
// UnmarshalAmino defines custom decoding scheme
func (i *Int) UnmarshalAmino(text string) error {
return unmarshalAmino(&(i.i), text)
if i.i == nil { // Necessary since default Int initialization has i.i as nil
i.i = new(big.Int)
}
return unmarshalAmino(i.i, text)
}
// MarshalJSON defines custom encoding scheme
func (i Int) MarshalJSON() ([]byte, error) {
return marshalJSON(&(i.i))
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return marshalJSON(i.i)
}
// UnmarshalJSON defines custom decoding scheme
func (i *Int) UnmarshalJSON(bz []byte) error {
return unmarshalJSON(&(i.i), bz)
if i.i == nil { // Necessary since default Int initialization has i.i as nil
i.i = new(big.Int)
}
return unmarshalJSON(i.i, bz)
}
// Int wraps integer with 256 bit range bound
// Checks overflow, underflow and division by zero
// Exists in range from 0 to 2^256-1
type Uint struct {
i big.Int
i *big.Int
}
// BigInt converts Uint to big.Unt
func (i Uint) BigInt() *big.Int {
return new(big.Int).Set(&(i.i))
return new(big.Int).Set(i.i)
}
// NewUint constructs Uint from int64
func NewUint(n uint64) Uint {
i := new(big.Int)
i.SetUint64(n)
return Uint{*i}
return Uint{i}
}
// NewUintFromBigUint constructs Uint from big.Uint
@ -259,7 +271,7 @@ func NewUintFromBigInt(i *big.Int) Uint {
if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 {
panic("Uint overflow")
}
return Uint{*i}
return Uint{i}
}
// NewUintFromString constructs Uint from string
@ -273,7 +285,7 @@ func NewUintFromString(s string) (res Uint, ok bool) {
ok = false
return
}
return Uint{*i}, true
return Uint{i}, true
}
// NewUintWithDecimal constructs Uint with decimal
@ -284,14 +296,14 @@ func NewUintWithDecimal(n int64, dec int) Uint {
if i.Sign() == -1 || i.Sign() == 1 && i.BitLen() > 256 {
panic("NewUintWithDecimal() out of bound")
}
return Uint{*i}
return Uint{i}
}
// ZeroUint returns Uint value with zero
func ZeroUint() Uint { return Uint{*big.NewInt(0)} }
func ZeroUint() Uint { return Uint{big.NewInt(0)} }
// OneUint returns Uint value with one
func OneUint() Uint { return Uint{*big.NewInt(1)} }
func OneUint() Uint { return Uint{big.NewInt(1)} }
// Uint64 converts Uint to uint64
// Panics if the value is out of range
@ -314,22 +326,22 @@ func (i Uint) Sign() int {
// Equal compares two Uints
func (i Uint) Equal(i2 Uint) bool {
return equal(&(i.i), &(i2.i))
return equal(i.i, i2.i)
}
// GT returns true if first Uint is greater than second
func (i Uint) GT(i2 Uint) bool {
return gt(&(i.i), &(i2.i))
return gt(i.i, i2.i)
}
// LT returns true if first Uint is lesser than second
func (i Uint) LT(i2 Uint) bool {
return lt(&(i.i), &(i2.i))
return lt(i.i, i2.i)
}
// Add adds Uint from another
func (i Uint) Add(i2 Uint) (res Uint) {
res = Uint{*add(&(i.i), &(i2.i))}
res = Uint{add(i.i, i2.i)}
// Check overflow
if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 {
panic("Uint overflow")
@ -344,7 +356,7 @@ func (i Uint) AddRaw(i2 uint64) Uint {
// Sub subtracts Uint from another
func (i Uint) Sub(i2 Uint) (res Uint) {
res = Uint{*sub(&(i.i), &(i2.i))}
res = Uint{sub(i.i, i2.i)}
// Check overflow
if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 {
panic("Uint overflow")
@ -363,7 +375,7 @@ func (i Uint) Mul(i2 Uint) (res Uint) {
if i.i.BitLen()+i2.i.BitLen()-1 > 256 {
panic("Uint overflow")
}
res = Uint{*mul(&(i.i), &(i2.i))}
res = Uint{mul(i.i, i2.i)}
// Check overflow
if res.Sign() == -1 || res.Sign() == 1 && res.i.BitLen() > 256 {
panic("Uint overflow")
@ -382,7 +394,7 @@ func (i Uint) Div(i2 Uint) (res Uint) {
if i2.Sign() == 0 {
panic("division-by-zero")
}
return Uint{*div(&(i.i), &(i2.i))}
return Uint{div(i.i, i2.i)}
}
// Div divides Uint with int64
@ -392,20 +404,32 @@ func (i Uint) DivRaw(i2 uint64) Uint {
// MarshalAmino defines custom encoding scheme
func (i Uint) MarshalAmino() (string, error) {
return marshalAmino(&(i.i))
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return marshalAmino(i.i)
}
// UnmarshalAmino defines custom decoding scheme
func (i *Uint) UnmarshalAmino(text string) error {
return unmarshalAmino(&(i.i), text)
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return unmarshalAmino(i.i, text)
}
// MarshalJSON defines custom encoding scheme
func (i Uint) MarshalJSON() ([]byte, error) {
return marshalJSON(&(i.i))
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return marshalJSON(i.i)
}
// UnmarshalJSON defines custom decoding scheme
func (i *Uint) UnmarshalJSON(bz []byte) error {
return unmarshalJSON(&(i.i), bz)
if i.i == nil { // Necessary since default Uint initialization has i.i as nil
i.i = new(big.Int)
}
return unmarshalJSON(i.i, bz)
}

View File

@ -18,20 +18,20 @@ import (
// we will panic unmarshalling into the
// nil embedded big.Rat
type Rat struct {
big.Rat `json:"rat"`
*big.Rat `json:"rat"`
}
// nolint - common values
func ZeroRat() Rat { return Rat{*big.NewRat(0, 1)} }
func OneRat() Rat { return Rat{*big.NewRat(1, 1)} }
func ZeroRat() Rat { return Rat{big.NewRat(0, 1)} }
func OneRat() Rat { return Rat{big.NewRat(1, 1)} }
// New - create a new Rat from integers
func NewRat(Numerator int64, Denominator ...int64) Rat {
switch len(Denominator) {
case 0:
return Rat{*big.NewRat(Numerator, 1)}
return Rat{big.NewRat(Numerator, 1)}
case 1:
return Rat{*big.NewRat(Numerator, Denominator[0])}
return Rat{big.NewRat(Numerator, Denominator[0])}
default:
panic("improper use of New, can only have one denominator")
}
@ -84,9 +84,9 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) {
func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat {
switch len(denom) {
case 0:
return Rat{*new(big.Rat).SetInt(num)}
return Rat{new(big.Rat).SetInt(num)}
case 1:
return Rat{*new(big.Rat).SetFrac(num, denom[0])}
return Rat{new(big.Rat).SetFrac(num, denom[0])}
default:
panic("improper use of NewRatFromBigInt, can only have one denominator")
}
@ -96,26 +96,29 @@ func NewRatFromBigInt(num *big.Int, denom ...*big.Int) Rat {
func NewRatFromInt(num Int, denom ...Int) Rat {
switch len(denom) {
case 0:
return Rat{*new(big.Rat).SetInt(num.BigInt())}
return Rat{new(big.Rat).SetInt(num.BigInt())}
case 1:
return Rat{*new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())}
return Rat{new(big.Rat).SetFrac(num.BigInt(), denom[0].BigInt())}
default:
panic("improper use of NewRatFromBigInt, can only have one denominator")
}
}
//nolint
func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator
func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator
func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero
func (r Rat) Equal(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 0 }
func (r Rat) GT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == 1 } // greater than
func (r Rat) LT(r2 Rat) bool { return (&(r.Rat)).Cmp(&(r2.Rat)) == -1 } // less than
func (r Rat) Mul(r2 Rat) Rat { return Rat{*new(big.Rat).Mul(&(r.Rat), &(r2.Rat))} } // Mul - multiplication
func (r Rat) Quo(r2 Rat) Rat { return Rat{*new(big.Rat).Quo(&(r.Rat), &(r2.Rat))} } // Quo - quotient
func (r Rat) Add(r2 Rat) Rat { return Rat{*new(big.Rat).Add(&(r.Rat), &(r2.Rat))} } // Add - addition
func (r Rat) Sub(r2 Rat) Rat { return Rat{*new(big.Rat).Sub(&(r.Rat), &(r2.Rat))} } // Sub - subtraction
func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) }
func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator
func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator
func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero
func (r Rat) Equal(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 0 }
func (r Rat) GT(r2 Rat) bool { return (r.Rat).Cmp(r2.Rat) == 1 } // greater than
func (r Rat) 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
func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.Rat)} } // Sub - subtraction
func (r Rat) String() string { return r.Rat.String() }
func (r Rat) FloatString() string { return r.Rat.FloatString(10) } // a human-friendly string format. The last digit is rounded to nearest, with halves rounded away from zero.
var (
zero = big.NewInt(0)
@ -168,8 +171,8 @@ func (r Rat) EvaluateInt() Int {
// round Rat with the provided precisionFactor
func (r Rat) Round(precisionFactor int64) Rat {
rTen := Rat{*new(big.Rat).Mul(&(r.Rat), big.NewRat(precisionFactor, 1))}
return Rat{*big.NewRat(rTen.Evaluate(), precisionFactor)}
rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))}
return Rat{big.NewRat(rTen.Evaluate(), precisionFactor)}
}
// TODO panic if negative or if totalDigits < len(initStr)???
@ -184,7 +187,10 @@ func (r Rat) ToLeftPadded(totalDigits int8) string {
//Wraps r.MarshalText().
func (r Rat) MarshalAmino() (string, error) {
bz, err := (&(r.Rat)).MarshalText()
if r.Rat == nil {
r.Rat = new(big.Rat)
}
bz, err := r.Rat.MarshalText()
return string(bz), err
}
@ -195,7 +201,7 @@ func (r *Rat) UnmarshalAmino(text string) (err error) {
if err != nil {
return err
}
r.Rat = *tempRat
r.Rat = tempRat
return nil
}

View File

@ -100,7 +100,7 @@ func TestEqualities(t *testing.T) {
}
func TestArithmatic(t *testing.T) {
func TestArithmetic(t *testing.T) {
tests := []struct {
r1, r2 Rat
resMul, resDiv, resAdd, resSub Rat
@ -180,8 +180,8 @@ func TestRound(t *testing.T) {
precFactor int64
}{
{NewRat(333, 777), NewRat(429, 1000), 1000},
{Rat{*new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000},
{Rat{*new(big.Rat).SetFrac(big3, big7)}, Rat{*big.NewRat(4285714286, 10000000000)}, 10000000000},
{Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000},
{Rat{new(big.Rat).SetFrac(big3, big7)}, Rat{big.NewRat(4285714286, 10000000000)}, 10000000000},
{NewRat(1, 2), NewRat(1, 2), 1000},
}
@ -229,7 +229,7 @@ func TestSerializationText(t *testing.T) {
bz, err := r.MarshalText()
require.NoError(t, err)
var r2 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)
@ -297,3 +297,14 @@ func TestRatsEqual(t *testing.T) {
}
}
func TestStringOverflow(t *testing.T) {
// two random 64 bit primes
rat1 := NewRat(5164315003622678713, 4389711697696177267)
rat2 := NewRat(-3179849666053572961, 8459429845579852627)
rat3 := rat1.Add(rat2)
assert.Equal(t,
"29728537197630860939575850336935951464/37134458148982045574552091851127630409",
rat3.String(),
)
}

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

@ -31,9 +31,6 @@ type Tx interface {
// Gets the Msg.
GetMsgs() []Msg
// Gets the memo.
GetMemo() string
}
//__________________________________________________________

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

@ -5,7 +5,6 @@ import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/viper"
)
const (
@ -38,7 +37,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
true
}
memo := tx.GetMemo()
memo := stdTx.GetMemo()
if len(memo) > maxMemoCharacters {
return ctx,
@ -72,12 +71,6 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
accNums[i] = sigs[i].AccountNumber
}
fee := stdTx.Fee
chainID := ctx.ChainID()
// XXX: major hack; need to get ChainID
// into the app right away (#565)
if chainID == "" {
chainID = viper.GetString("chain-id")
}
// Check sig and nonce and collect signer accounts.
var signerAccs = make([]Account, len(signerAddrs))
@ -85,7 +78,7 @@ func NewAnteHandler(am AccountMapper, fck FeeCollectionKeeper) sdk.AnteHandler {
signerAddr, sig := signerAddrs[i], sigs[i]
// check signature, return account with incremented nonce
signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, tx.GetMemo())
signBytes := StdSignBytes(ctx.ChainID(), accNums[i], sequences[i], fee, msgs, stdTx.GetMemo())
signerAcc, res := processSig(
ctx, am,
signerAddr, sig, signBytes,

View File

@ -3,14 +3,38 @@ package mock
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/auth"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
)
// A mock transaction that has a validation which can fail.
type testMsg struct {
signers []sdk.Address
positiveNum int64
}
// TODO: Clean this up, make it public
const msgType = "testMsg"
func (tx testMsg) Type() string { return msgType }
func (tx testMsg) GetMsg() sdk.Msg { return tx }
func (tx testMsg) GetMemo() string { return "" }
func (tx testMsg) GetSignBytes() []byte { return nil }
func (tx testMsg) GetSigners() []sdk.Address { return tx.signers }
func (tx testMsg) GetSignatures() []auth.StdSignature { return nil }
func (tx testMsg) ValidateBasic() sdk.Error {
if tx.positiveNum >= 0 {
return nil
}
return sdk.ErrTxDecode("positiveNum should be a non-negative integer.")
}
// test auth module messages
var (
@ -20,19 +44,50 @@ var (
addr2 = priv2.PubKey().Address()
coins = sdk.Coins{sdk.NewCoin("foocoin", 10)}
sendMsg1 = bank.MsgSend{
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
}
testMsg1 = testMsg{signers: []sdk.Address{addr1}, positiveNum: 1}
)
// initialize the mock application for this module
func getMockApp(t *testing.T) *App {
mapp := NewApp()
coinKeeper := bank.NewKeeper(mapp.AccountMapper)
mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper))
mapp.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) (res sdk.Result) { return })
require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{}))
return mapp
}
func TestMsgPrivKeys(t *testing.T) {
mapp := getMockApp(t)
mapp.Cdc.RegisterConcrete(testMsg{}, "mock/testMsg", nil)
// Construct some genesis bytes to reflect basecoin/types/AppAccount
// Give 77 foocoin to the first key
coins := sdk.Coins{sdk.NewCoin("foocoin", 77)}
acc1 := &auth.BaseAccount{
Address: addr1,
Coins: coins,
}
accs := []auth.Account{acc1}
// Construct genesis state
SetGenesis(mapp, accs)
// A checkTx context (true)
ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{})
res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1)
assert.Equal(t, acc1, res1.(*auth.BaseAccount))
// Run a CheckDeliver
SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{0}, true, priv1)
// signing a SendMsg with the wrong privKey should be an auth error
mapp.BeginBlock(abci.RequestBeginBlock{})
tx := GenTx([]sdk.Msg{testMsg1}, []int64{0}, []int64{1}, priv2)
res := mapp.Deliver(tx)
assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log)
// resigning the tx with the correct priv key should still work
res = SignCheckDeliver(t, mapp.BaseApp, []sdk.Msg{testMsg1}, []int64{0}, []int64{1}, true, priv1)
assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeOK), res.Code, res.Log)
}

View File

@ -54,6 +54,23 @@ func GenTx(msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyE
return auth.NewStdTx(msgs, fee, sigs, memo)
}
// generate a set of signed transactions a msg, that differ only by having the
// sequence numbers incremented between every transaction.
func GenSequenceOfTxs(msgs []sdk.Msg, accnums []int64, initSeqNums []int64, numToGenerate int, priv ...crypto.PrivKeyEd25519) []auth.StdTx {
txs := make([]auth.StdTx, numToGenerate, numToGenerate)
for i := 0; i < numToGenerate; i++ {
txs[i] = GenTx(msgs, accnums, initSeqNums, priv...)
incrementAllSequenceNumbers(initSeqNums)
}
return txs
}
func incrementAllSequenceNumbers(initSeqNums []int64) {
for i := 0; i < len(initSeqNums); i++ {
initSeqNums[i]++
}
}
// check a transaction result
func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.Result {
tx := GenTx(msgs, accnums, seq, priv...)
@ -62,7 +79,7 @@ func SignCheck(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int
}
// simulate a block
func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) {
func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnums []int64, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) sdk.Result {
// Sign the tx
tx := GenTx(msgs, accnums, seq, priv...)
@ -86,4 +103,5 @@ func SignCheckDeliver(t *testing.T, app *baseapp.BaseApp, msgs []sdk.Msg, accnum
app.EndBlock(abci.RequestEndBlock{})
app.Commit()
return res
}

View File

@ -77,14 +77,22 @@ var (
// initialize the mock application for this module
func getMockApp(t *testing.T) *mock.App {
mapp, err := getBenchmarkMockApp()
require.NoError(t, err)
return mapp
}
// getBenchmarkMockApp initializes a mock application for this module, for purposes of benchmarking
// Any long term API support commitments do not apply to this function.
func getBenchmarkMockApp() (*mock.App, error) {
mapp := mock.NewApp()
RegisterWire(mapp.Cdc)
coinKeeper := NewKeeper(mapp.AccountMapper)
mapp.Router().AddRoute("bank", NewHandler(coinKeeper))
require.NoError(t, mapp.CompleteSetup([]*sdk.KVStoreKey{}))
return mapp
err := mapp.CompleteSetup([]*sdk.KVStoreKey{})
return mapp, err
}
func TestMsgSendWithAccounts(t *testing.T) {

41
x/bank/bench_test.go Normal file
View File

@ -0,0 +1,41 @@
package bank
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/mock"
abci "github.com/tendermint/abci/types"
)
func BenchmarkOneBankSendTxPerBlock(b *testing.B) {
benchmarkApp, _ := getBenchmarkMockApp()
// Add an account at genesis
acc := &auth.BaseAccount{
Address: addr1,
// Some value conceivably higher than the benchmarks would ever go
Coins: sdk.Coins{sdk.NewCoin("foocoin", 100000000000)},
}
accs := []auth.Account{acc}
// Construct genesis state
mock.SetGenesis(benchmarkApp, accs)
// Precompute all txs
txs := mock.GenSequenceOfTxs([]sdk.Msg{sendMsg1}, []int64{0}, []int64{int64(0)}, b.N, priv1)
b.ResetTimer()
// Run this with a profiler, so its easy to distinguish what time comes from
// Committing, and what time comes from Check/Deliver Tx.
for i := 0; i < b.N; i++ {
benchmarkApp.BeginBlock(abci.RequestBeginBlock{})
x := benchmarkApp.Check(txs[i])
if !x.IsOK() {
panic("something is broken in checking transaction")
}
benchmarkApp.Deliver(txs[i])
benchmarkApp.EndBlock(abci.RequestEndBlock{})
benchmarkApp.Commit()
}
}

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)
}()
// retreive 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 occured 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 retreive the old validator record
validator, found := k.GetValidator(ctx, address)
if !found {
return
}
// delete the old validator record
store := ctx.KVStore(k.storeKey)
pool := k.getPool(store)
store.Delete(GetValidatorKey(address))
store.Delete(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 seperate 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 calcualtions
}
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)
@ -272,14 +306,14 @@ func (v Validator) HumanReadableString() (string, error) {
resp := "Validator \n"
resp += fmt.Sprintf("Owner: %s\n", bechOwner)
resp += fmt.Sprintf("Validator: %s\n", bechVal)
resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.String())
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String())
resp += fmt.Sprintf("Shares: Status %s, Amount: %s\n", sdk.BondStatusToString(v.PoolShares.Status), v.PoolShares.Amount.FloatString())
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.FloatString())
resp += fmt.Sprintf("Description: %s\n", v.Description)
resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight)
resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String())
resp += fmt.Sprintf("Commission: %s\n", v.Commission.String())
resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String())
resp += fmt.Sprintf("Comission Change Rate: %s\n", v.CommissionChangeRate.String())
resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String())
resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String())
resp += fmt.Sprintf("Previously Bonded Stares: %s\n", v.PrevBondedShares.String())

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