2018-02-23 15:57:31 -08:00
|
|
|
package stake
|
|
|
|
|
|
|
|
import (
|
2018-02-25 16:47:59 -08:00
|
|
|
"errors"
|
2018-02-23 15:57:31 -08:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
2018-02-25 16:47:59 -08:00
|
|
|
crypto "github.com/tendermint/go-crypto"
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
2018-02-27 18:07:20 -08:00
|
|
|
"github.com/cosmos/cosmos-sdk/x/bank"
|
2018-02-23 15:57:31 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
// separated for testing
|
2018-03-13 11:27:52 -07:00
|
|
|
func InitState(ctx sdk.Context, mapper Mapper, key, value string) error {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
params := mapper.loadParams()
|
2018-02-23 15:57:31 -08:00
|
|
|
switch key {
|
|
|
|
case "allowed_bond_denom":
|
|
|
|
params.AllowedBondDenom = value
|
2018-02-25 16:47:59 -08:00
|
|
|
case "max_vals", "gas_bond", "gas_unbond":
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
i, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("input must be integer, Error: %v", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
switch key {
|
|
|
|
case "max_vals":
|
2018-02-25 16:47:59 -08:00
|
|
|
if i < 0 {
|
|
|
|
return errors.New("cannot designate negative max validators")
|
|
|
|
}
|
2018-02-23 15:57:31 -08:00
|
|
|
params.MaxVals = uint16(i)
|
|
|
|
case "gas_bond":
|
|
|
|
params.GasDelegate = int64(i)
|
|
|
|
case "gas_unbound":
|
|
|
|
params.GasUnbond = int64(i)
|
|
|
|
}
|
|
|
|
default:
|
2018-03-13 11:27:52 -07:00
|
|
|
return sdk.ErrUnknownRequest(key)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
mapper.saveParams(params)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-27 18:07:20 -08:00
|
|
|
//_______________________________________________________________________
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler {
|
2018-02-27 18:07:20 -08:00
|
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
params := mapper.loadParams()
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
res := msg.ValidateBasic().Result()
|
|
|
|
if res.Code != sdk.CodeOK {
|
|
|
|
return res
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
|
|
|
sender, err := getTxSender(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
transact := NewTransact(ctx, ck)
|
2018-02-27 18:07:20 -08:00
|
|
|
|
|
|
|
// Run the transaction
|
|
|
|
switch _tx := tx.Unwrap().(type) {
|
|
|
|
case TxDeclareCandidacy:
|
2018-03-13 11:27:52 -07:00
|
|
|
if !ctx.IsCheckTx() {
|
|
|
|
res.GasUsed = params.GasDeclareCandidacy
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
return res, transact.declareCandidacy(_tx)
|
2018-02-27 18:07:20 -08:00
|
|
|
case TxEditCandidacy:
|
2018-03-13 11:27:52 -07:00
|
|
|
if !ctx.IsCheckTx() {
|
|
|
|
res.GasUsed = params.GasEditCandidacy
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
return res, transact.editCandidacy(_tx)
|
2018-02-27 18:07:20 -08:00
|
|
|
case TxDelegate:
|
2018-03-13 11:27:52 -07:00
|
|
|
if !ctx.IsCheckTx() {
|
|
|
|
res.GasUsed = params.GasDelegate
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
return res, transact.delegate(_tx)
|
2018-02-27 18:07:20 -08:00
|
|
|
case TxUnbond:
|
|
|
|
//context with hold account permissions
|
2018-03-13 11:27:52 -07:00
|
|
|
if !ctx.IsCheckTx() {
|
|
|
|
params := loadParams(store)
|
|
|
|
res.GasUsed = params.GasUnbond
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
return res, transact.unbond(_tx)
|
2018-03-13 11:27:52 -07:00
|
|
|
default:
|
|
|
|
return sdk.ErrUnknownTxType(msgType)
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
|
|
|
return
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get the sender from the ctx and ensure it matches the tx pubkey
|
2018-02-25 16:47:59 -08:00
|
|
|
func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) {
|
2018-02-23 15:57:31 -08:00
|
|
|
senders := ctx.GetPermissions("", auth.NameSigs)
|
|
|
|
if len(senders) != 1 {
|
|
|
|
return sender, ErrMissingSignature()
|
|
|
|
}
|
|
|
|
return senders[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//_____________________________________________________________________
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
// common fields to all transactions
|
|
|
|
type transact struct {
|
|
|
|
sender crypto.Address
|
2018-03-13 11:27:52 -07:00
|
|
|
mapper Mapper
|
2018-02-28 08:27:07 -08:00
|
|
|
coinKeeper bank.CoinKeeper
|
|
|
|
params Params
|
|
|
|
gs *GlobalState
|
2018-03-13 11:27:52 -07:00
|
|
|
isCheckTx sdk.Context
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
func newTransact(ctx sdk.Context, sender sdk.Address, mapper Mapper, ck bank.CoinKeeper) transact {
|
2018-02-28 08:27:07 -08:00
|
|
|
return transact{
|
|
|
|
sender: sender,
|
2018-03-13 11:27:52 -07:00
|
|
|
mapper: mapper,
|
2018-02-28 08:27:07 -08:00
|
|
|
coinKeeper: ck,
|
2018-03-13 11:27:52 -07:00
|
|
|
params: mapper.loadParams(),
|
|
|
|
gs: mapper.loadGlobalState(),
|
|
|
|
isCheckTx: ctx.IsCheckTx(),
|
2018-02-28 08:27:07 -08:00
|
|
|
}
|
|
|
|
}
|
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
|
|
|
// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK
|
|
|
|
|
|
|
|
// move a candidates asset pool from bonded to unbonded pool
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) bondedToUnbondedPool(candidate *Candidate) error {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
// replace bonded shares with unbonded shares
|
2018-02-28 08:27:07 -08:00
|
|
|
tokens := tr.gs.removeSharesBonded(candidate.Assets)
|
|
|
|
candidate.Assets = tr.gs.addTokensUnbonded(tokens)
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status = Unbonded
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
return tr.transfer(tr.params.HoldBonded, tr.params.HoldUnbonded,
|
|
|
|
sdk.Coins{{tr.params.AllowedBondDenom, tokens}})
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// move a candidates asset pool from unbonded to bonded pool
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) unbondedToBondedPool(candidate *Candidate) error {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
// replace bonded shares with unbonded shares
|
2018-02-28 08:27:07 -08:00
|
|
|
tokens := tr.gs.removeSharesUnbonded(candidate.Assets)
|
|
|
|
candidate.Assets = tr.gs.addTokensBonded(tokens)
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status = Bonded
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
return tr.transfer(tr.params.HoldUnbonded, tr.params.HoldBonded,
|
|
|
|
sdk.Coins{{tr.params.AllowedBondDenom, tokens}})
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-02-27 18:07:20 -08:00
|
|
|
// return an error if the bonds coins are incorrect
|
2018-03-13 11:27:52 -07:00
|
|
|
func checkDenom(mapper Mapper, tx BondUpdate) error {
|
|
|
|
if tx.Bond.Denom != mapper.loadParams().AllowedBondDenom {
|
2018-02-27 18:07:20 -08:00
|
|
|
return fmt.Errorf("Invalid coin denomination")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
//_____________________________________________________________________
|
|
|
|
|
|
|
|
// These functions assume everything has been authenticated,
|
|
|
|
// now we just perform action and save
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error {
|
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-13 11:27:52 -07:00
|
|
|
if tr.mapper.loadCandidate(tx.PubKey) != nil {
|
2018-02-27 18:07:20 -08:00
|
|
|
return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+
|
|
|
|
" PubKey %v already registered with %v candidate address",
|
|
|
|
candidate.PubKey, candidate.Owner)
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
err := checkDenom(tx.BondUpdate, tr.mapper)
|
2018-02-27 18:07:20 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
if tr.IsCheckTx {
|
|
|
|
return nil
|
|
|
|
}
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// create and save the empty candidate
|
2018-03-13 11:27:52 -07:00
|
|
|
bond := tr.mapper.loadCandidate(tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if bond != nil {
|
|
|
|
return ErrCandidateExistsAddr()
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description)
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveCandidate(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
// move coins from the tr.sender 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
|
|
|
|
txDelegate := TxDelegate{tx.BondUpdate}
|
2018-02-28 08:27:07 -08:00
|
|
|
return tr.delegateWithCandidate(txDelegate, candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) editCandidacy(tx TxEditCandidacy) error {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
2018-02-27 18:07:20 -08:00
|
|
|
// candidate must already be registered
|
2018-03-13 11:27:52 -07:00
|
|
|
if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist
|
2018-02-27 18:07:20 -08:00
|
|
|
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
if tr.IsCheckTx {
|
|
|
|
return nil
|
|
|
|
}
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// Get the pubKey bond account
|
2018-03-13 11:27:52 -07:00
|
|
|
candidate := tr.mapper.loadCandidate(tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
|
|
|
return ErrBondNotNominated()
|
|
|
|
}
|
|
|
|
if candidate.Status == Unbonded { //candidate has been withdrawn
|
|
|
|
return ErrBondNotNominated()
|
|
|
|
}
|
|
|
|
|
|
|
|
//check and edit any of the editable terms
|
|
|
|
if tx.Description.Moniker != "" {
|
|
|
|
candidate.Description.Moniker = tx.Description.Moniker
|
|
|
|
}
|
|
|
|
if tx.Description.Identity != "" {
|
|
|
|
candidate.Description.Identity = tx.Description.Identity
|
|
|
|
}
|
|
|
|
if tx.Description.Website != "" {
|
|
|
|
candidate.Description.Website = tx.Description.Website
|
|
|
|
}
|
|
|
|
if tx.Description.Details != "" {
|
|
|
|
candidate.Description.Details = tx.Description.Details
|
|
|
|
}
|
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveCandidate(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) delegate(tx TxDelegate) error {
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist
|
2018-02-27 18:07:20 -08:00
|
|
|
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
err := checkDenom(tx.BondUpdate, tr.mapper)
|
2018-02-27 18:07:20 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-13 11:27:52 -07:00
|
|
|
if tr.IsCheckTx {
|
|
|
|
return nil
|
|
|
|
}
|
2018-02-27 18:07:20 -08:00
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// Get the pubKey bond account
|
2018-03-13 11:27:52 -07:00
|
|
|
candidate := tr.mapper.loadCandidate(tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
|
|
|
return ErrBondNotNominated()
|
|
|
|
}
|
2018-02-28 08:27:07 -08:00
|
|
|
return tr.delegateWithCandidate(tx, candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error {
|
2018-02-23 15:57:31 -08:00
|
|
|
|
|
|
|
if candidate.Status == Revoked { //candidate has been withdrawn
|
|
|
|
return ErrBondNotNominated()
|
|
|
|
}
|
|
|
|
|
2018-02-25 16:47:59 -08:00
|
|
|
var poolAccount crypto.Address
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate.Status == Bonded {
|
2018-02-28 08:27:07 -08:00
|
|
|
poolAccount = tr.params.HoldBonded
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-02-28 08:27:07 -08:00
|
|
|
poolAccount = tr.params.HoldUnbonded
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
// XXX refactor all steps like this into GlobalState.addBondedTokens()
|
2018-02-23 15:57:31 -08:00
|
|
|
// Move coins from the delegator account to the bonded pool account
|
2018-02-28 08:27:07 -08:00
|
|
|
err := tr.transfer(tr.sender, poolAccount, sdk.Coins{tx.Bond})
|
2018-02-23 15:57:31 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get or create the delegator bond
|
2018-03-13 11:27:52 -07:00
|
|
|
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if bond == nil {
|
|
|
|
bond = &DelegatorBond{
|
|
|
|
PubKey: tx.PubKey,
|
2018-02-27 18:07:20 -08:00
|
|
|
Shares: sdk.ZeroRat,
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Account new shares, save
|
2018-02-28 08:27:07 -08:00
|
|
|
bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, tr.gs))
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveCandidate(candidate)
|
|
|
|
tr.mapper.saveDelegatorBond(tr.sender, bond)
|
|
|
|
tr.mapper.saveGlobalState(tr.gs)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
func (tr transact) unbond(tx TxUnbond) error {
|
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-13 11:27:52 -07:00
|
|
|
existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
|
2018-02-27 18:07:20 -08:00
|
|
|
sharesStr := viper.GetString(tx.Shares)
|
2018-03-13 11:27:52 -07:00
|
|
|
if existingBond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares
|
|
|
|
return errors.New("no shares in account to unbond")
|
2018-02-27 18:07:20 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// if shares set to special case Max then we're good
|
|
|
|
if sharesStr != "MAX" {
|
|
|
|
// test getting rational number from decimal provided
|
|
|
|
shares, err := sdk.NewRatFromDecimal(sharesStr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// test that there are enough shares to unbond
|
|
|
|
if bond.Shares.LT(shares) {
|
|
|
|
return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v",
|
|
|
|
bond.Shares, tx.Shares)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// XXX end of old checkTx
|
|
|
|
|
2018-02-23 15:57:31 -08:00
|
|
|
// get delegator bond
|
2018-03-13 11:27:52 -07:00
|
|
|
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if bond == nil {
|
|
|
|
return ErrNoDelegatorForAddress()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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-02-23 15:57:31 -08:00
|
|
|
if tx.Shares == "MAX" {
|
|
|
|
shares = bond.Shares
|
|
|
|
} else {
|
|
|
|
var err error
|
2018-02-27 18:07:20 -08:00
|
|
|
shares, err = sdk.NewRatFromDecimal(tx.Shares)
|
2018-02-23 15:57:31 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// subtract bond tokens from delegator bond
|
|
|
|
if bond.Shares.LT(shares) { // bond shares < tx shares
|
|
|
|
return ErrInsufficientFunds()
|
|
|
|
}
|
|
|
|
bond.Shares = bond.Shares.Sub(shares)
|
|
|
|
|
|
|
|
// get pubKey candidate
|
2018-03-13 11:27:52 -07:00
|
|
|
candidate := tr.mapper.loadCandidate(tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate == nil {
|
|
|
|
return ErrNoCandidateForAddress()
|
|
|
|
}
|
|
|
|
|
|
|
|
revokeCandidacy := false
|
|
|
|
if bond.Shares.IsZero() {
|
|
|
|
|
|
|
|
// if the bond is the owner of the candidate then
|
|
|
|
// trigger a revoke candidacy
|
2018-02-28 08:27:07 -08:00
|
|
|
if tr.sender.Equals(candidate.Owner) &&
|
2018-02-23 15:57:31 -08:00
|
|
|
candidate.Status != Revoked {
|
|
|
|
revokeCandidacy = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the bond
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.removeDelegatorBond(tr.sender, tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveDelegatorBond(tr.sender, bond)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// transfer coins back to account
|
2018-02-25 16:47:59 -08:00
|
|
|
var poolAccount crypto.Address
|
2018-02-23 15:57:31 -08:00
|
|
|
if candidate.Status == Bonded {
|
2018-02-28 08:27:07 -08:00
|
|
|
poolAccount = tr.params.HoldBonded
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-02-28 08:27:07 -08:00
|
|
|
poolAccount = tr.params.HoldUnbonded
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-02-28 08:27:07 -08:00
|
|
|
returnCoins := candidate.removeShares(shares, tr.gs)
|
|
|
|
err := tr.transfer(poolAccount, tr.sender,
|
|
|
|
sdk.Coins{{tr.params.AllowedBondDenom, returnCoins}})
|
2018-02-23 15:57:31 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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-02-28 08:27:07 -08:00
|
|
|
err = tr.bondedToUnbondedPool(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// lastly update the status
|
|
|
|
candidate.Status = Revoked
|
|
|
|
}
|
|
|
|
|
|
|
|
// deduct shares from the candidate and save
|
|
|
|
if candidate.Liabilities.IsZero() {
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.removeCandidate(tx.PubKey)
|
2018-02-23 15:57:31 -08:00
|
|
|
} else {
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveCandidate(candidate)
|
2018-02-23 15:57:31 -08:00
|
|
|
}
|
|
|
|
|
2018-03-13 11:27:52 -07:00
|
|
|
tr.mapper.saveGlobalState(tr.gs)
|
2018-02-23 15:57:31 -08:00
|
|
|
return nil
|
|
|
|
}
|