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