refactor, staking uses addresses instead of pubkey

staking refactoring working

working
This commit is contained in:
rigelrozanski 2018-03-14 19:42:50 +01:00
parent 292e156872
commit dc8636390c
12 changed files with 451 additions and 383 deletions

View File

@ -50,7 +50,7 @@ type Params struct {
ReserveTax rational.Rational // Tax collected on all fees
MaxVals uint16 // maximum number of validators
AllowedBondDenom string // bondable coin denomination
BondDenom string // bondable coin denomination
// gas costs for txs
GasDeclareCandidacy int64

View File

@ -34,6 +34,15 @@ func (msg SetTrendMsg) String() string {
return fmt.Sprintf("SetTrendMsg{Sender: %v, Cool: %v}", msg.Sender, msg.Cool)
}
// Get the bytes for the message signer to sign on
func (msg SetTrendMsg) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// Validate Basic is used to quickly disqualify obviously invalid messages quickly
func (msg SetTrendMsg) ValidateBasic() sdk.Error {
if len(msg.Sender) == 0 {
@ -48,15 +57,6 @@ func (msg SetTrendMsg) ValidateBasic() sdk.Error {
return nil
}
// Get the bytes for the message signer to sign on
func (msg SetTrendMsg) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
//_______________________________________________________________________
// A message type to quiz how cool you are. these fields are can be entirely

View File

@ -1,7 +1,6 @@
package types
import (
"errors"
"math/big"
"strconv"
"strings"
@ -57,7 +56,7 @@ func NewRat(Numerator int64, Denominator ...int64) Rat {
}
//NewFromDecimal - create a rational from decimal string or integer string
func NewRatFromDecimal(decimalStr string) (f Rat, err error) {
func NewRatFromDecimal(decimalStr string) (f Rat, err Error) {
// first extract any negative symbol
neg := false
@ -73,23 +72,23 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err error) {
switch len(str) {
case 1:
if len(str[0]) == 0 {
return f, errors.New("not a decimal string")
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0]
case 2:
if len(str[0]) == 0 || len(str[1]) == 0 {
return f, errors.New("not a decimal string")
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0] + str[1]
len := int64(len(str[1]))
denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64()
default:
return f, errors.New("not a decimal string")
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
num, err := strconv.Atoi(numStr)
if err != nil {
return f, err
num, errConv := strconv.Atoi(numStr)
if errConv != nil {
return f, NewError(CodeUnknownRequest, errConv.Error())
}
if neg {

View File

@ -147,6 +147,7 @@ type StdSignMsg struct {
// XXX: Alt
}
// get message bytes
func (msg StdSignMsg) Bytes() []byte {
return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg)
}

View File

@ -107,7 +107,7 @@ func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error {
Details: viper.GetString(FlagDetails),
}
tx := stake.NewTxDeclareCandidacy(amount, pk, description)
tx := stake.NewMsgDeclareCandidacy(amount, pk, description)
return doTx(tx)
}
@ -125,7 +125,7 @@ func cmdEditCandidacy(cmd *cobra.Command, args []string) error {
Details: viper.GetString(FlagDetails),
}
tx := stake.NewTxEditCandidacy(pk, description)
tx := stake.NewMsgEditCandidacy(pk, description)
return doTx(tx)
}
@ -140,7 +140,7 @@ func cmdDelegate(cmd *cobra.Command, args []string) error {
return err
}
tx := stake.NewTxDelegate(amount, pk)
tx := stake.NewMsgDelegate(amount, pk)
return doTx(tx)
}
@ -167,7 +167,7 @@ func cmdUnbond(cmd *cobra.Command, args []string) error {
return err
}
tx := stake.NewTxUnbond(sharesStr, pk)
tx := stake.NewMsgUnbond(sharesStr, pk)
return doTx(tx)
}

View File

@ -43,46 +43,49 @@ func codeToDefaultMsg(code CodeType) string {
//----------------------------------------
// Error constructors
func ErrCandidateEmpty() error {
func ErrCandidateEmpty() sdk.Error {
return newError(CodeInvalidValidator, "Cannot bond to an empty candidate")
}
func ErrBadBondingDenom() error {
func ErrBadBondingDenom() sdk.Error {
return newError(CodeInvalidValidator, "Invalid coin denomination")
}
func ErrBadBondingAmount() error {
func ErrBadBondingAmount() sdk.Error {
return newError(CodeInvalidValidator, "Amount must be > 0")
}
func ErrNoBondingAcct() error {
func ErrNoBondingAcct() sdk.Error {
return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair")
}
func ErrCommissionNegative() error {
func ErrCommissionNegative() sdk.Error {
return newError(CodeInvalidValidator, "Commission must be positive")
}
func ErrCommissionHuge() error {
func ErrCommissionHuge() sdk.Error {
return newError(CodeInvalidValidator, "Commission cannot be more than 100%")
}
func ErrBadValidatorAddr() error {
func ErrBadValidatorAddr() sdk.Error {
return newError(CodeInvalidValidator, "Validator does not exist for that address")
}
func ErrCandidateExistsAddr() error {
func ErrBadCandidateAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate does not exist for that address")
}
func ErrCandidateExistsAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy")
}
func ErrMissingSignature() error {
func ErrMissingSignature() sdk.Error {
return newError(CodeInvalidValidator, "Missing signature")
}
func ErrBondNotNominated() error {
func ErrBondNotNominated() sdk.Error {
return newError(CodeInvalidValidator, "Cannot bond to non-nominated account")
}
func ErrNoCandidateForAddress() error {
func ErrNoCandidateForAddress() sdk.Error {
return newError(CodeInvalidValidator, "Validator does not exist for that address")
}
func ErrNoDelegatorForAddress() error {
func ErrNoDelegatorForAddress() sdk.Error {
return newError(CodeInvalidValidator, "Delegator does not contain validator bond")
}
func ErrInsufficientFunds() error {
func ErrInsufficientFunds() sdk.Error {
return newError(CodeInvalidValidator, "Insufficient bond shares")
}
func ErrBadRemoveValidator() error {
func ErrBadRemoveValidator() sdk.Error {
return newError(CodeInvalidValidator, "Error removing validator")
}

View File

@ -1,7 +1,6 @@
package stake
import (
"errors"
"fmt"
"strconv"
@ -9,28 +8,27 @@ import (
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// separated for testing
func InitState(ctx sdk.Context, mapper Mapper, key, value string) error {
func InitState(ctx sdk.Context, mapper Mapper, key, value string) sdk.Error {
params := mapper.loadParams()
switch key {
case "allowed_bond_denom":
params.AllowedBondDenom = value
params.BondDenom = value
case "max_vals", "gas_bond", "gas_unbond":
i, err := strconv.Atoi(value)
if err != nil {
return fmt.Errorf("input must be integer, Error: %v", err.Error())
return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error()))
}
switch key {
case "max_vals":
if i < 0 {
return errors.New("cannot designate negative max validators")
return sdk.ErrUnknownRequest("cannot designate negative max validators")
}
params.MaxVals = uint16(i)
case "gas_bond":
@ -53,112 +51,98 @@ func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler {
params := mapper.loadParams()
res := msg.ValidateBasic().Result()
if res.Code != sdk.CodeOK {
return res
}
sender, err := getTxSender(ctx)
err := msg.ValidateBasic()
if err != nil {
return
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, ck)
transact := newTransact(ctx, sender, mapper, ck)
// Run the transaction
switch _tx := tx.Unwrap().(type) {
case TxDeclareCandidacy:
switch msg := msg.(type) {
case MsgDeclareCandidacy:
res := transact.declareCandidacy(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasDeclareCandidacy
}
return res, transact.declareCandidacy(_tx)
case TxEditCandidacy:
return res
case MsgEditCandidacy:
res := transact.editCandidacy(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasEditCandidacy
}
return res, transact.editCandidacy(_tx)
case TxDelegate:
return res
case MsgDelegate:
res := transact.delegate(msg).Result()
if !ctx.IsCheckTx() {
res.GasUsed = params.GasDelegate
}
return res, transact.delegate(_tx)
case TxUnbond:
//context with hold account permissions
return res
case MsgUnbond:
res := transact.unbond(msg).Result()
if !ctx.IsCheckTx() {
params := loadParams(store)
res.GasUsed = params.GasUnbond
}
return res, transact.unbond(_tx)
return res
default:
return sdk.ErrUnknownTxType(msgType)
return sdk.ErrTxParse("invalid message parse in staking module").Result()
}
return
}
}
// get the sender from the ctx and ensure it matches the tx pubkey
func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) {
senders := ctx.GetPermissions("", auth.NameSigs)
if len(senders) != 1 {
return sender, ErrMissingSignature()
}
return senders[0], nil
}
//_____________________________________________________________________
// common fields to all transactions
type transact struct {
ctx sdk.Context
sender crypto.Address
mapper Mapper
coinKeeper bank.CoinKeeper
params Params
gs *GlobalState
isCheckTx sdk.Context
}
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(),
isCheckTx: ctx.IsCheckTx(),
}
}
//_____________________________________________________________________
// helper functions
// 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
func (tr transact) bondedToUnbondedPool(candidate *Candidate) error {
func (tr transact) bondedToUnbondedPool(candidate *Candidate) {
// replace bonded shares with unbonded shares
tokens := tr.gs.removeSharesBonded(candidate.Assets)
candidate.Assets = tr.gs.addTokensUnbonded(tokens)
candidate.Status = Unbonded
return tr.transfer(tr.params.HoldBonded, tr.params.HoldUnbonded,
sdk.Coins{{tr.params.AllowedBondDenom, tokens}})
}
// move a candidates asset pool from unbonded to bonded pool
func (tr transact) unbondedToBondedPool(candidate *Candidate) error {
func (tr transact) unbondedToBondedPool(candidate *Candidate) {
// replace bonded shares with unbonded shares
// replace unbonded shares with bonded shares
tokens := tr.gs.removeSharesUnbonded(candidate.Assets)
candidate.Assets = tr.gs.addTokensBonded(tokens)
candidate.Status = Bonded
return tr.transfer(tr.params.HoldUnbonded, tr.params.HoldBonded,
sdk.Coins{{tr.params.AllowedBondDenom, tokens}})
}
// return an error if the bonds coins are incorrect
func checkDenom(mapper Mapper, tx BondUpdate) error {
if tx.Bond.Denom != mapper.loadParams().AllowedBondDenom {
return fmt.Errorf("Invalid coin denomination")
func checkDenom(mapper Mapper, bond sdk.Coin) sdk.Error {
if bond.Denom != mapper.loadParams().BondDenom {
return ErrBadBondingDenom()
}
return nil
}
@ -167,48 +151,42 @@ func checkDenom(mapper Mapper, tx BondUpdate) error {
// These functions assume everything has been authenticated,
// now we just perform action and save
func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error {
func (tr transact) declareCandidacy(tx MsgDeclareCandidacy) sdk.Error {
// check to see if the pubkey or sender has been registered before
if tr.mapper.loadCandidate(tx.PubKey) != nil {
return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+
" PubKey %v already registered with %v candidate address",
candidate.PubKey, candidate.Owner)
if tr.mapper.loadCandidate(tx.Address) != nil {
return ErrCandidateExistsAddr()
}
err := checkDenom(tx.BondUpdate, tr.mapper)
err := checkDenom(tr.mapper, tx.Bond)
if err != nil {
return err
}
if tr.IsCheckTx {
if tr.ctx.IsCheckTx() {
return nil
}
// create and save the empty candidate
bond := tr.mapper.loadCandidate(tx.PubKey)
if bond != nil {
return ErrCandidateExistsAddr()
}
candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description)
tr.mapper.saveCandidate(candidate)
// move coins from the tr.sender account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
txDelegate := TxDelegate{tx.BondUpdate}
txDelegate := NewMsgDelegate(tx.Address, tx.Bond)
return tr.delegateWithCandidate(txDelegate, candidate)
}
func (tr transact) editCandidacy(tx TxEditCandidacy) error {
func (tr transact) editCandidacy(tx MsgEditCandidacy) sdk.Error {
// candidate must already be registered
if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
if tr.mapper.loadCandidate(tx.Address) == nil {
return ErrBadCandidateAddr()
}
if tr.IsCheckTx {
if tr.ctx.IsCheckTx() {
return nil
}
// Get the pubKey bond account
candidate := tr.mapper.loadCandidate(tx.PubKey)
candidate := tr.mapper.loadCandidate(tx.Address)
if candidate == nil {
return ErrBondNotNominated()
}
@ -234,28 +212,28 @@ func (tr transact) editCandidacy(tx TxEditCandidacy) error {
return nil
}
func (tr transact) delegate(tx TxDelegate) error {
func (tr transact) delegate(tx MsgDelegate) sdk.Error {
if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist
return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey)
if tr.mapper.loadCandidate(tx.Address) == nil { // does PubKey exist
return ErrBadCandidateAddr()
}
err := checkDenom(tx.BondUpdate, tr.mapper)
err := checkDenom(tr.mapper, tx.Bond)
if err != nil {
return err
}
if tr.IsCheckTx {
if tr.ctx.IsCheckTx() {
return nil
}
// Get the pubKey bond account
candidate := tr.mapper.loadCandidate(tx.PubKey)
candidate := tr.mapper.loadCandidate(tx.Address)
if candidate == nil {
return ErrBondNotNominated()
}
return tr.delegateWithCandidate(tx, candidate)
}
func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error {
func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) sdk.Error {
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
@ -268,37 +246,33 @@ func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) er
poolAccount = tr.params.HoldUnbonded
}
// XXX refactor all steps like this into GlobalState.addBondedTokens()
// Move coins from the delegator account to the bonded pool account
err := tr.transfer(tr.sender, poolAccount, sdk.Coins{tx.Bond})
if err != nil {
return err
}
// Get or create the delegator bond
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address)
if bond == nil {
bond = &DelegatorBond{
PubKey: tx.PubKey,
PubKey: tx.Address,
Shares: sdk.ZeroRat,
}
}
// Account new shares, save
bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, tr.gs))
tr.mapper.saveCandidate(candidate)
err := bond.BondTokens(candidate, tx.Bond, tr)
if err != nil {
return err
}
tr.mapper.saveDelegatorBond(tr.sender, bond)
tr.mapper.saveCandidate(candidate)
tr.mapper.saveGlobalState(tr.gs)
return nil
}
func (tr transact) unbond(tx TxUnbond) error {
func (tr transact) unbond(tx MsgUnbond) sdk.Error {
// check if bond has any shares in it unbond
existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address)
sharesStr := viper.GetString(tx.Shares)
if existingBond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares
return errors.New("no shares in account to unbond")
return ErrInsufficientFunds()
}
// if shares set to special case Max then we're good
@ -315,10 +289,12 @@ func (tr transact) unbond(tx TxUnbond) error {
bond.Shares, tx.Shares)
}
}
// XXX end of old checkTx
if tr.ctx.IsCheckTx() {
return nil
}
// get delegator bond
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey)
bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address)
if bond == nil {
return ErrNoDelegatorForAddress()
}
@ -328,7 +304,6 @@ func (tr transact) unbond(tx TxUnbond) error {
if tx.Shares == "MAX" {
shares = bond.Shares
} else {
var err error
shares, err = sdk.NewRatFromDecimal(tx.Shares)
if err != nil {
return err
@ -342,7 +317,7 @@ func (tr transact) unbond(tx TxUnbond) error {
bond.Shares = bond.Shares.Sub(shares)
// get pubKey candidate
candidate := tr.mapper.loadCandidate(tx.PubKey)
candidate := tr.mapper.loadCandidate(tx.Address)
if candidate == nil {
return ErrNoCandidateForAddress()
}
@ -358,7 +333,7 @@ func (tr transact) unbond(tx TxUnbond) error {
}
// remove the bond
tr.mapper.removeDelegatorBond(tr.sender, tx.PubKey)
tr.mapper.removeDelegatorBond(tr.sender, tx.Address)
} else {
tr.mapper.saveDelegatorBond(tr.sender, bond)
}
@ -373,7 +348,7 @@ func (tr transact) unbond(tx TxUnbond) error {
returnCoins := candidate.removeShares(shares, tr.gs)
err := tr.transfer(poolAccount, tr.sender,
sdk.Coins{{tr.params.AllowedBondDenom, returnCoins}})
sdk.Coins{{tr.params.BondDenom, returnCoins}})
if err != nil {
return err
}
@ -383,10 +358,7 @@ func (tr transact) unbond(tx TxUnbond) error {
// change the share types to unbonded if they were not already
if candidate.Status == Bonded {
err = tr.bondedToUnbondedPool(candidate)
if err != nil {
return err
}
tr.bondedToUnbondedPool(candidate)
}
// lastly update the status
@ -395,7 +367,7 @@ func (tr transact) unbond(tx TxUnbond) error {
// deduct shares from the candidate and save
if candidate.Liabilities.IsZero() {
tr.mapper.removeCandidate(tx.PubKey)
tr.mapper.removeCandidate(tx.Address)
} else {
tr.mapper.saveCandidate(candidate)
}

View File

@ -24,25 +24,24 @@ func initAccounts(n int, amount int64) ([]sdk.Address, map[string]int64) {
return senders, accStore
}
func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy {
return TxDeclareCandidacy{
BondUpdate{
PubKey: pubKey,
Bond: coin.Coin{"fermion", amt},
},
func newTestMsgDeclareCandidacy(amt int64, pubKey crypto.PubKey, address sdk.Address) MsgDeclareCandidacy {
return MsgDeclareCandidacy{
MsgAddr: NewMsgAddr(address),
PubKey: pubKey,
Bond: coin.Coin{"fermion", amt},
Description{},
}
}
func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate {
return TxDelegate{BondUpdate{
PubKey: pubKey,
Bond: coin.Coin{"fermion", amt},
}}
func newTestMsgDelegate(amt int64, address sdk.Address) MsgDelegate {
return MsgDelegate{
MsgAddr: NewMsgAddr(address),
Bond: coin.Coin{"fermion", amt},
}
}
func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond {
return TxUnbond{
func newMsgUnbond(shares string, pubKey crypto.PubKey) MsgUnbond {
return MsgUnbond{
PubKey: pubKey,
Shares: shares,
}
@ -50,14 +49,12 @@ func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond {
func paramsNoInflation() Params {
return Params{
HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")),
HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")),
InflationRateChange: sdk.Zero,
InflationMax: sdk.Zero,
InflationMin: sdk.Zero,
GoalBonded: sdk.New(67, 100),
MaxVals: 100,
AllowedBondDenom: "fermion",
BondDenom: "fermion",
GasDeclareCandidacy: 20,
GasEditCandidacy: 20,
GasDelegate: 20,
@ -72,7 +69,7 @@ func newTestTransact(t, sender sdk.Address, isCheckTx bool) transact {
newTransact(ctx, sender, mapper, coinKeeper)
}
func TestDuplicatesTxDeclareCandidacy(t *testing.T) {
func TestDuplicatesMsgDeclareCandidacy(t *testing.T) {
senders, accStore := initAccounts(2, 1000) // for accounts
deliverer := newDeliver(t, senders[0], accStore)
@ -81,9 +78,9 @@ func TestDuplicatesTxDeclareCandidacy(t *testing.T) {
sender: senders[0],
}
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(t, got, "expected no error on runTxDeclareCandidacy")
assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
// one sender can bond to two different pubKeys
txDeclareCandidacy.PubKey = pks[1]
@ -97,21 +94,21 @@ func TestDuplicatesTxDeclareCandidacy(t *testing.T) {
assert.NotNil(t, err, "expected error on checkTx")
}
func TestIncrementsTxDelegate(t *testing.T) {
func TestIncrementsMsgDelegate(t *testing.T) {
initSender := int64(1000)
senders, accStore := initAccounts(1, initSender) // for accounts
deliverer := newDeliver(t, senders[0], accStore)
// first declare candidacy
bondAmount := int64(10)
txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0])
txDeclareCandidacy := newTestMsgDeclareCandidacy(bondAmount, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got)
expectedBond := bondAmount // 1 since we send 1 at the start of loop,
// just send the same txbond multiple times
holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates
txDelegate := newTxDelegate(bondAmount, pks[0])
txDelegate := newTestMsgDelegate(bondAmount, pks[0])
for i := 0; i < 5; i++ {
got := deliverer.delegate(txDelegate)
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
@ -129,7 +126,7 @@ func TestIncrementsTxDelegate(t *testing.T) {
}
}
func TestIncrementsTxUnbond(t *testing.T) {
func TestIncrementsMsgUnbond(t *testing.T) {
initSender := int64(0)
senders, accStore := initAccounts(1, initSender) // for accounts
deliverer := newDeliver(t, senders[0], accStore)
@ -137,7 +134,7 @@ func TestIncrementsTxUnbond(t *testing.T) {
// set initial bond
initBond := int64(1000)
accStore[string(deliverer.sender.Address)] = initBond
got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0]))
got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(initBond, pks[0]))
assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got)
// just send the same txunbond multiple times
@ -145,7 +142,7 @@ func TestIncrementsTxUnbond(t *testing.T) {
// XXX use decimals here
unbondShares, unbondSharesStr := int64(10), "10"
txUndelegate := newTxUnbond(unbondSharesStr, pks[0])
txUndelegate := newMsgUnbond(unbondSharesStr, pks[0])
nUnbonds := 5
for i := 0; i < nUnbonds; i++ {
got := deliverer.unbond(txUndelegate)
@ -174,7 +171,7 @@ func TestIncrementsTxUnbond(t *testing.T) {
}
for _, c := range errorCases {
unbondShares := strconv.Itoa(int(c))
txUndelegate := newTxUnbond(unbondShares, pks[0])
txUndelegate := newMsgUnbond(unbondShares, pks[0])
got = deliverer.unbond(txUndelegate)
assert.Error(t, got, "expected unbond tx to fail")
}
@ -182,17 +179,17 @@ func TestIncrementsTxUnbond(t *testing.T) {
leftBonded := initBond - unbondShares*int64(nUnbonds)
// should be unable to unbond one more than we have
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0])
txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)+1), pks[0])
got = deliverer.unbond(txUndelegate)
assert.Error(t, got, "expected unbond tx to fail")
// should be able to unbond just what we have
txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0])
txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)), pks[0])
got = deliverer.unbond(txUndelegate)
assert.NoError(t, got, "expected unbond tx to pass")
}
func TestMultipleTxDeclareCandidacy(t *testing.T) {
func TestMultipleMsgDeclareCandidacy(t *testing.T) {
initSender := int64(1000)
senders, accStore := initAccounts(3, initSender)
pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]}
@ -200,7 +197,7 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) {
// bond them all
for i, sender := range senders {
txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i])
txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pubKeys[i])
deliverer.sender = sender
got := deliverer.declareCandidacy(txDeclareCandidacy)
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
@ -217,7 +214,7 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) {
// unbond them all
for i, sender := range senders {
candidatePre := loadCandidate(deliverer.store, pubKeys[i])
txUndelegate := newTxUnbond("10", pubKeys[i])
txUndelegate := newMsgUnbond("10", pubKeys[i])
deliverer.sender = sender
got := deliverer.unbond(txUndelegate)
assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
@ -233,19 +230,19 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) {
}
}
func TestMultipleTxDelegate(t *testing.T) {
func TestMultipleMsgDelegate(t *testing.T) {
accounts, accStore := initAccounts(3, 1000)
sender, delegators := accounts[0], accounts[1:]
deliverer := newDeliver(t, sender, accStore)
//first make a candidate
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
require.NoError(t, got, "expected tx to be ok, got %v", got)
// delegate multiple parties
for i, delegator := range delegators {
txDelegate := newTxDelegate(10, pks[0])
txDelegate := newTestMsgDelegate(10, pks[0])
deliverer.sender = delegator
got := deliverer.delegate(txDelegate)
require.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
@ -257,7 +254,7 @@ func TestMultipleTxDelegate(t *testing.T) {
// unbond them all
for i, delegator := range delegators {
txUndelegate := newTxUnbond("10", pks[0])
txUndelegate := newMsgUnbond("10", pks[0])
deliverer.sender = delegator
got := deliverer.unbond(txUndelegate)
require.NoError(t, got, "expected tx %d to be ok, got %v", i, got)
@ -274,21 +271,21 @@ func TestVoidCandidacy(t *testing.T) {
deliverer := newDeliver(t, sender, accStore)
// create the candidate
txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0])
txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0])
got := deliverer.declareCandidacy(txDeclareCandidacy)
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
// bond a delegator
txDelegate := newTxDelegate(10, pks[0])
txDelegate := newTestMsgDelegate(10, pks[0])
deliverer.sender = delegator
got = deliverer.delegate(txDelegate)
require.NoError(t, got, "expected ok, got %v", got)
// unbond the candidates bond portion
txUndelegate := newTxUnbond("10", pks[0])
txUndelegate := newMsgUnbond("10", pks[0])
deliverer.sender = sender
got = deliverer.unbond(txUndelegate)
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
// test that this pubkey cannot yet be bonded too
deliverer.sender = delegator
@ -297,7 +294,7 @@ func TestVoidCandidacy(t *testing.T) {
// test that the delegator can still withdraw their bonds
got = deliverer.unbond(txUndelegate)
require.NoError(t, got, "expected no error on runTxDeclareCandidacy")
require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
// verify that the pubkey can now be reused
got = deliverer.declareCandidacy(txDeclareCandidacy)

View File

@ -1,8 +1,6 @@
package stake
import (
crypto "github.com/tendermint/go-crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
@ -10,9 +8,9 @@ import (
//nolint
var (
// Keys for store prefixes
CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys
ParamKey = []byte{0x02} // key for global parameters relating to staking
GlobalStateKey = []byte{0x03} // key for global parameters relating to staking
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
@ -22,29 +20,29 @@ var (
DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond
)
// GetCandidateKey - get the key for the candidate with pubKey
func GetCandidateKey(pubKey crypto.PubKey) []byte {
return append(CandidateKeyPrefix, pubKey.Bytes()...)
// 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(pubKey crypto.PubKey, power sdk.Rational) []byte {
b, _ := cdc.MarshalJSON(power) // TODO need to handle error here?
return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store
func GetValidatorKey(address sdk.Address, power sdk.Rational) []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(pubKey crypto.PubKey) []byte {
return append(ValidatorUpdatesKeyPrefix, pubKey.Bytes()...) // TODO does this need prefix if its in its own 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 crypto.Address, candidate crypto.PubKey) []byte {
func GetDelegatorBondKey(delegator, candidate sdk.Address) []byte {
return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...)
}
// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates
func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte {
func GetDelegatorBondKeyPrefix(delegator sdk.Address) []byte {
res, err := cdc.MarshalJSON(&delegator)
if err != nil {
panic(err)
@ -53,7 +51,7 @@ func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte {
}
// GetDelegatorBondsKey - get the key for list of all the delegator's bonds
func GetDelegatorBondsKey(delegator crypto.Address) []byte {
func GetDelegatorBondsKey(delegator sdk.Address) []byte {
res, err := cdc.MarshalJSON(&delegator)
if err != nil {
panic(err)
@ -69,20 +67,15 @@ type Mapper struct {
cdc *wire.Codec
}
func NewMapper(ctx sdk.Context, key sdk.StoreKey) Mapper {
cdc := wire.NewCodec()
cdc.RegisterInterface((*sdk.Rational)(nil), nil) // XXX make like crypto.RegisterWire()
cdc.RegisterConcrete(sdk.Rat{}, "rat", nil)
crypto.RegisterWire(cdc)
func NewMapper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey) Mapper {
return StakeMapper{
store: ctx.KVStore(m.key),
cdc: cdc,
}
}
func (m Mapper) loadCandidate(pubKey crypto.PubKey) *Candidate {
b := m.store.Get(GetCandidateKey(pubKey))
func (m Mapper) loadCandidate(address sdk.Address) *Candidate {
b := m.store.Get(GetCandidateKey(address))
if b == nil {
return nil
}
@ -97,28 +90,28 @@ func (m Mapper) loadCandidate(pubKey crypto.PubKey) *Candidate {
func (m Mapper) saveCandidate(candidate *Candidate) {
// XXX should only remove validator if we know candidate is a validator
removeValidator(m.store, candidate.PubKey)
validator := &Validator{candidate.PubKey, candidate.VotingPower}
removeValidator(m.store, candidate.Address)
validator := &Validator{candidate.Address, candidate.VotingPower}
updateValidator(m.store, validator)
b, err := cdc.MarshalJSON(*candidate)
if err != nil {
panic(err)
}
m.store.Set(GetCandidateKey(candidate.PubKey), b)
m.store.Set(GetCandidateKey(candidate.Address), b)
}
func (m Mapper) removeCandidate(pubKey crypto.PubKey) {
func (m Mapper) removeCandidate(address sdk.Address) {
// XXX should only remove validator if we know candidate is a validator
removeValidator(m.store, pubKey)
m.store.Delete(GetCandidateKey(pubKey))
removeValidator(m.store, address)
m.store.Delete(GetCandidateKey(address))
}
//___________________________________________________________________________
//func loadValidator(m.store sdk.KVStore, pubKey crypto.PubKey, votingPower sdk.Rational) *Validator {
//b := m.store.Get(GetValidatorKey(pubKey, votingPower))
//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
//}
@ -140,25 +133,25 @@ func (m Mapper) updateValidator(validator *Validator) {
}
// add to the validators to update list if necessary
m.store.Set(GetValidatorUpdatesKey(validator.PubKey), b)
m.store.Set(GetValidatorUpdatesKey(validator.Address), b)
// update the list ordered by voting power
m.store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b)
m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower), b)
}
func (m Mapper) removeValidator(pubKey crypto.PubKey) {
func (m Mapper) removeValidator(address sdk.Address) {
//add validator with zero power to the validator updates
b, err := cdc.MarshalJSON(Validator{pubKey, sdk.ZeroRat})
b, err := cdc.MarshalJSON(Validator{address, sdk.ZeroRat})
if err != nil {
panic(err)
}
m.store.Set(GetValidatorUpdatesKey(pubKey), b)
m.store.Set(GetValidatorUpdatesKey(address), b)
// now actually delete from the validator set
candidate := loadCandidate(m.store, pubKey)
candidate := loadCandidate(m.store, address)
if candidate != nil {
m.store.Delete(GetValidatorKey(pubKey, candidate.VotingPower))
m.store.Delete(GetValidatorKey(address, candidate.VotingPower))
}
}
@ -243,14 +236,14 @@ func (m Mapper) loadCandidates() (candidates Candidates) {
//_____________________________________________________________________
// load the pubkeys of all candidates a delegator is delegated too
func (m Mapper) loadDelegatorCandidates(delegator crypto.Address) (candidates []crypto.PubKey) {
func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs []sdk.Address) {
candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator))
if candidateBytes == nil {
return nil
}
err := cdc.UnmarshalJSON(candidateBytes, &candidates)
err := cdc.UnmarshalJSON(candidateBytes, &candidateAddrs)
if err != nil {
panic(err)
}
@ -259,8 +252,7 @@ func (m Mapper) loadDelegatorCandidates(delegator crypto.Address) (candidates []
//_____________________________________________________________________
func (m Mapper) loadDelegatorBond(delegator crypto.Address,
candidate crypto.PubKey) *DelegatorBond {
func (m Mapper) loadDelegatorBond(delegator, candidate sdk.Address) *DelegatorBond {
delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate))
if delegatorBytes == nil {
@ -275,13 +267,13 @@ func (m Mapper) loadDelegatorBond(delegator crypto.Address,
return bond
}
func (m Mapper) saveDelegatorBond(delegator crypto.Address,
func (m Mapper) saveDelegatorBond(delegator sdk.Address,
bond *DelegatorBond) {
// if a new bond add to the list of bonds
if loadDelegatorBond(m.store, delegator, bond.PubKey) == nil {
if loadDelegatorBond(m.store, delegator, bond.Address) == nil {
pks := loadDelegatorCandidates(m.store, delegator)
pks = append(pks, (*bond).PubKey)
pks = append(pks, (*bond).Address)
b, err := cdc.MarshalJSON(pks)
if err != nil {
panic(err)
@ -294,11 +286,11 @@ func (m Mapper) saveDelegatorBond(delegator crypto.Address,
if err != nil {
panic(err)
}
m.store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b)
m.store.Set(GetDelegatorBondKey(delegator, bond.Address), b)
//updateDelegatorBonds(store, delegator)
}
func (m Mapper) removeDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) {
func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidate sdk.Address) {
// TODO use list queries on multistore to remove iterations here!
// first remove from the list of bonds
pks := loadDelegatorCandidates(m.store, delegator)

View File

@ -1,52 +1,204 @@
package stake
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// Tx
//--------------------------------------------------------------------------------
// register the tx type with its validation logic
// make sure to use the name of the handler as the prefix in the tx type,
// so it gets routed properly
const (
ByteTxDeclareCandidacy = 0x55
ByteTxEditCandidacy = 0x56
ByteTxDelegate = 0x57
ByteTxUnbond = 0x58
TypeTxDeclareCandidacy = "staking/declareCandidacy"
TypeTxEditCandidacy = "staking/editCandidacy"
TypeTxDelegate = "staking/delegate"
TypeTxUnbond = "staking/unbond"
)
//func init() {
//sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy)
//sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy)
//sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate)
//sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond)
//}
//Verify interface at compile time
//var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{}
var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{}
// BondUpdate - struct for bonding or unbonding transactions
type BondUpdate struct {
PubKey crypto.PubKey `json:"pub_key"`
Bond sdk.Coin `json:"amount"`
//______________________________________________________________________
// 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 "stake" }
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 (tx BondUpdate) ValidateBasic() error {
if tx.PubKey.Empty() {
func (msg MsgAddr) ValidateBasic() sdk.Error {
if msg.Address.Empty() {
return errCandidateEmpty
}
coins := sdk.Coins{tx.Bond}
}
//______________________________________________________________________
// MsgDeclareCandidacy - struct for unbonding transactions
type MsgDeclareCandidacy struct {
MsgAddr
Description
Bond sdk.Coin `json:"bond"`
PubKey crypto.PubKey `json:"pubkey"`
}
func NewMsgDeclareCandidacy(bond sdk.Coin, address sdk.Address, pubkey crypto.PubKey, 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 := MsgAddr.ValidateBasic()
if err != nil {
return err
}
err := validateCoin(msg.Bond)
if err != nil {
return err
}
empty := Description{}
if msg.Description == empty {
return fmt.Errorf("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 := MsgAddr.ValidateBasic()
if err != nil {
return err
}
empty := Description{}
if msg.Description == empty {
return fmt.Errorf("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 := 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(shares string, address sdk.Address) MsgDelegate {
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 := MsgAddr.ValidateBasic()
if err != nil {
return err
}
if msg.Shares {
return ErrCandidateEmpty()
}
return nil
}
//______________________________________________________________________
// helper
func validateCoin(coin coin.Coin) sdk.Error {
coins := sdk.Coins{bond}
if !sdk.IsValid() {
return sdk.ErrInvalidCoins()
}
@ -55,92 +207,3 @@ func (tx BondUpdate) ValidateBasic() error {
}
return nil
}
// TxDeclareCandidacy - struct for unbonding transactions
type TxDeclareCandidacy struct {
BondUpdate
Description
}
// NewTxDeclareCandidacy - new TxDeclareCandidacy
func NewTxDeclareCandidacy(bond sdk.Coin, pubKey crypto.PubKey, description Description) sdk.Tx {
return TxDeclareCandidacy{
BondUpdate{
PubKey: pubKey,
Bond: bond,
},
description,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} }
// TxEditCandidacy - struct for editing a candidate
type TxEditCandidacy struct {
PubKey crypto.PubKey `json:"pub_key"`
Description
}
// NewTxEditCandidacy - new TxEditCandidacy
func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx {
return TxEditCandidacy{
PubKey: pubKey,
Description: description,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} }
// ValidateBasic - Check for non-empty candidate,
func (tx TxEditCandidacy) ValidateBasic() error {
if tx.PubKey.Empty() {
return errCandidateEmpty
}
empty := Description{}
if tx.Description == empty {
return fmt.Errorf("Transaction must include some information to modify")
}
return nil
}
// TxDelegate - struct for bonding transactions
type TxDelegate struct{ BondUpdate }
// NewTxDelegate - new TxDelegate
func NewTxDelegate(bond sdk.Coin, pubKey crypto.PubKey) sdk.Tx {
return TxDelegate{BondUpdate{
PubKey: pubKey,
Bond: bond,
}}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} }
// TxUnbond - struct for unbonding transactions
type TxUnbond struct {
PubKey crypto.PubKey `json:"pub_key"`
Shares string `json:"amount"`
}
// NewTxUnbond - new TxUnbond
func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx {
return TxUnbond{
PubKey: pubKey,
Shares: shares,
}.Wrap()
}
// Wrap - Wrap a Tx as a Basecoin Tx
func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} }
// ValidateBasic - Check for non-empty candidate, positive shares
func (tx TxUnbond) ValidateBasic() error {
if tx.PubKey.Empty() {
return errCandidateEmpty
}
return nil
}

View File

@ -24,29 +24,40 @@ var (
coinNegNotAtoms = sdk.Coin{"foo", -10000}
)
func TestBondUpdateValidateBasic(t *testing.T) {
func TestMsgAddrValidateBasic(t *testing.T) {
tests := []struct {
name string
PubKey crypto.PubKey
Bond sdk.Coin
address sdk.Address
wantErr bool
}{
{"basic good", pks[0], coinPos, false},
{"empty delegator", crypto.PubKey{}, coinPos, true},
{"zero coin", pks[0], coinZero, true},
{"neg coin", pks[0], coinNeg, true},
{"basic good", pks[0], false},
{"empty delegator", crypto.PubKey{}, true},
}
for _, tc := range tests {
tx := TxDelegate{BondUpdate{
PubKey: tc.PubKey,
Bond: tc.Bond,
}}
tx := NewMsgAddr(tc.address)
assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil,
"test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic())
}
}
func TestValidateCoin(t *testing.T) {
tests := []struct {
name string
coin sdk.Coin
wantErr bool
}{
{"basic good", coinPos, false},
{"zero coin", coinZero, true},
{"neg coin", coinNeg, true},
}
for _, tc := range tests {
assert.Equal(t, tc.wantErr, tx.validateCoin(tc.coin) != nil,
"test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic())
}
}
func TestAllAreTx(t *testing.T) {
// make sure all types construct properly
@ -54,23 +65,20 @@ func TestAllAreTx(t *testing.T) {
bondAmt := 1234321
bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)}
// Note that Wrap is only defined on BondUpdate, so when you call it,
// you lose all info on the embedding type. Please add Wrap()
// method to all the parents
txDelegate := NewTxDelegate(bond, pubKey)
_, ok := txDelegate.Unwrap().(TxDelegate)
txDelegate := NewMsgDelegate(bond, pubKey)
_, ok := txDelegate.(MsgDelegate)
assert.True(t, ok, "%#v", txDelegate)
txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey)
_, ok = txUnbond.Unwrap().(TxUnbond)
txUnbond := NewMsgUnbond(strconv.Itoa(bondAmt), pubKey)
_, ok = txUnbond.(MsgUnbond)
assert.True(t, ok, "%#v", txUnbond)
txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{})
_, ok = txDecl.Unwrap().(TxDeclareCandidacy)
txDecl := NewMsgDeclareCandidacy(bond, pubKey, Description{})
_, ok = txDecl.(MsgDeclareCandidacy)
assert.True(t, ok, "%#v", txDecl)
txEditCan := NewTxEditCandidacy(pubKey, Description{})
_, ok = txEditCan.Unwrap().(TxEditCandidacy)
txEditCan := NewMsgEditCandidacy(pubKey, Description{})
_, ok = txEditCan.(MsgEditCandidacy)
assert.True(t, ok, "%#v", txEditCan)
}
@ -84,9 +92,9 @@ func TestSerializeTx(t *testing.T) {
tests := []struct {
tx sdk.Tx
}{
{NewTxUnbond(strconv.Itoa(bondAmt), pubKey)},
{NewTxDeclareCandidacy(bond, pubKey, Description{})},
{NewTxDeclareCandidacy(bond, pubKey, Description{})},
{NewMsgUnbond(strconv.Itoa(bondAmt), pubKey)},
{NewMsgDeclareCandidacy(bond, pubKey, Description{})},
{NewMsgDeclareCandidacy(bond, pubKey, Description{})},
// {NewTxRevokeCandidacy(pubKey)},
}

View File

@ -13,8 +13,8 @@ type Params struct {
InflationMin sdk.Rational `json:"inflation_min"` // minimum inflation rate
GoalBonded sdk.Rational `json:"goal_bonded"` // Goal of percent bonded atoms
MaxVals uint16 `json:"max_vals"` // maximum number of validators
AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination
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"`
@ -30,7 +30,7 @@ func defaultParams() Params {
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
MaxVals: 100,
AllowedBondDenom: "fermion",
BondDenom: "fermion",
GasDeclareCandidacy: 20,
GasEditCandidacy: 20,
GasDelegate: 20,
@ -92,6 +92,7 @@ func (gs *GlobalState) unbondedShareExRate() sdk.Rational {
// XXX XXX XXX
// expand to include the function of actually transfering the tokens
//XXX CONFIRM that use of the exRate is correct with Zarko Spec!
func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) {
issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
gs.BondedPool += amount
@ -99,6 +100,7 @@ func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational)
return
}
//XXX CONFIRM that use of the exRate is correct with Zarko Spec!
func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens int64) {
removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
gs.BondedShares = gs.BondedShares.Sub(shares)
@ -106,6 +108,7 @@ func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens in
return
}
//XXX CONFIRM that use of the exRate is correct with Zarko Spec!
func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rational) {
issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares)
@ -113,6 +116,7 @@ func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rationa
return
}
//XXX CONFIRM that use of the exRate is correct with Zarko Spec!
func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rational) (removedTokens int64) {
removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
gs.UnbondedShares = gs.UnbondedShares.Sub(shares)
@ -144,7 +148,7 @@ const (
type Candidate struct {
Status CandidateStatus `json:"status"` // Bonded status
PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate
Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here
Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here
Assets sdk.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares
Liabilities sdk.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares
VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator
@ -160,11 +164,11 @@ type Description struct {
}
// NewCandidate - initialize a new candidate
func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate {
func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate {
return &Candidate{
Status: Unbonded,
PubKey: pubKey,
Owner: owner,
PubKey: pubKet,
Address: address,
Assets: sdk.ZeroRat,
Liabilities: sdk.ZeroRat,
VotingPower: sdk.ZeroRat,
@ -172,8 +176,6 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri
}
}
// XXX define candidate interface?
// get the exchange rate of global pool shares over delegator shares
func (c *Candidate) delegatorShareExRate() sdk.Rational {
if c.Liabilities.IsZero() {
@ -254,6 +256,37 @@ type Candidates []*Candidate
// owned by one delegator, and is associated with the voting power of one
// pubKey.
type DelegatorBond struct {
PubKey crypto.PubKey `json:"pub_key"`
Shares sdk.Rational `json:"shares"`
Address sdk.Address `json:"pub_key"`
Shares sdk.Rational `json:"shares"`
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error {
_, err := tr.coinKeeper.SubtractCoins(tr.ctx, d.Address, sdk.Coins{tokens})
if err != nil {
return err
}
newShares = candidate.addTokens(tokens.Amount, tr.gs)
bond.Shares = bond.Shares.Add(newShares)
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func (bond *DelegatorBond) UnbondCoins(candidate *Candidate, shares int64, tr transact) sdk.Error {
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) {
return ErrInsufficientFunds()
}
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, d.Address, returnCoins)
if err != nil {
return err
}
return nil
}