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-04-04 20:22:13 -07:00
|
|
|
abci "github.com/tendermint/abci/types"
|
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-02-27 18:07:20 -08:00
|
|
|
//_______________________________________________________________________
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-04-06 11:35:54 -07:00
|
|
|
func NewHandler(k Keeper) 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
|
2018-03-14 11:42:50 -07:00
|
|
|
switch msg := msg.(type) {
|
|
|
|
case MsgDeclareCandidacy:
|
2018-03-27 17:26:52 -07:00
|
|
|
return handleMsgDeclareCandidacy(ctx, msg, k)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgEditCandidacy:
|
2018-03-27 17:26:52 -07:00
|
|
|
return handleMsgEditCandidacy(ctx, msg, k)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgDelegate:
|
2018-03-27 17:26:52 -07:00
|
|
|
return handleMsgDelegate(ctx, msg, k)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgUnbond:
|
2018-03-27 17:26:52 -07:00
|
|
|
return handleMsgUnbond(ctx, msg, k)
|
2018-03-13 11:27:52 -07:00
|
|
|
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-04-06 22:50:46 -07:00
|
|
|
//_____________________________________________________________________
|
2018-04-04 20:22:13 -07:00
|
|
|
|
|
|
|
// NewEndBlocker generates sdk.EndBlocker
|
|
|
|
// Performs tick functionality
|
|
|
|
func NewEndBlocker(k Keeper) sdk.EndBlocker {
|
|
|
|
return func(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
|
|
|
|
res.ValidatorUpdates = k.Tick(ctx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
//_____________________________________________________________________
|
|
|
|
|
2018-04-06 22:50:46 -07:00
|
|
|
// InitGenesis - store genesis parameters
|
2018-04-11 10:27:36 -07:00
|
|
|
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) {
|
|
|
|
k.setPool(ctx, data.Pool)
|
|
|
|
k.setParams(ctx, data.Params)
|
2018-04-23 17:05:58 -07:00
|
|
|
for _, candidate := range data.Candidates {
|
|
|
|
k.setCandidate(ctx, candidate)
|
|
|
|
}
|
2018-04-06 22:50:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//_____________________________________________________________________
|
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// These functions assume everything has been authenticated,
|
|
|
|
// now we just perform action and save
|
2018-03-14 11:42:50 -07:00
|
|
|
|
2018-03-27 17:26:52 -07:00
|
|
|
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
|
2018-03-22 10:10:25 -07:00
|
|
|
_, found := k.GetCandidate(ctx, msg.CandidateAddr)
|
2018-03-20 14:21:18 -07:00
|
|
|
if found {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrCandidateExistsAddr(k.codespace).Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
2018-03-22 10:10:25 -07:00
|
|
|
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrBadBondingDenom(k.codespace).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-03-13 11:27:52 -07:00
|
|
|
}
|
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
|
2018-04-03 10:03:49 -07:00
|
|
|
err := delegate(ctx, k, msg.CandidateAddr, msg.Bond, candidate)
|
|
|
|
if err != nil {
|
|
|
|
return err.Result()
|
|
|
|
}
|
|
|
|
return sdk.Result{}
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-27 17:26:52 -07: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
|
2018-03-22 10:10:25 -07:00
|
|
|
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
|
2018-03-20 14:21:18 -07:00
|
|
|
if !found {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrBadCandidateAddr(k.codespace).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-03-13 11:27:52 -07:00
|
|
|
}
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-20 14:21:18 -07:00
|
|
|
// XXX move to types
|
2018-03-28 08:08:22 -07:00
|
|
|
// replace all editable fields (clients should autofill existing values)
|
|
|
|
candidate.Description.Moniker = msg.Description.Moniker
|
|
|
|
candidate.Description.Identity = msg.Description.Identity
|
|
|
|
candidate.Description.Website = msg.Description.Website
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2018-03-27 17:26:52 -07:00
|
|
|
func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-03-22 10:10:25 -07:00
|
|
|
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
|
2018-03-20 14:21:18 -07:00
|
|
|
if !found {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrBadCandidateAddr(k.codespace).Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
2018-03-22 10:10:25 -07:00
|
|
|
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrBadBondingDenom(k.codespace).Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
2018-04-03 19:26:39 -07:00
|
|
|
if candidate.Status == Revoked {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrCandidateRevoked(k.codespace).Result()
|
2018-04-03 19:26:39 -07:00
|
|
|
}
|
2018-03-20 06:56:07 -07:00
|
|
|
if ctx.IsCheckTx() {
|
|
|
|
return sdk.Result{
|
|
|
|
GasUsed: GasDelegate,
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
}
|
2018-04-03 10:03:49 -07:00
|
|
|
err := delegate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate)
|
|
|
|
if err != nil {
|
|
|
|
return err.Result()
|
|
|
|
}
|
|
|
|
return sdk.Result{}
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-04-03 19:26:39 -07:00
|
|
|
// common functionality between handlers
|
2018-04-01 09:05:58 -07:00
|
|
|
func delegate(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
|
|
|
|
|
|
|
// Get or create the delegator bond
|
2018-04-10 20:51:09 -07:00
|
|
|
bond, found := k.GetDelegatorBond(ctx, delegatorAddr, candidate.Address)
|
2018-03-20 14:21:18 -07:00
|
|
|
if !found {
|
2018-04-03 19:26:39 -07:00
|
|
|
bond = DelegatorBond{
|
2018-03-20 14:21:18 -07:00
|
|
|
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
|
2018-04-03 19:26:39 -07:00
|
|
|
pool := k.GetPool(ctx)
|
|
|
|
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt})
|
2018-03-16 13:36:16 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-04-03 19:26:39 -07:00
|
|
|
pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount)
|
2018-03-16 13:36:16 -07:00
|
|
|
bond.Shares = bond.Shares.Add(newShares)
|
2018-04-03 19:26:39 -07:00
|
|
|
|
2018-04-23 09:32:55 -07:00
|
|
|
// Update bond height
|
|
|
|
bond.Height = ctx.BlockHeight()
|
|
|
|
|
2018-03-20 14:21:18 -07:00
|
|
|
k.setDelegatorBond(ctx, bond)
|
2018-04-03 19:26:39 -07:00
|
|
|
k.setCandidate(ctx, candidate)
|
|
|
|
k.setPool(ctx, pool)
|
2018-03-16 13:36:16 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:26:52 -07:00
|
|
|
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-04-10 20:51:09 -07:00
|
|
|
bond, found := k.GetDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr)
|
2018-03-20 14:21:18 -07:00
|
|
|
if !found {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrNoDelegatorForAddress(k.codespace).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
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrInsufficientFunds(k.codespace).Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
|
|
|
|
2018-04-10 20:51:09 -07:00
|
|
|
var shares sdk.Rat
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-03-27 17:54:54 -07:00
|
|
|
// test that there are enough shares to unbond
|
|
|
|
if msg.Shares == "MAX" {
|
|
|
|
if !bond.Shares.GT(sdk.ZeroRat) {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
|
2018-03-27 17:54:54 -07:00
|
|
|
}
|
|
|
|
} else {
|
2018-04-10 20:51:09 -07:00
|
|
|
var err sdk.Error
|
|
|
|
shares, err = sdk.NewRatFromDecimal(msg.Shares)
|
|
|
|
if err != nil {
|
|
|
|
return err.Result()
|
|
|
|
}
|
2018-04-03 19:26:39 -07:00
|
|
|
if bond.Shares.LT(shares) {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrNotEnoughBondShares(k.codespace, msg.Shares).Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
|
|
|
}
|
2018-03-27 17:54:54 -07:00
|
|
|
|
|
|
|
// get candidate
|
|
|
|
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
|
|
|
|
if !found {
|
2018-04-17 19:16:21 -07:00
|
|
|
return ErrNoCandidateForAddress(k.codespace).Result()
|
2018-03-27 17:54:54 -07:00
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
if ctx.IsCheckTx() {
|
|
|
|
return sdk.Result{
|
|
|
|
GasUsed: GasUnbond,
|
|
|
|
}
|
2018-03-14 11:42:50 -07:00
|
|
|
}
|
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-03-20 06:56:07 -07:00
|
|
|
if msg.Shares == "MAX" {
|
2018-02-23 15:57:31 -08:00
|
|
|
shares = bond.Shares
|
|
|
|
}
|
|
|
|
|
|
|
|
// subtract bond tokens from delegator bond
|
|
|
|
bond.Shares = bond.Shares.Sub(shares)
|
|
|
|
|
2018-03-27 17:54:54 -07:00
|
|
|
// remove the bond
|
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
|
|
|
|
}
|
|
|
|
|
2018-03-20 14:21:18 -07:00
|
|
|
k.removeDelegatorBond(ctx, bond)
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-04-23 09:32:55 -07:00
|
|
|
// Update bond height
|
|
|
|
bond.Height = ctx.BlockHeight()
|
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-29 03:11:47 -07:00
|
|
|
p := k.GetPool(ctx)
|
2018-04-03 19:26:39 -07:00
|
|
|
p, candidate, returnAmount := p.candidateRemoveShares(candidate, shares)
|
2018-03-22 10:10:25 -07:00
|
|
|
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
|
|
|
|
2018-04-03 19:26:39 -07:00
|
|
|
/////////////////////////////////////
|
|
|
|
|
2018-03-27 17:54:54 -07:00
|
|
|
// revoke candidate if necessary
|
2018-02-23 15:57:31 -08:00
|
|
|
if revokeCandidacy {
|
|
|
|
|
|
|
|
// change the share types to unbonded if they were not already
|
|
|
|
if candidate.Status == Bonded {
|
2018-03-29 03:11:47 -07:00
|
|
|
p, candidate = p.bondedToUnbondedPool(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-29 03:11:47 -07:00
|
|
|
k.setPool(ctx, p)
|
2018-03-20 06:56:07 -07:00
|
|
|
return sdk.Result{}
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|