cosmos-sdk/x/stake/handler.go

319 lines
8.5 KiB
Go
Raw Normal View History

2018-02-23 15:57:31 -08:00
package stake
import (
2018-03-16 12:47:17 -07:00
"bytes"
2018-02-23 15:57:31 -08:00
sdk "github.com/cosmos/cosmos-sdk/types"
2018-02-27 18:07:20 -08:00
"github.com/cosmos/cosmos-sdk/x/bank"
2018-02-23 15:57:31 -08:00
)
2018-03-20 06:56:07 -07:00
//nolint
const (
GasDeclareCandidacy int64 = 20
GasEditCandidacy int64 = 20
GasDelegate int64 = 20
GasUnbond int64 = 20
)
2018-02-23 15:57:31 -08:00
2018-03-20 06:56:07 -07:00
//XXX fix initstater
// separated for testing
//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error {
//params := k.GetParams(ctx)
2018-03-20 06:56:07 -07:00
//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
//}
2018-02-23 15:57:31 -08:00
2018-02-27 18:07:20 -08:00
//_______________________________________________________________________
2018-02-23 15:57:31 -08:00
2018-03-20 06:56:07 -07:00
func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler {
2018-02-27 18:07:20 -08:00
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
2018-03-20 06:56:07 -07:00
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgDeclareCandidacy:
return handleMsgDeclareCandidacy(ctx, msg, k)
case MsgEditCandidacy:
return handleMsgEditCandidacy(ctx, msg, k)
case MsgDelegate:
return handleMsgDelegate(ctx, msg, k)
case MsgUnbond:
return handleMsgUnbond(ctx, msg, k)
default:
2018-03-23 09:15:41 -07:00
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
2018-02-27 18:07:20 -08:00
}
2018-02-23 15:57:31 -08:00
}
}
//_____________________________________________________________________
2018-03-20 06:56:07 -07:00
// 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]
//}
2018-02-23 15:57:31 -08:00
//_____________________________________________________________________
// These functions assume everything has been authenticated,
// now we just perform action and save
func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keeper) sdk.Result {
2018-02-23 15:57:31 -08:00
2018-02-27 18:07:20 -08:00
// check to see if the pubkey or sender has been registered before
_, found := k.GetCandidate(ctx, msg.CandidateAddr)
2018-03-20 14:21:18 -07:00
if found {
return ErrCandidateExistsAddr().Result()
2018-02-27 18:07:20 -08:00
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
2018-03-20 14:21:18 -07:00
return ErrBadBondingDenom().Result()
2018-02-27 18:07:20 -08:00
}
2018-03-20 06:56:07 -07:00
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDeclareCandidacy,
}
}
2018-02-27 18:07:20 -08:00
2018-03-20 14:21:18 -07:00
candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description)
k.setCandidate(ctx, candidate)
2018-02-23 15:57:31 -08:00
2018-03-20 06:56:07 -07:00
// move coins from the msg.Address account to a (self-bond) delegator account
2018-02-23 15:57:31 -08:00
// the candidate account and global shares are updated within here
return delegateWithCandidate(ctx, k, msg.CandidateAddr, msg.Bond, candidate).Result()
2018-02-23 15:57:31 -08:00
}
func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result {
2018-02-23 15:57:31 -08:00
2018-02-27 18:07:20 -08:00
// candidate must already be registered
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
2018-03-20 14:21:18 -07:00
if !found {
2018-03-20 06:56:07 -07:00
return ErrBadCandidateAddr().Result()
2018-02-27 18:07:20 -08:00
}
2018-03-20 06:56:07 -07:00
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasEditCandidacy,
}
}
2018-02-23 15:57:31 -08:00
if candidate.Status == Unbonded { //candidate has been withdrawn
2018-03-20 06:56:07 -07:00
return ErrBondNotNominated().Result()
2018-02-23 15:57:31 -08:00
}
2018-03-20 14:21:18 -07:00
// XXX move to types
2018-02-23 15:57:31 -08:00
//check and edit any of the editable terms
2018-03-20 06:56:07 -07:00
if msg.Description.Moniker != "" {
candidate.Description.Moniker = msg.Description.Moniker
2018-02-23 15:57:31 -08:00
}
2018-03-20 06:56:07 -07:00
if msg.Description.Identity != "" {
candidate.Description.Identity = msg.Description.Identity
2018-02-23 15:57:31 -08:00
}
2018-03-20 06:56:07 -07:00
if msg.Description.Website != "" {
candidate.Description.Website = msg.Description.Website
2018-02-23 15:57:31 -08:00
}
2018-03-20 06:56:07 -07:00
if msg.Description.Details != "" {
candidate.Description.Details = msg.Description.Details
2018-02-23 15:57:31 -08:00
}
2018-03-20 14:21:18 -07:00
k.setCandidate(ctx, candidate)
return sdk.Result{}
2018-02-23 15:57:31 -08:00
}
func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
2018-02-27 18:07:20 -08:00
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
2018-03-20 14:21:18 -07:00
if !found {
2018-03-20 06:56:07 -07:00
return ErrBadCandidateAddr().Result()
2018-02-27 18:07:20 -08:00
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
2018-03-20 06:56:07 -07:00
return ErrBadBondingDenom().Result()
2018-02-27 18:07:20 -08:00
}
2018-03-20 06:56:07 -07:00
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDelegate,
}
}
return delegateWithCandidate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate).Result()
2018-02-23 15:57:31 -08:00
}
func delegateWithCandidate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
2018-03-20 06:56:07 -07:00
bondAmt sdk.Coin, candidate Candidate) sdk.Error {
2018-02-23 15:57:31 -08:00
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
}
// Get or create the delegator bond
2018-03-20 14:21:18 -07:00
existingBond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address)
if !found {
existingBond = DelegatorBond{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidate.Address,
2018-03-20 06:56:07 -07:00
Shares: sdk.ZeroRat,
2018-02-23 15:57:31 -08:00
}
}
// Account new shares, save
err := BondCoins(ctx, k, existingBond, candidate, bondAmt)
if err != nil {
2018-03-20 14:21:18 -07:00
return err
}
2018-03-20 14:21:18 -07:00
k.setDelegatorBond(ctx, existingBond)
k.setCandidate(ctx, candidate)
2018-02-23 15:57:31 -08:00
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error {
2018-03-20 06:56:07 -07:00
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
if err != nil {
return err
}
2018-03-20 14:21:18 -07:00
newShares := k.candidateAddTokens(ctx, candidate, amount.Amount)
bond.Shares = bond.Shares.Add(newShares)
2018-03-20 14:21:18 -07:00
k.setDelegatorBond(ctx, bond)
return nil
}
func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
2018-02-23 15:57:31 -08:00
2018-02-27 18:07:20 -08:00
// check if bond has any shares in it unbond
2018-03-20 14:21:18 -07:00
bond, found := k.getDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr)
if !found {
2018-03-20 06:56:07 -07:00
return ErrNoDelegatorForAddress().Result()
2018-03-16 12:47:17 -07:00
}
2018-03-20 06:56:07 -07:00
if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < msg shares
return ErrInsufficientFunds().Result()
2018-02-27 18:07:20 -08:00
}
// if shares set to special case Max then we're good
2018-03-20 06:56:07 -07:00
if msg.Shares != "MAX" {
2018-02-27 18:07:20 -08:00
// test getting rational number from decimal provided
2018-03-20 06:56:07 -07:00
shares, err := sdk.NewRatFromDecimal(msg.Shares)
2018-02-27 18:07:20 -08:00
if err != nil {
2018-03-20 06:56:07 -07:00
return err.Result()
2018-02-27 18:07:20 -08:00
}
// test that there are enough shares to unbond
2018-03-16 12:47:17 -07:00
if !bond.Shares.GT(shares) {
2018-03-20 06:56:07 -07:00
return ErrNotEnoughBondShares(msg.Shares).Result()
2018-02-27 18:07:20 -08:00
}
}
2018-03-20 06:56:07 -07:00
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasUnbond,
}
}
2018-02-27 18:07:20 -08:00
2018-02-23 15:57:31 -08:00
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
2018-02-27 18:07:20 -08:00
var shares sdk.Rat
2018-03-16 12:47:17 -07:00
var err sdk.Error
2018-03-20 06:56:07 -07:00
if msg.Shares == "MAX" {
2018-02-23 15:57:31 -08:00
shares = bond.Shares
} else {
2018-03-20 06:56:07 -07:00
shares, err = sdk.NewRatFromDecimal(msg.Shares)
2018-02-23 15:57:31 -08:00
if err != nil {
2018-03-20 06:56:07 -07:00
return err.Result()
2018-02-23 15:57:31 -08:00
}
}
// subtract bond tokens from delegator bond
2018-03-20 06:56:07 -07:00
if bond.Shares.LT(shares) { // bond shares < msg shares
return ErrInsufficientFunds().Result()
2018-02-23 15:57:31 -08:00
}
bond.Shares = bond.Shares.Sub(shares)
// get pubKey candidate
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
2018-03-20 14:21:18 -07:00
if !found {
2018-03-20 06:56:07 -07:00
return ErrNoCandidateForAddress().Result()
2018-02-23 15:57:31 -08:00
}
revokeCandidacy := false
if bond.Shares.IsZero() {
// if the bond is the owner of the candidate then
// trigger a revoke candidacy
2018-03-20 14:21:18 -07:00
if bytes.Equal(bond.DelegatorAddr, candidate.Address) &&
2018-02-23 15:57:31 -08:00
candidate.Status != Revoked {
revokeCandidacy = true
}
// remove the bond
2018-03-20 14:21:18 -07:00
k.removeDelegatorBond(ctx, bond)
2018-02-23 15:57:31 -08:00
} else {
2018-03-20 14:21:18 -07:00
k.setDelegatorBond(ctx, bond)
2018-02-23 15:57:31 -08:00
}
2018-03-16 12:47:17 -07:00
// Add the coins
2018-03-20 14:21:18 -07:00
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
2018-03-20 14:21:18 -07:00
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
2018-02-23 15:57:31 -08:00
// lastly if an revoke candidate if necessary
if revokeCandidacy {
// change the share types to unbonded if they were not already
if candidate.Status == Bonded {
2018-03-20 14:21:18 -07:00
k.bondedToUnbondedPool(ctx, candidate)
2018-02-23 15:57:31 -08:00
}
// lastly update the status
candidate.Status = Revoked
}
2018-03-20 14:21:18 -07:00
// deduct shares from the candidate
2018-02-23 15:57:31 -08:00
if candidate.Liabilities.IsZero() {
2018-03-20 14:21:18 -07:00
k.removeCandidate(ctx, candidate.Address)
2018-02-23 15:57:31 -08:00
} else {
2018-03-20 14:21:18 -07:00
k.setCandidate(ctx, candidate)
2018-02-23 15:57:31 -08:00
}
2018-03-20 06:56:07 -07:00
return sdk.Result{}
2018-02-23 15:57:31 -08:00
}
2018-03-20 14:21:18 -07:00
// XXX where this used
// Perform all the actions required to bond tokens to a delegator bond from their account
func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error {
2018-03-20 14:21:18 -07:00
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) {
return sdk.ErrInsufficientFunds("") //XXX variables inside
}
bond.Shares = bond.Shares.Sub(shares)
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
2018-03-20 14:21:18 -07:00
_, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
if err != nil {
return err
}
return nil
}