working sunny staking refactor

This commit is contained in:
rigelrozanski 2018-03-20 14:56:07 +01:00
parent c1d8aefecd
commit 93173e9fdd
14 changed files with 825 additions and 827 deletions

View File

@ -274,7 +274,7 @@ type TxDeclareCandidacy struct {
}
declareCandidacy(tx TxDeclareCandidacy):
candidate = loadCandidate(store, tx.PubKey)
candidate = getCandidate(store, msg.Address)
if candidate != nil return // candidate with that public key already exists
candidate = NewCandidate(tx.PubKey)
@ -285,7 +285,7 @@ declareCandidacy(tx TxDeclareCandidacy):
candidate.ProposerRewardPool = Coin(0)
candidate.Description = tx.Description
saveCandidate(store, candidate)
setCandidate(store, candidate)
txDelegate = TxDelegate(tx.PubKey, tx.Amount)
return delegateWithCandidate(txDelegate, candidate)
@ -343,7 +343,7 @@ delegateWithCandidate(tx TxDelegate, candidate Candidate):
poolAccount = params.HoldBonded
else
poolAccount = params.HoldUnbonded
err = transfer(sender, poolAccount, tx.Amount)
if err != nil return

View File

@ -72,6 +72,9 @@ func ErrBadValidatorAddr() sdk.Error {
func ErrBadCandidateAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate does not exist for that address")
}
func ErrBadDelegatorAddr() sdk.Error {
return newError(CodeInvalidValidator, "Delegator does not exist for that address")
}
func ErrCandidateExistsAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy")
}

View File

@ -2,93 +2,67 @@ package stake
import (
"bytes"
"fmt"
"strconv"
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
//nolint
const (
GasDeclareCandidacy int64 = 20
GasEditCandidacy int64 = 20
GasDelegate int64 = 20
GasUnbond int64 = 20
)
//XXX fix initstater
// separated for testing
func InitState(ctx sdk.Context, mapper Mapper, key, value string) sdk.Error {
//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error {
params := mapper.loadParams()
switch key {
case "allowed_bond_denom":
params.BondDenom = value
case "max_vals", "gas_bond", "gas_unbond":
//params := k.getParams(ctx)
//switch key {
//case "allowed_bond_denom":
//params.BondDenom = value
//case "max_vals", "gas_bond", "gas_unbond":
i, err := strconv.Atoi(value)
if err != nil {
return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error()))
}
//i, err := strconv.Atoi(value)
//if err != nil {
//return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error()))
//}
switch key {
case "max_vals":
if i < 0 {
return sdk.ErrUnknownRequest("cannot designate negative max validators")
}
params.MaxVals = uint16(i)
case "gas_bond":
params.GasDelegate = int64(i)
case "gas_unbound":
params.GasUnbond = int64(i)
}
default:
return sdk.ErrUnknownRequest(key)
}
//switch key {
//case "max_vals":
//if i < 0 {
//return sdk.ErrUnknownRequest("cannot designate negative max validators")
//}
//params.MaxValidators = uint16(i)
//case "gas_bond":
//GasDelegate = int64(i)
//case "gas_unbound":
//GasUnbond = int64(i)
//}
//default:
//return sdk.ErrUnknownRequest(key)
//}
mapper.saveParams(params)
return nil
}
//k.setParams(params)
//return nil
//}
//_______________________________________________________________________
func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler {
func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
params := mapper.loadParams()
err := msg.ValidateBasic()
if err != nil {
return err.Result() // TODO should also return gasUsed?
}
signers := msg.GetSigners()
if len(signers) != 1 {
return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result()
}
sender := signers[0]
transact := newTransact(ctx, sender, mapper, ck)
// Run the transaction
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgDeclareCandidacy:
res := transact.declareCandidacy(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasDeclareCandidacy
}
return res
return k.handleMsgDeclareCandidacy(ctx, msg)
case MsgEditCandidacy:
res := transact.editCandidacy(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasEditCandidacy
}
return res
return k.handleMsgEditCandidacy(ctx, msg)
case MsgDelegate:
res := transact.delegate(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasDelegate
}
return res
return k.handleMsgDelegate(ctx, msg)
case MsgUnbond:
res := transact.unbond(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasUnbond
}
return res
return k.handleMsgUnbond(ctx, msg)
default:
return sdk.ErrTxParse("invalid message parse in staking module").Result()
}
@ -97,249 +71,239 @@ func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler {
//_____________________________________________________________________
// common fields to all transactions
type transact struct {
ctx sdk.Context
sender crypto.Address
mapper Mapper
coinKeeper bank.CoinKeeper
params Params
gs *GlobalState
}
func newTransact(ctx sdk.Context, sender sdk.Address, mapper Mapper, ck bank.CoinKeeper) transact {
return transact{
ctx: ctx,
sender: sender,
mapper: mapper,
coinKeeper: ck,
params: mapper.loadParams(),
gs: mapper.loadGlobalState(),
}
}
// XXX should be send in the msg (init in CLI)
//func getSender() sdk.Address {
//signers := msg.GetSigners()
//if len(signers) != 1 {
//return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result()
//}
//sender := signers[0]
//}
//_____________________________________________________________________
// helper functions
// move a candidates asset pool from bonded to unbonded pool
func (tr transact) bondedToUnbondedPool(candidate *Candidate) {
func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate *Candidate) {
// replace bonded shares with unbonded shares
tokens := tr.gs.removeSharesBonded(candidate.Assets)
candidate.Assets = tr.gs.addTokensUnbonded(tokens)
tokens := k.getGlobalState(ctx).removeSharesBonded(candidate.Assets)
candidate.Assets = k.getGlobalState(ctx).addTokensUnbonded(tokens)
candidate.Status = Unbonded
}
// move a candidates asset pool from unbonded to bonded pool
func (tr transact) unbondedToBondedPool(candidate *Candidate) {
func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate *Candidate) {
// replace unbonded shares with bonded shares
tokens := tr.gs.removeSharesUnbonded(candidate.Assets)
candidate.Assets = tr.gs.addTokensBonded(tokens)
tokens := k.getGlobalState(ctx).removeSharesUnbonded(candidate.Assets)
candidate.Assets = k.getGlobalState(ctx).addTokensBonded(tokens)
candidate.Status = Bonded
}
// return an error if the bonds coins are incorrect
func checkDenom(mapper Mapper, bond sdk.Coin) sdk.Error {
if bond.Denom != mapper.loadParams().BondDenom {
return ErrBadBondingDenom()
}
return nil
}
//_____________________________________________________________________
// These functions assume everything has been authenticated,
// now we just perform action and save
func (tr transact) declareCandidacy(tx MsgDeclareCandidacy) sdk.Error {
func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) sdk.Result {
// check to see if the pubkey or sender has been registered before
if tr.mapper.loadCandidate(tx.Address) != nil {
if k.getCandidate(msg.Address) != nil {
return ErrCandidateExistsAddr()
}
err := checkDenom(tr.mapper, tx.Bond)
if err != nil {
return err
if msg.bond.Denom != k.getParams().BondDenom {
return ErrBadBondingDenom()
}
if tr.ctx.IsCheckTx() {
return nil
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDeclareCandidacy,
}
}
candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description)
tr.mapper.saveCandidate(candidate)
candidate := NewCandidate(msg.PubKey, msg.Address, msg.Description)
k.setCandidate(candidate)
// move coins from the tr.sender 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
txDelegate := NewMsgDelegate(tx.Address, tx.Bond)
return tr.delegateWithCandidate(txDelegate, candidate)
txDelegate := NewMsgDelegate(msg.Address, msg.Bond)
return delegateWithCandidate(txDelegate, candidate)
}
func (tr transact) editCandidacy(tx MsgEditCandidacy) sdk.Error {
func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sdk.Result {
// candidate must already be registered
if tr.mapper.loadCandidate(tx.Address) == nil {
return ErrBadCandidateAddr()
if k.getCandidate(msg.Address) == nil {
return ErrBadCandidateAddr().Result()
}
if tr.ctx.IsCheckTx() {
return nil
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasEditCandidacy,
}
}
// Get the pubKey bond account
candidate := tr.mapper.loadCandidate(tx.Address)
candidate := k.getCandidate(msg.Address)
if candidate == nil {
return ErrBondNotNominated()
return ErrBondNotNominated().Result()
}
if candidate.Status == Unbonded { //candidate has been withdrawn
return ErrBondNotNominated()
return ErrBondNotNominated().Result()
}
//check and edit any of the editable terms
if tx.Description.Moniker != "" {
candidate.Description.Moniker = tx.Description.Moniker
if msg.Description.Moniker != "" {
candidate.Description.Moniker = msg.Description.Moniker
}
if tx.Description.Identity != "" {
candidate.Description.Identity = tx.Description.Identity
if msg.Description.Identity != "" {
candidate.Description.Identity = msg.Description.Identity
}
if tx.Description.Website != "" {
candidate.Description.Website = tx.Description.Website
if msg.Description.Website != "" {
candidate.Description.Website = msg.Description.Website
}
if tx.Description.Details != "" {
candidate.Description.Details = tx.Description.Details
if msg.Description.Details != "" {
candidate.Description.Details = msg.Description.Details
}
tr.mapper.saveCandidate(candidate)
k.setCandidate(candidate)
return nil
}
func (tr transact) delegate(tx MsgDelegate) sdk.Error {
func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result {
if tr.mapper.loadCandidate(tx.Address) == nil {
return ErrBadCandidateAddr()
if k.getCandidate(msg.Address) == nil {
return ErrBadCandidateAddr().Result()
}
err := checkDenom(tr.mapper, tx.Bond)
if err != nil {
return err
if msg.bond.Denom != k.getParams().BondDenom {
return ErrBadBondingDenom().Result()
}
if tr.ctx.IsCheckTx() {
return nil
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDelegate,
}
}
// Get the pubKey bond account
candidate := tr.mapper.loadCandidate(tx.Address)
candidate := k.getCandidate(msg.Address)
if candidate == nil {
return ErrBondNotNominated()
return ErrBondNotNominated().Result()
}
return tr.delegateWithCandidate(tx, candidate)
return tr.delegateWithCandidate(msg, candidate).Result()
}
func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) sdk.Error {
func (k Keeper) delegateWithCandidate(ctx sdk.Context, candidateAddr, delegatorAddr sdk.Address,
bondAmt sdk.Coin, candidate Candidate) sdk.Error {
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
}
// Get or create the delegator bond
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address)
bond := k.getDelegatorBond(tr.sender, canad)
if bond == nil {
bond = &DelegatorBond{
Address: tx.Address,
Shares: sdk.ZeroRat,
CandidateAddr: delegatorAddr,
DelegatorAddr: candidateAddr,
Shares: sdk.ZeroRat,
}
}
// Account new shares, save
err := tr.BondCoins(bond, candidate, tx.Bond)
err := BondCoins(bond, candidate, msg.Bond)
if err != nil {
return err
return err.Result()
}
tr.mapper.saveDelegatorBond(tr.sender, bond)
tr.mapper.saveCandidate(candidate)
tr.mapper.saveGlobalState(tr.gs)
k.setDelegatorBond(tr.sender, bond)
k.setCandidate(candidate)
k.setGlobalState(tr.gs)
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func (tr *transact) BondCoins(bond *DelegatorBond, candidate *Candidate, tokens sdk.Coin) sdk.Error {
func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, amount sdk.Coin) sdk.Error {
_, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens})
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
if err != nil {
return err
}
newShares := candidate.addTokens(tokens.Amount, tr.gs)
bond.Shares = bond.Shares.Add(newShares)
k.SetDelegatorBond()
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func (tr *transact) UnbondCoins(bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error {
func (k Keeper) UnbondCoins(ctx sdk.Context, bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error {
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) {
return sdk.ErrInsufficientFunds("") // TODO
return sdk.ErrInsufficientFunds("") //XXX variables inside
}
bond.Shares = bond.Shares.Sub(shares)
returnAmount := candidate.removeShares(shares, tr.gs)
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
_, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins)
_, err := tr.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
if err != nil {
return err
}
return nil
}
func (tr transact) unbond(tx MsgUnbond) sdk.Error {
func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result {
// check if bond has any shares in it unbond
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address)
bond := k.getDelegatorBond(sender, msg.Address)
if bond == nil {
return ErrNoDelegatorForAddress()
return ErrNoDelegatorForAddress().Result()
}
if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < tx shares
return ErrInsufficientFunds()
if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < msg shares
return ErrInsufficientFunds().Result()
}
// if shares set to special case Max then we're good
if tx.Shares != "MAX" {
if msg.Shares != "MAX" {
// test getting rational number from decimal provided
shares, err := sdk.NewRatFromDecimal(tx.Shares)
shares, err := sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return err
return err.Result()
}
// test that there are enough shares to unbond
if !bond.Shares.GT(shares) {
return ErrNotEnoughBondShares(tx.Shares)
return ErrNotEnoughBondShares(msg.Shares).Result()
}
}
if tr.ctx.IsCheckTx() {
return nil
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasUnbond,
}
}
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
var shares sdk.Rat
var err sdk.Error
if tx.Shares == "MAX" {
if msg.Shares == "MAX" {
shares = bond.Shares
} else {
shares, err = sdk.NewRatFromDecimal(tx.Shares)
shares, err = sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return err
return err.Result()
}
}
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) { // bond shares < tx shares
return ErrInsufficientFunds()
if bond.Shares.LT(shares) { // bond shares < msg shares
return ErrInsufficientFunds().Result()
}
bond.Shares = bond.Shares.Sub(shares)
// get pubKey candidate
candidate := tr.mapper.loadCandidate(tx.Address)
candidate := k.getCandidate(msg.Address)
if candidate == nil {
return ErrNoCandidateForAddress()
return ErrNoCandidateForAddress().Result()
}
revokeCandidacy := false
@ -353,15 +317,15 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error {
}
// remove the bond
tr.mapper.removeDelegatorBond(tr.sender, tx.Address)
k.removeDelegatorBond(ctx, msg.Address)
} else {
tr.mapper.saveDelegatorBond(tr.sender, bond)
k.setDelegatorBond(tr.sender, bond)
}
// Add the coins
returnAmount := candidate.removeShares(shares, tr.gs)
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
tr.coinKeeper.AddCoins(tr.ctx, tr.sender, returnCoins)
tr.coinKeeper.AddCoins(ctx, tr.sender, returnCoins)
// lastly if an revoke candidate if necessary
if revokeCandidacy {
@ -377,11 +341,11 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error {
// deduct shares from the candidate and save
if candidate.Liabilities.IsZero() {
tr.mapper.removeCandidate(tx.Address)
k.removeCandidate(msg.Address)
} else {
tr.mapper.saveCandidate(candidate)
k.setCandidate(candidate)
}
tr.mapper.saveGlobalState(tr.gs)
return nil
k.setGlobalState(tr.gs)
return sdk.Result{}
}

View File

@ -67,7 +67,7 @@ func TestIncrementsMsgDelegate(t *testing.T) {
assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the accounts and the bond account have the appropriate values
candidates := mapper.loadCandidates()
candidates := mapper.getCandidates()
expectedBond += bondAmount
//expectedSender := initSender - expectedBond
gotBonded := candidates[0].Liabilities.Evaluate()
@ -96,7 +96,7 @@ func TestIncrementsMsgUnbond(t *testing.T) {
assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the accounts and the bond account have the appropriate values
candidates := mapper.loadCandidates()
candidates := mapper.getCandidates()
expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop
//expectedSender := initSender + (initBond - expectedBond)
gotBonded := candidates[0].Liabilities.Evaluate()
@ -147,7 +147,7 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) {
assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the account is bonded
candidates := mapper.loadCandidates()
candidates := mapper.getCandidates()
require.Equal(t, i, len(candidates))
val := candidates[i]
balanceExpd := initSender - 10
@ -159,17 +159,17 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) {
// unbond them all
for i, addr := range addrs {
candidatePre := mapper.loadCandidate(addrs[i])
candidatePre := mapper.getCandidate(addrs[i])
msgUndelegate := NewMsgUnbond(addrs[i], "10")
deliverer.sender = addr
got := deliverer.unbond(msgUndelegate)
assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the account is unbonded
candidates := mapper.loadCandidates()
candidates := mapper.getCandidates()
assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates))
candidatePost := mapper.loadCandidate(addrs[i])
candidatePost := mapper.getCandidate(addrs[i])
balanceExpd := initSender
balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins()
assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost)
@ -194,7 +194,7 @@ func TestMultipleMsgDelegate(t *testing.T) {
require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the account is bonded
bond := mapper.loadDelegatorBond(delegator, sender)
bond := mapper.getDelegatorBond(delegator, sender)
assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond)
}
@ -206,7 +206,7 @@ func TestMultipleMsgDelegate(t *testing.T) {
require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
//Check that the account is unbonded
bond := mapper.loadDelegatorBond(delegator, sender)
bond := mapper.getDelegatorBond(delegator, sender)
assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond)
}
}

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

@ -0,0 +1,397 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
)
//nolint
var (
// Keys for store prefixes
CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses
ParamKey = []byte{0x02} // key for global parameters relating to staking
GlobalStateKey = []byte{0x03} // key for global parameters relating to staking
// Key prefixes
CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate
ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate
ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate
DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond
DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond
)
// XXX remove beggining word get from all these keys
// GetCandidateKey - get the key for the candidate with address
func GetCandidateKey(addr sdk.Address) []byte {
return append(CandidateKeyPrefix, addr.Bytes()...)
}
// GetValidatorKey - get the key for the validator used in the power-store
func GetValidatorKey(addr sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte {
b, _ := cdc.MarshalJSON(power) // TODO need to handle error here?
return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store
}
// GetValidatorUpdatesKey - get the key for the validator used in the power-store
func GetValidatorUpdatesKey(addr sdk.Address) []byte {
return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store
}
// GetDelegatorBondKey - get the key for delegator bond with candidate
func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegatorBondKeyPrefix(delegatorAddr, cdc), candidateAddr.Bytes()...)
}
// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates
func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalJSON(&delegatorAddr)
if err != nil {
panic(err)
}
return append(DelegatorBondKeyPrefix, res...)
}
// GetDelegatorBondsKey - get the key for list of all the delegator's bonds
func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalJSON(&delegatorAddr)
if err != nil {
panic(err)
}
return append(DelegatorBondsKeyPrefix, res...)
}
//___________________________________________________________________________
// keeper of the staking store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec
coinKeeper bank.CoinKeeper
//just caches
gs GlobalState
params Params
}
func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinKeeper) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
coinKeeper: ck,
}
return keeper
}
//XXX load/save -> get/set
func (m Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate) {
store := ctx.KVStore(storeKey)
b := store.Get(GetCandidateKey(addr))
if b == nil {
return nil
}
err := m.cdc.UnmarshalJSON(b, &candidate)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (m Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
store := ctx.KVStore(storeKey)
// XXX should only remove validator if we know candidate is a validator
m.removeValidator(candidate.Address)
validator := &Validator{candidate.Address, candidate.VotingPower}
m.updateValidator(validator)
b, err := m.cdc.MarshalJSON(candidate)
if err != nil {
panic(err)
}
store.Set(GetCandidateKey(candidate.Address), b)
}
func (m Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) {
store := ctx.KVStore(storeKey)
// XXX should only remove validator if we know candidate is a validator
m.removeValidator(candidateAddr)
store.Delete(GetCandidateKey(candidateAddr))
}
//___________________________________________________________________________
//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator {
//b := store.Get(GetValidatorKey(address, votingPower))
//if b == nil {
//return nil
//}
//validator := new(Validator)
//err := cdc.UnmarshalJSON(b, validator)
//if err != nil {
//panic(err) // This error should never occur big problem if does
//}
//return validator
//}
// updateValidator - update a validator and create accumulate any changes
// in the changed validator substore
func (m Keeper) updateValidator(ctx sdk.Context, validator Validator) {
store := ctx.KVStore(storeKey)
b, err := m.cdc.MarshalJSON(validator)
if err != nil {
panic(err)
}
// add to the validators to update list if necessary
store.Set(GetValidatorUpdatesKey(validator.Address), b)
// update the list ordered by voting power
store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b)
}
func (m Keeper) removeValidator(ctx sdk.Context, address sdk.Address) {
store := ctx.KVStore(storeKey)
//add validator with zero power to the validator updates
b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat})
if err != nil {
panic(err)
}
store.Set(GetValidatorUpdatesKey(address), b)
// now actually delete from the validator set
candidate := m.getCandidate(address)
if candidate != nil {
store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc))
}
}
// get the most recent updated validator set from the Candidates. These bonds
// are already sorted by VotingPower from the UpdateVotingPower function which
// is the only function which is to modify the VotingPower
func (m Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Validator) {
store := ctx.KVStore(storeKey)
iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest
validators = make([]Validator, maxVal)
for i := 0; ; i++ {
if !iterator.Valid() || i > int(maxVal) {
iterator.Close()
break
}
valBytes := iterator.Value()
var val Validator
err := m.cdc.UnmarshalJSON(valBytes, &val)
if err != nil {
panic(err)
}
validators[i] = val
iterator.Next()
}
return
}
//_________________________________________________________________________
// get the most updated validators
func (m Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) {
store := ctx.KVStore(storeKey)
iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val Validator
err := m.cdc.UnmarshalJSON(valBytes, &val)
if err != nil {
panic(err)
}
updates = append(updates, val)
}
iterator.Close()
return
}
// remove all validator update entries
func (m Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) {
store := ctx.KVStore(storeKey)
iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix))
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop
}
iterator.Close()
}
//---------------------------------------------------------------------
// getCandidates - get the active list of all candidates TODO replace with multistore
func (m Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) {
store := ctx.KVStore(storeKey)
iterator := store.Iterator(subspace(CandidateKeyPrefix))
//iterator := store.Iterator(CandidateKeyPrefix, []byte(nil))
//iterator := store.Iterator([]byte{}, []byte(nil))
for ; iterator.Valid(); iterator.Next() {
candidateBytes := iterator.Value()
var candidate Candidate
err := m.cdc.UnmarshalJSON(candidateBytes, &candidate)
if err != nil {
panic(err)
}
candidates = append(candidates, &candidate)
}
iterator.Close()
return candidates
}
//_____________________________________________________________________
// XXX use a store iterator to get
//// load the pubkeys of all candidates a delegator is delegated too
//func (m Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) {
//store := ctx.KVStore(storeKey)
//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, m.cdc))
//if candidateBytes == nil {
//return nil
//}
//err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs)
//if err != nil {
//panic(err)
//}
//return
//}
//_____________________________________________________________________
func (m Keeper) getDelegatorBond(ctx sdk.Context,
delegator, candidate sdk.Address) (bond DelegatorBond) {
store := ctx.KVStore(storeKey)
delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc))
if delegatorBytes == nil {
return nil
}
err := m.cdc.UnmarshalJSON(delegatorBytes, &bond)
if err != nil {
panic(err)
}
return bond
}
func (m Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) {
store := ctx.KVStore(storeKey)
// XXX use store iterator
// if a new bond add to the list of bonds
//if m.getDelegatorBond(delegator, bond.Address) == nil {
//pks := m.getDelegatorCandidates(delegator)
//pks = append(pks, bond.Address)
//b, err := m.cdc.MarshalJSON(pks)
//if err != nil {
//panic(err)
//}
//store.Set(GetDelegatorBondsKey(delegator, m.cdc), b)
//}
// now actually save the bond
b, err := m.cdc.MarshalJSON(bond)
if err != nil {
panic(err)
}
store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b)
}
func (m Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) {
store := ctx.KVStore(storeKey)
// XXX use store iterator
// TODO use list queries on multistore to remove iterations here!
// first remove from the list of bonds
//addrs := m.getDelegatorCandidates(delegator)
//for i, addr := range addrs {
//if bytes.Equal(candidateAddr, addr) {
//addrs = append(addrs[:i], addrs[i+1:]...)
//}
//}
//b, err := m.cdc.MarshalJSON(addrs)
//if err != nil {
//panic(err)
//}
//store.Set(GetDelegatorBondsKey(delegator, m.cdc), b)
// now remove the actual bond
store.Delete(GetDelegatorBondKey(bond.delegatorAddr, bond.candidateAddr, m.cdc))
//updateDelegatorBonds(store, delegator) //XXX remove?
}
//_______________________________________________________________________
// load/save the global staking params
func (m Keeper) getParams(ctx sdk.Context) (params Params) {
// check if cached before anything
if m.params != (Params{}) {
return m.params
}
store := ctx.KVStore(storeKey)
b := store.Get(ParamKey)
if b == nil {
return defaultParams()
}
err := m.cdc.UnmarshalJSON(b, &params)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (m Keeper) setParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(storeKey)
b, err := m.cdc.MarshalJSON(params)
if err != nil {
panic(err)
}
store.Set(ParamKey, b)
m.params = Params{} // clear the cache
}
//_______________________________________________________________________
// XXX nothing is this Keeper should return a pointer...!!!!!!
// load/save the global staking state
func (m Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) {
// check if cached before anything
if m.gs != nil {
return m.gs
}
store := ctx.KVStore(storeKey)
b := store.Get(GlobalStateKey)
if b == nil {
return initialGlobalState()
}
gs = new(GlobalState)
err := m.cdc.UnmarshalJSON(b, &gs)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (m Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) {
store := ctx.KVStore(storeKey)
b, err := m.cdc.MarshalJSON(gs)
if err != nil {
panic(err)
}
store.Set(GlobalStateKey, b)
m.gs = GlobalState{} // clear the cache
}

View File

@ -18,8 +18,8 @@ import (
//func TestUpdateVotingPower(t *testing.T) {
//assert := assert.New(t)
//store := initTestStore(t)
//params := loadParams(store)
//gs := loadGlobalState(store)
//params := getParams(store)
//gs := getGlobalState(store)
//N := 5
//actors := newAddrs(N)
@ -38,7 +38,7 @@ import (
//// test the max validators term
//params.MaxVals = 4
//saveParams(store, params)
//setParams(store, params)
//candidates.updateVotingPower(store, gs, params)
//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4])
//}
@ -118,11 +118,11 @@ import (
//testRemove(t, vs1[1], changed[0])
//testRemove(t, vs1[2], changed[1])
//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5
//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getGlobalState(store) //N := 5
//actors := newAddrs(N)
//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1})
//for _, c := range candidates {
//saveCandidate(store, c)
//setCandidate(store, c)
//}
//// they should all already be validators
@ -132,12 +132,12 @@ import (
//// test the max value and test again
//params.MaxVals = 4
//saveParams(store, params)
//setParams(store, params)
//change, err = UpdateValidatorSet(store, gs, params)
//require.Nil(err)
//require.Equal(1, len(change), "%v", change)
//testRemove(t, candidates[4].validator(), change[0])
//candidates = loadCandidates(store)
//candidates = getCandidates(store)
//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate())
//// mess with the power's of the candidates and test
@ -147,12 +147,12 @@ import (
//candidates[3].Assets = sdk.OneRat
//candidates[4].Assets = sdk.NewRat(10)
//for _, c := range candidates {
//saveCandidate(store, c)
//setCandidate(store, c)
//}
//change, err = UpdateValidatorSet(store, gs, params)
//require.Nil(err)
//require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed
//candidates = loadCandidates(store)
//candidates = getCandidates(store)
//testChange(t, candidates[0].validator(), change[0])
//testChange(t, candidates[1].validator(), change[1])
//testChange(t, candidates[2].validator(), change[2])
@ -161,7 +161,7 @@ import (
//}
func TestState(t *testing.T) {
_, _, mapper, _ := createTestInput(t, nil, false, 0)
_, _, keeper, _ := createTestInput(t, nil, false, 0)
addrDel := sdk.Address([]byte("addressdelegator"))
addrVal := sdk.Address([]byte("addressvalidator"))
@ -190,26 +190,26 @@ func TestState(t *testing.T) {
c1.Description == c2.Description
}
// check the empty mapper first
resCand := mapper.loadCandidate(addrVal)
// check the empty keeper first
resCand := keeper.getCandidate(addrVal)
assert.Nil(t, resCand)
resPks := mapper.loadCandidates()
resPks := keeper.getCandidates()
assert.Zero(t, len(resPks))
// set and retrieve a record
mapper.saveCandidate(candidate)
resCand = mapper.loadCandidate(addrVal)
keeper.setCandidate(candidate)
resCand = keeper.getCandidate(addrVal)
//assert.Equal(candidate, resCand)
assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate)
// modify a records, save, and retrieve
candidate.Liabilities = sdk.NewRat(99)
mapper.saveCandidate(candidate)
resCand = mapper.loadCandidate(addrVal)
keeper.setCandidate(candidate)
resCand = keeper.getCandidate(addrVal)
assert.True(t, candidatesEqual(candidate, resCand))
// also test that the pubkey has been added to pubkey list
resPks = mapper.loadCandidates()
resPks = keeper.getCandidates()
require.Equal(t, 1, len(resPks))
assert.Equal(t, addrVal, resPks[0].PubKey)
@ -226,19 +226,19 @@ func TestState(t *testing.T) {
b1.Shares == b2.Shares
}
//check the empty mapper first
resBond := mapper.loadDelegatorBond(addrDel, addrVal)
//check the empty keeper first
resBond := keeper.getDelegatorBond(addrDel, addrVal)
assert.Nil(t, resBond)
//Set and retrieve a record
mapper.saveDelegatorBond(addrDel, bond)
resBond = mapper.loadDelegatorBond(addrDel, addrVal)
keeper.setDelegatorBond(addrDel, bond)
resBond = keeper.getDelegatorBond(addrDel, addrVal)
assert.True(t, bondsEqual(bond, resBond))
//modify a records, save, and retrieve
bond.Shares = sdk.NewRat(99)
mapper.saveDelegatorBond(addrDel, bond)
resBond = mapper.loadDelegatorBond(addrDel, addrVal)
keeper.setDelegatorBond(addrDel, bond)
resBond = keeper.getDelegatorBond(addrDel, addrVal)
assert.True(t, bondsEqual(bond, resBond))
//----------------------------------------------------------------------
@ -246,22 +246,22 @@ func TestState(t *testing.T) {
params := defaultParams()
//check that the empty mapper loads the default
resParams := mapper.loadParams()
//check that the empty keeper loads the default
resParams := keeper.getParams()
assert.Equal(t, params, resParams)
//modify a params, save, and retrieve
params.MaxVals = 777
mapper.saveParams(params)
resParams = mapper.loadParams()
keeper.setParams(params)
resParams = keeper.getParams()
assert.Equal(t, params, resParams)
}
func TestGetValidators(t *testing.T) {
_, _, mapper, _ := createTestInput(t, nil, false, 0)
candidatesFromAddrs(mapper, addrs, []int64{400, 200, 0, 0, 0})
_, _, keeper, _ := createTestInput(t, nil, false, 0)
candidatesFromAddrs(keeper, addrs, []int64{400, 200, 0, 0, 0})
validators := mapper.getValidators(5)
validators := keeper.getValidators(5)
require.Equal(t, 2, len(validators))
assert.Equal(t, addrs[0], validators[0].Address)
assert.Equal(t, addrs[1], validators[1].Address)

View File

@ -1,360 +0,0 @@
package stake
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
//nolint
var (
// Keys for store prefixes
CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses
ParamKey = []byte{0x02} // key for global parameters relating to staking
GlobalStateKey = []byte{0x03} // key for global parameters relating to staking
// Key prefixes
CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate
ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate
ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate
DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond
DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond
)
// GetCandidateKey - get the key for the candidate with address
func GetCandidateKey(address sdk.Address) []byte {
return append(CandidateKeyPrefix, address.Bytes()...)
}
// GetValidatorKey - get the key for the validator used in the power-store
func GetValidatorKey(address sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte {
b, _ := cdc.MarshalJSON(power) // TODO need to handle error here?
return append(ValidatorKeyPrefix, append(b, address.Bytes()...)...) // TODO does this need prefix if its in its own store
}
// GetValidatorUpdatesKey - get the key for the validator used in the power-store
func GetValidatorUpdatesKey(address sdk.Address) []byte {
return append(ValidatorUpdatesKeyPrefix, address.Bytes()...) // TODO does this need prefix if its in its own store
}
// GetDelegatorBondKey - get the key for delegator bond with candidate
func GetDelegatorBondKey(delegator, candidate sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegatorBondKeyPrefix(delegator, cdc), candidate.Bytes()...)
}
// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates
func GetDelegatorBondKeyPrefix(delegator sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalJSON(&delegator)
if err != nil {
panic(err)
}
return append(DelegatorBondKeyPrefix, res...)
}
// GetDelegatorBondsKey - get the key for list of all the delegator's bonds
func GetDelegatorBondsKey(delegator sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalJSON(&delegator)
if err != nil {
panic(err)
}
return append(DelegatorBondsKeyPrefix, res...)
}
//___________________________________________________________________________
// mapper of the staking store
type Mapper struct {
store sdk.KVStore
cdc *wire.Codec
}
func NewMapper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey) Mapper {
return Mapper{
store: ctx.KVStore(key),
cdc: cdc,
}
}
func (m Mapper) loadCandidate(address sdk.Address) *Candidate {
b := m.store.Get(GetCandidateKey(address))
if b == nil {
return nil
}
candidate := new(Candidate)
err := m.cdc.UnmarshalJSON(b, candidate)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return candidate
}
func (m Mapper) saveCandidate(candidate *Candidate) {
// XXX should only remove validator if we know candidate is a validator
m.removeValidator(candidate.Address)
validator := &Validator{candidate.Address, candidate.VotingPower}
m.updateValidator(validator)
b, err := m.cdc.MarshalJSON(*candidate)
if err != nil {
panic(err)
}
m.store.Set(GetCandidateKey(candidate.Address), b)
}
func (m Mapper) removeCandidate(address sdk.Address) {
// XXX should only remove validator if we know candidate is a validator
m.removeValidator(address)
m.store.Delete(GetCandidateKey(address))
}
//___________________________________________________________________________
//func loadValidator(m.store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator {
//b := m.store.Get(GetValidatorKey(address, votingPower))
//if b == nil {
//return nil
//}
//validator := new(Validator)
//err := cdc.UnmarshalJSON(b, validator)
//if err != nil {
//panic(err) // This error should never occur big problem if does
//}
//return validator
//}
// updateValidator - update a validator and create accumulate any changes
// in the changed validator substore
func (m Mapper) updateValidator(validator *Validator) {
b, err := m.cdc.MarshalJSON(*validator)
if err != nil {
panic(err)
}
// add to the validators to update list if necessary
m.store.Set(GetValidatorUpdatesKey(validator.Address), b)
// update the list ordered by voting power
m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b)
}
func (m Mapper) removeValidator(address sdk.Address) {
//add validator with zero power to the validator updates
b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat})
if err != nil {
panic(err)
}
m.store.Set(GetValidatorUpdatesKey(address), b)
// now actually delete from the validator set
candidate := m.loadCandidate(address)
if candidate != nil {
m.store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc))
}
}
// get the most recent updated validator set from the Candidates. These bonds
// are already sorted by VotingPower from the UpdateVotingPower function which
// is the only function which is to modify the VotingPower
func (m Mapper) getValidators(maxVal uint16) (validators []Validator) {
iterator := m.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest
validators = make([]Validator, maxVal)
for i := 0; ; i++ {
if !iterator.Valid() || i > int(maxVal) {
iterator.Close()
break
}
valBytes := iterator.Value()
var val Validator
err := m.cdc.UnmarshalJSON(valBytes, &val)
if err != nil {
panic(err)
}
validators[i] = val
iterator.Next()
}
return
}
//_________________________________________________________________________
// get the most updated validators
func (m Mapper) getValidatorUpdates() (updates []Validator) {
iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val Validator
err := m.cdc.UnmarshalJSON(valBytes, &val)
if err != nil {
panic(err)
}
updates = append(updates, val)
}
iterator.Close()
return
}
// remove all validator update entries
func (m Mapper) clearValidatorUpdates(maxVal int) {
iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix))
for ; iterator.Valid(); iterator.Next() {
m.store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop
}
iterator.Close()
}
//---------------------------------------------------------------------
// loadCandidates - get the active list of all candidates TODO replace with multistore
func (m Mapper) loadCandidates() (candidates Candidates) {
iterator := m.store.Iterator(subspace(CandidateKeyPrefix))
//iterator := m.store.Iterator(CandidateKeyPrefix, []byte(nil))
//iterator := m.store.Iterator([]byte{}, []byte(nil))
for ; iterator.Valid(); iterator.Next() {
candidateBytes := iterator.Value()
var candidate Candidate
err := m.cdc.UnmarshalJSON(candidateBytes, &candidate)
if err != nil {
panic(err)
}
candidates = append(candidates, &candidate)
}
iterator.Close()
return candidates
}
//_____________________________________________________________________
// load the pubkeys of all candidates a delegator is delegated too
func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs []sdk.Address) {
candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator, m.cdc))
if candidateBytes == nil {
return nil
}
err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs)
if err != nil {
panic(err)
}
return
}
//_____________________________________________________________________
func (m Mapper) loadDelegatorBond(delegator, candidate sdk.Address) *DelegatorBond {
delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc))
if delegatorBytes == nil {
return nil
}
bond := new(DelegatorBond)
err := m.cdc.UnmarshalJSON(delegatorBytes, bond)
if err != nil {
panic(err)
}
return bond
}
func (m Mapper) saveDelegatorBond(delegator sdk.Address,
bond *DelegatorBond) {
// if a new bond add to the list of bonds
if m.loadDelegatorBond(delegator, bond.Address) == nil {
pks := m.loadDelegatorCandidates(delegator)
pks = append(pks, (*bond).Address)
b, err := m.cdc.MarshalJSON(pks)
if err != nil {
panic(err)
}
m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b)
}
// now actually save the bond
b, err := m.cdc.MarshalJSON(*bond)
if err != nil {
panic(err)
}
m.store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b)
//updateDelegatorBonds(store, delegator) //XXX remove?
}
func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidateAddr sdk.Address) {
// TODO use list queries on multistore to remove iterations here!
// first remove from the list of bonds
addrs := m.loadDelegatorCandidates(delegator)
for i, addr := range addrs {
if bytes.Equal(candidateAddr, addr) {
addrs = append(addrs[:i], addrs[i+1:]...)
}
}
b, err := m.cdc.MarshalJSON(addrs)
if err != nil {
panic(err)
}
m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b)
// now remove the actual bond
m.store.Delete(GetDelegatorBondKey(delegator, candidateAddr, m.cdc))
//updateDelegatorBonds(store, delegator) //XXX remove?
}
//_______________________________________________________________________
// load/save the global staking params
func (m Mapper) loadParams() (params Params) {
b := m.store.Get(ParamKey)
if b == nil {
return defaultParams()
}
err := m.cdc.UnmarshalJSON(b, &params)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (m Mapper) saveParams(params Params) {
b, err := m.cdc.MarshalJSON(params)
if err != nil {
panic(err)
}
m.store.Set(ParamKey, b)
}
//_______________________________________________________________________
// load/save the global staking state
func (m Mapper) loadGlobalState() (gs *GlobalState) {
b := m.store.Get(GlobalStateKey)
if b == nil {
return initialGlobalState()
}
gs = new(GlobalState)
err := m.cdc.UnmarshalJSON(b, gs)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (m Mapper) saveGlobalState(gs *GlobalState) {
b, err := m.cdc.MarshalJSON(*gs)
if err != nil {
panic(err)
}
m.store.Set(GlobalStateKey, b)
}

213
x/stake/msg.go Normal file
View File

@ -0,0 +1,213 @@
package stake
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// name to idetify transaction types
var MsgType = "stake"
//Verify interface at compile time
var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{}
//______________________________________________________________________
// MsgDeclareCandidacy - struct for unbonding transactions
type MsgDeclareCandidacy struct {
Description
CandidateAddr sdk.Address `json:"address"`
Bond sdk.Coin `json:"bond"`
PubKey crypto.PubKey `json:"pubkey"`
}
func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey,
bond sdk.Coin, description Description) MsgDeclareCandidacy {
return MsgDeclareCandidacy{
Description: description,
CandidateAddr: candidateAddr,
Bond: bond,
PubKey: pubkey,
}
}
//nolint
func (msg MsgDeclareCandidacy) Type() string { return MsgType } //TODO update "stake/declarecandidacy"
func (msg MsgDeclareCandidacy) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} }
func (msg MsgDeclareCandidacy) String() string {
return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgDeclareCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error {
if msg.Address == nil {
return ErrCandidateEmpty()
}
if msg.Bond.Denom <= 0 {
return sdk.ErrInvalidCoins(coins)
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "description must be included")
}
return nil
}
//______________________________________________________________________
// MsgEditCandidacy - struct for editing a candidate
type MsgEditCandidacy struct {
Description
CandidateAddr sdk.Address `json:"address"`
}
func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy {
return MsgEditCandidacy{
Description: description,
Address: address,
}
}
//nolint
func (msg MsgEditCandidacy) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgEditCandidacy) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} }
func (msg MsgEditCandidacy) String() string {
return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgEditCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgEditCandidacy) ValidateBasic() sdk.Error {
if msg.Address == nil {
return ErrCandidateEmpty()
}
if err != nil {
return err
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "Transaction must include some information to modify")
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgDelegate struct {
DelegatorAddr sdk.Address `json:"address"`
CandidateAddr sdk.Address `json:"address"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgDelegate(delegatorAddr, candidateAddr sdk.Address, bond sdk.Coin) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidateAddr,
Bond: bond,
}
}
//nolint
func (msg MsgDelegate) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgDelegate) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgDelegate) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
func (msg MsgDelegate) String() string {
return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgDelegate) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr()
}
if msg.CandidateAddr == nil {
return ErrBadCandidateAddr()
}
if msg.Bond.Denom <= 0 {
return sdk.ErrInvalidCoins(coins)
}
return nil
}
//______________________________________________________________________
// MsgUnbond - struct for unbonding transactions
type MsgUnbond struct {
DelegatorAddr sdk.Address `json:"address"`
CandidateAddr sdk.Address `json:"address"`
Shares string `json:"shares"`
}
func NewMsgUnbond(delegatorAddr, candidateAddr sdk.Address, shares string) MsgUnbond {
return MsgUnbond{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidateAddr,
Shares: shares,
}
}
//nolint
func (msg MsgUnbond) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgUnbond) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
func (msg MsgUnbond) String() string {
return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgUnbond) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgUnbond) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr()
}
if msg.CandidateAddr == nil {
return ErrBadCandidateAddr()
}
if msg.Shares != "MAX" {
shares, err = sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return ErrBadShares()
}
}
return nil
}

View File

@ -96,7 +96,7 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins
)
ck := bank.NewCoinKeeper(accountMapper)
params := paramsNoInflation()
mapper.saveParams(params)
mapper.setParams(params)
// fill all the addresses with some coins
for _, addr := range addrs {
@ -167,7 +167,7 @@ func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) {
Liabilities: sdk.NewRat(amts[i]),
VotingPower: sdk.NewRat(amts[i]),
}
mapper.saveCandidate(c)
mapper.setCandidate(c)
}
}

View File

@ -9,8 +9,8 @@ import (
func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) {
// retrieve params
params := m.loadParams()
gs := m.loadGlobalState()
params := m.getParams()
gs := m.getGlobalState()
height := ctx.BlockHeight()
// Process Validator Provisions
@ -46,7 +46,7 @@ func processProvisions(m Mapper, gs *GlobalState, params Params) {
// XXX XXX XXX XXX XXX XXX XXX XXX XXX
// save the params
m.saveGlobalState(gs)
m.setGlobalState(gs)
}
// get the next inflation rate for the hour

View File

@ -9,8 +9,8 @@ import (
func TestGetInflation(t *testing.T) {
_, _, mapper, _ := createTestInput(t, nil, false, 0)
params := mapper.loadParams()
gs := mapper.loadGlobalState()
params := mapper.getParams()
gs := mapper.getGlobalState()
// Governing Mechanism:
// bondedRatio = BondedPool / TotalSupply
@ -54,8 +54,8 @@ func TestGetInflation(t *testing.T) {
func TestProcessProvisions(t *testing.T) {
_, _, mapper, _ := createTestInput(t, nil, false, 0)
params := mapper.loadParams()
gs := mapper.loadGlobalState()
params := mapper.getParams()
gs := mapper.getGlobalState()
// create some candidates some bonded, some unbonded
candidates := candidatesFromAddrsEmpty(addrs)
@ -66,7 +66,7 @@ func TestProcessProvisions(t *testing.T) {
mintedTokens := int64((i + 1) * 10000000)
gs.TotalSupply += mintedTokens
candidate.addTokens(mintedTokens, gs)
mapper.saveCandidate(candidate)
mapper.setCandidate(candidate)
}
var totalSupply int64 = 550000000
var bondedShares int64 = 150000000

View File

@ -1,214 +0,0 @@
package stake
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// name to idetify transaction types
var Name = "stake"
//Verify interface at compile time
var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{}
//______________________________________________________________________
// MsgAddr - struct for bonding or unbonding transactions
type MsgAddr struct {
Address sdk.Address `json:"address"`
}
func NewMsgAddr(address sdk.Address) MsgAddr {
return MsgAddr{
Address: address,
}
}
// nolint
func (msg MsgAddr) Type() string { return Name }
func (msg MsgAddr) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgAddr) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} }
func (msg MsgAddr) String() string {
return fmt.Sprintf("MsgAddr{Address: %v}", msg.Address)
}
// ValidateBasic - Check for non-empty candidate, and valid coins
func (msg MsgAddr) ValidateBasic() sdk.Error {
if msg.Address == nil {
return ErrCandidateEmpty()
}
return nil
}
//______________________________________________________________________
// MsgDeclareCandidacy - struct for unbonding transactions
type MsgDeclareCandidacy struct {
MsgAddr
Description
Bond sdk.Coin `json:"bond"`
PubKey crypto.PubKey `json:"pubkey"`
}
func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy {
return MsgDeclareCandidacy{
MsgAddr: NewMsgAddr(address),
Description: description,
Bond: bond,
PubKey: pubkey,
}
}
// get the bytes for the message signer to sign on
func (msg MsgDeclareCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error {
err := msg.MsgAddr.ValidateBasic()
if err != nil {
return err
}
err = validateCoin(msg.Bond)
if err != nil {
return err
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "description must be included")
}
return nil
}
//______________________________________________________________________
// MsgEditCandidacy - struct for editing a candidate
type MsgEditCandidacy struct {
MsgAddr
Description
}
func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy {
return MsgEditCandidacy{
MsgAddr: NewMsgAddr(address),
Description: description,
}
}
// get the bytes for the message signer to sign on
func (msg MsgEditCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgEditCandidacy) ValidateBasic() sdk.Error {
err := msg.MsgAddr.ValidateBasic()
if err != nil {
return err
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "Transaction must include some information to modify")
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgDelegate struct {
MsgAddr
Bond sdk.Coin `json:"bond"`
}
func NewMsgDelegate(address sdk.Address, bond sdk.Coin) MsgDelegate {
return MsgDelegate{
MsgAddr: NewMsgAddr(address),
Bond: bond,
}
}
// get the bytes for the message signer to sign on
func (msg MsgDelegate) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDelegate) ValidateBasic() sdk.Error {
err := msg.MsgAddr.ValidateBasic()
if err != nil {
return err
}
err = validateCoin(msg.Bond)
if err != nil {
return err
}
return nil
}
//______________________________________________________________________
// MsgUnbond - struct for unbonding transactions
type MsgUnbond struct {
MsgAddr
Shares string `json:"shares"`
}
func NewMsgUnbond(address sdk.Address, shares string) MsgUnbond {
return MsgUnbond{
MsgAddr: NewMsgAddr(address),
Shares: shares,
}
}
// get the bytes for the message signer to sign on
func (msg MsgUnbond) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgUnbond) ValidateBasic() sdk.Error {
err := msg.MsgAddr.ValidateBasic()
if err != nil {
return err
}
if msg.Shares == "MAX" {
return ErrCandidateEmpty()
}
return nil
}
//______________________________________________________________________
// helper
func validateCoin(coin sdk.Coin) sdk.Error {
coins := sdk.Coins{coin}
if !coins.IsValid() {
return sdk.ErrInvalidCoins(coins)
}
if !coins.IsPositive() {
return sdk.ErrInvalidCoins(coins) // XXX: add "Amount must be > 0" ?
}
return nil
}

View File

@ -12,14 +12,8 @@ type Params struct {
InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate
GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms
MaxVals uint16 `json:"max_vals"` // maximum number of validators
BondDenom string `json:"bond_denom"` // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64 `json:"gas_declare_candidacy"`
GasEditCandidacy int64 `json:"gas_edit_candidacy"`
GasDelegate int64 `json:"gas_delegate"`
GasUnbond int64 `json:"gas_unbond"`
MaxValidators uint16 `json:"max_validators"` // maximum number of validators
BondDenom string `json:"bond_denom"` // bondable coin denomination
}
func defaultParams() Params {
@ -28,12 +22,8 @@ func defaultParams() Params {
InflationMax: sdk.NewRat(20, 100),
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
MaxVals: 100,
MaxValidators: 100,
BondDenom: "fermion",
GasDeclareCandidacy: 20,
GasEditCandidacy: 20,
GasDelegate: 20,
GasUnbond: 20,
}
}
@ -142,12 +132,10 @@ const (
// 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.
// XXX update to use Address as the main key NOT the pubkey
type Candidate struct {
Status CandidateStatus `json:"status"` // Bonded status
PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate
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 TODO custom type PoolShares
Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares
VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator
@ -163,11 +151,11 @@ type Description struct {
}
// NewCandidate - initialize a new candidate
func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate {
func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) *Candidate {
return &Candidate{
Status: Unbonded,
PubKey: pubKey,
Address: address,
PubKey: pubKey,
Assets: sdk.ZeroRat,
Liabilities: sdk.ZeroRat,
VotingPower: sdk.ZeroRat,
@ -226,6 +214,11 @@ func (c *Candidate) validator() Validator {
}
}
//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"` // Address of validator
@ -256,7 +249,9 @@ type Candidates []*Candidate
// 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 {
Address sdk.Address `json:"pub_key"`
Shares sdk.Rat `json:"shares"`
Address sdk.Address `json:"address"`
CandidateAddr sdk.Address `json:"candidate_addr"`
Shares sdk.Rat `json:"shares"`
}