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)
|
|
|
|
//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
|
2018-03-14 11:42:50 -07:00
|
|
|
switch msg := msg.(type) {
|
|
|
|
case MsgDeclareCandidacy:
|
2018-03-20 06:56:07 -07:00
|
|
|
return k.handleMsgDeclareCandidacy(ctx, msg)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgEditCandidacy:
|
2018-03-20 06:56:07 -07:00
|
|
|
return k.handleMsgEditCandidacy(ctx, msg)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgDelegate:
|
2018-03-20 06:56:07 -07:00
|
|
|
return k.handleMsgDelegate(ctx, msg)
|
2018-03-14 11:42:50 -07:00
|
|
|
case MsgUnbond:
|
2018-03-20 06:56:07 -07:00
|
|
|
return k.handleMsgUnbond(ctx, msg)
|
2018-03-13 11:27:52 -07:00
|
|
|
default:
|
2018-03-14 11:42:50 -07:00
|
|
|
return sdk.ErrTxParse("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
|
|
|
|
|
|
|
//_____________________________________________________________________
|
2018-02-27 18:07:20 -08:00
|
|
|
// helper functions
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
// move a candidates asset pool from bonded to unbonded pool
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate *Candidate) {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
// replace bonded shares with unbonded shares
|
2018-03-20 06:56:07 -07:00
|
|
|
tokens := k.getGlobalState(ctx).removeSharesBonded(candidate.Assets)
|
|
|
|
candidate.Assets = k.getGlobalState(ctx).addTokensUnbonded(tokens)
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status = Unbonded
|
|
|
|
}
|
|
|
|
|
|
|
|
// move a candidates asset pool from unbonded to bonded pool
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate *Candidate) {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-14 11:42:50 -07:00
|
|
|
// replace unbonded shares with bonded shares
|
2018-03-20 06:56:07 -07:00
|
|
|
tokens := k.getGlobalState(ctx).removeSharesUnbonded(candidate.Assets)
|
|
|
|
candidate.Assets = k.getGlobalState(ctx).addTokensBonded(tokens)
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status = Bonded
|
|
|
|
}
|
|
|
|
|
|
|
|
//_____________________________________________________________________
|
|
|
|
|
|
|
|
// These functions assume everything has been authenticated,
|
|
|
|
// now we just perform action and save
|
2018-03-14 11:42:50 -07:00
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) 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-20 06:56:07 -07:00
|
|
|
if k.getCandidate(msg.Address) != nil {
|
2018-03-14 11:42:50 -07:00
|
|
|
return ErrCandidateExistsAddr()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
2018-03-20 06:56:07 -07:00
|
|
|
if msg.bond.Denom != k.getParams().BondDenom {
|
|
|
|
return ErrBadBondingDenom()
|
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 06:56:07 -07:00
|
|
|
candidate := NewCandidate(msg.PubKey, msg.Address, msg.Description)
|
|
|
|
k.setCandidate(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-03-20 06:56:07 -07:00
|
|
|
txDelegate := NewMsgDelegate(msg.Address, msg.Bond)
|
|
|
|
return delegateWithCandidate(txDelegate, candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) 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-20 06:56:07 -07:00
|
|
|
if k.getCandidate(msg.Address) == nil {
|
|
|
|
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-03-13 11:27:52 -07:00
|
|
|
}
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// Get the pubKey bond account
|
2018-03-20 06:56:07 -07:00
|
|
|
candidate := k.getCandidate(msg.Address)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
2018-03-20 06:56:07 -07:00
|
|
|
return ErrBondNotNominated().Result()
|
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
|
|
|
}
|
|
|
|
|
|
|
|
//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 06:56:07 -07:00
|
|
|
k.setCandidate(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result {
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
if k.getCandidate(msg.Address) == nil {
|
|
|
|
return ErrBadCandidateAddr().Result()
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
2018-03-20 06:56:07 -07:00
|
|
|
if msg.bond.Denom != k.getParams().BondDenom {
|
|
|
|
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,
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
}
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// Get the pubKey bond account
|
2018-03-20 06:56:07 -07:00
|
|
|
candidate := k.getCandidate(msg.Address)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
2018-03-20 06:56:07 -07:00
|
|
|
return ErrBondNotNominated().Result()
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
2018-03-20 06:56:07 -07:00
|
|
|
|
|
|
|
return tr.delegateWithCandidate(msg, candidate).Result()
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) delegateWithCandidate(ctx sdk.Context, candidateAddr, delegatorAddr sdk.Address,
|
|
|
|
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 06:56:07 -07:00
|
|
|
bond := k.getDelegatorBond(tr.sender, canad)
|
2018-02-23 15:57:31 -08:00
|
|
|
if bond == nil {
|
|
|
|
bond = &DelegatorBond{
|
2018-03-20 06:56:07 -07:00
|
|
|
CandidateAddr: delegatorAddr,
|
|
|
|
DelegatorAddr: candidateAddr,
|
|
|
|
Shares: sdk.ZeroRat,
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Account new shares, save
|
2018-03-20 06:56:07 -07:00
|
|
|
err := BondCoins(bond, candidate, msg.Bond)
|
2018-03-14 11:42:50 -07:00
|
|
|
if err != nil {
|
2018-03-20 06:56:07 -07:00
|
|
|
return err.Result()
|
2018-03-14 11:42:50 -07:00
|
|
|
}
|
2018-03-20 06:56:07 -07:00
|
|
|
k.setDelegatorBond(tr.sender, bond)
|
|
|
|
k.setCandidate(candidate)
|
|
|
|
k.setGlobalState(tr.gs)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-16 13:36:16 -07:00
|
|
|
// Perform all the actions required to bond tokens to a delegator bond from their account
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, amount sdk.Coin) sdk.Error {
|
2018-03-16 13:36:16 -07:00
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
|
2018-03-16 13:36:16 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
newShares := candidate.addTokens(tokens.Amount, tr.gs)
|
|
|
|
bond.Shares = bond.Shares.Add(newShares)
|
2018-03-20 06:56:07 -07:00
|
|
|
k.SetDelegatorBond()
|
2018-03-16 13:36:16 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform all the actions required to bond tokens to a delegator bond from their account
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) UnbondCoins(ctx sdk.Context, bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error {
|
2018-03-16 13:36:16 -07:00
|
|
|
|
|
|
|
// subtract bond tokens from delegator bond
|
|
|
|
if bond.Shares.LT(shares) {
|
2018-03-20 06:56:07 -07:00
|
|
|
return sdk.ErrInsufficientFunds("") //XXX variables inside
|
2018-03-16 13:36:16 -07:00
|
|
|
}
|
|
|
|
bond.Shares = bond.Shares.Sub(shares)
|
|
|
|
|
|
|
|
returnAmount := candidate.removeShares(shares, tr.gs)
|
|
|
|
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
_, err := tr.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
|
2018-03-16 13:36:16 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) 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 06:56:07 -07:00
|
|
|
bond := k.getDelegatorBond(sender, msg.Address)
|
2018-03-16 12:47:17 -07:00
|
|
|
if bond == nil {
|
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-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-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
|
2018-03-20 06:56:07 -07:00
|
|
|
candidate := k.getCandidate(msg.Address)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
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-16 12:47:17 -07:00
|
|
|
if bytes.Equal(tr.sender, candidate.Address) &&
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status != Revoked {
|
|
|
|
revokeCandidacy = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the bond
|
2018-03-20 06:56:07 -07:00
|
|
|
k.removeDelegatorBond(ctx, msg.Address)
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-03-20 06:56:07 -07:00
|
|
|
k.setDelegatorBond(tr.sender, bond)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-16 12:47:17 -07:00
|
|
|
// Add the coins
|
|
|
|
returnAmount := candidate.removeShares(shares, tr.gs)
|
|
|
|
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
|
2018-03-20 06:56:07 -07:00
|
|
|
tr.coinKeeper.AddCoins(ctx, tr.sender, 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-14 11:42:50 -07:00
|
|
|
tr.bondedToUnbondedPool(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// lastly update the status
|
|
|
|
candidate.Status = Revoked
|
|
|
|
}
|
|
|
|
|
|
|
|
// deduct shares from the candidate and save
|
|
|
|
if candidate.Liabilities.IsZero() {
|
2018-03-20 06:56:07 -07:00
|
|
|
k.removeCandidate(msg.Address)
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-03-20 06:56:07 -07:00
|
|
|
k.setCandidate(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-20 06:56:07 -07:00
|
|
|
k.setGlobalState(tr.gs)
|
|
|
|
return sdk.Result{}
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|