diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index e3a528d948b5..77fcfcbdbe01 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -240,7 +240,7 @@ type TxDeclareCandidacy struct { ``` declareCandidacy(tx TxDeclareCandidacy): // create and save the empty candidate - candidate = loadCandidate(store, tx.PubKey) + candidate = getCandidate(store, tx.PubKey) if candidate != nil then return candidate = NewCandidate(tx.PubKey) @@ -251,7 +251,7 @@ declareCandidacy(tx TxDeclareCandidacy): candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description - saveCandidate(store, candidate) + setCandidate(store, candidate) // move coins from the sender account to a (self-bond) delegator account // the candidate account and global shares are updated within here @@ -275,12 +275,12 @@ type TxEditCandidacy struct { ``` editCandidacy(tx TxEditCandidacy): - candidate = loadCandidate(store, tx.PubKey) + candidate = getCandidate(store, tx.PubKey) if candidate == nil or candidate.Status == Unbonded return if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey if tx.Commission >= 0 then candidate.Commission = tx.Commission if tx.Description != nil then candidate.Description = tx.Description - saveCandidate(store, candidate) + setCandidate(store, candidate) return ``` @@ -303,7 +303,7 @@ type TxDelegate struct { ``` delegate(tx TxDelegate): - candidate = loadCandidate(store, tx.PubKey) + candidate = getCandidate(store, tx.PubKey) if candidate == nil then return return delegateWithCandidate(tx, candidate) @@ -320,18 +320,18 @@ delegateWithCandidate(tx TxDelegate, candidate Candidate): if err != nil then return // Get or create the delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) + bond = getDelegatorBond(store, sender, tx.PubKey) if bond == nil then bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)} issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) bond.Shares = bond.Shares.Add(issuedDelegatorShares) - saveCandidate(store, candidate) + setCandidate(store, candidate) store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) - saveGlobalState(store, gs) + setGlobalState(store, gs) return addTokens(amount int64, gs GlobalState, candidate Candidate): @@ -377,7 +377,7 @@ type TxUnbond struct { unbond(tx TxUnbond): // get delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) + bond = getDelegatorBond(store, sender, tx.PubKey) if bond == nil then return // subtract bond tokens from delegator bond @@ -385,7 +385,7 @@ unbond(tx TxUnbond): bond.Shares = bond.Shares.Sub(ts.Shares) - candidate = loadCandidate(store, tx.PubKey) + candidate = getCandidate(store, tx.PubKey) if candidate == nil return revokeCandidacy = false @@ -397,7 +397,7 @@ unbond(tx TxUnbond): // remove the bond removeDelegatorBond(store, sender, tx.PubKey) else - saveDelegatorBond(store, sender, bond) + setDelegatorBond(store, sender, bond) // transfer coins back to account if candidate.Status == Bonded then @@ -425,15 +425,15 @@ unbond(tx TxUnbond): if candidate.GlobalStakeShares.IsZero() then removeCandidate(store, tx.PubKey) else - saveCandidate(store, candidate) + setCandidate(store, candidate) - saveGlobalState(store, gs) + setGlobalState(store, gs) return removeDelegatorBond(candidate Candidate): // first remove from the list of bonds - pks = loadDelegatorCandidates(store, sender) + pks = getDelegatorCandidates(store, sender) for i, pk := range pks { if candidate.Equals(pk) { pks = append(pks[:i], pks[i+1:]...) diff --git a/x/stake/errors.go b/x/stake/errors.go index 68b6bd9a6ee1..aae855f6e1f9 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -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") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 402d3e2f1851..e96e8484c4c6 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -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" ) -// separated for testing -func InitState(ctx sdk.Context, mapper Mapper, key, value string) sdk.Error { - - params := mapper.loadParams() - 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())) - } - - 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) - } +//nolint +const ( + GasDeclareCandidacy int64 = 20 + GasEditCandidacy int64 = 20 + GasDelegate int64 = 20 + GasUnbond int64 = 20 +) - mapper.saveParams(params) - return nil -} +//XXX fix initstater +// separated for testing +//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error { + +//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())) +//} + +//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) +//} + +//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{} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 824fe88e0a72..214ee4350382 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -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) } } diff --git a/x/stake/keeper.go b/x/stake/keeper.go new file mode 100644 index 000000000000..2428aaeca70b --- /dev/null +++ b/x/stake/keeper.go @@ -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, ¶ms) + 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 +} diff --git a/x/stake/mapper_test.go b/x/stake/keeper_test.go similarity index 86% rename from x/stake/mapper_test.go rename to x/stake/keeper_test.go index 5cf5b1820a80..680b5164297c 100644 --- a/x/stake/mapper_test.go +++ b/x/stake/keeper_test.go @@ -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) diff --git a/x/stake/mapper.go b/x/stake/mapper.go deleted file mode 100644 index caebe84999d4..000000000000 --- a/x/stake/mapper.go +++ /dev/null @@ -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, ¶ms) - 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) -} diff --git a/x/stake/msg.go b/x/stake/msg.go new file mode 100644 index 000000000000..ad9e7e2eb809 --- /dev/null +++ b/x/stake/msg.go @@ -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 +} diff --git a/x/stake/tx_test.go b/x/stake/msg_test.go similarity index 100% rename from x/stake/tx_test.go rename to x/stake/msg_test.go diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 11d0752a9b20..601ee50fdb16 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -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) } } diff --git a/x/stake/tick.go b/x/stake/tick.go index 4b206f4dcc74..27ba44f6a068 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -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 diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index d04581a7c519..fcd67af9cdd7 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -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 diff --git a/x/stake/tx.go b/x/stake/tx.go deleted file mode 100644 index 7104b92a8bdc..000000000000 --- a/x/stake/tx.go +++ /dev/null @@ -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 -} diff --git a/x/stake/types.go b/x/stake/types.go index 2ffc58ede1b3..d1cf6df5fa25 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -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"` }