diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index bd87ec0285..7010ee153d 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -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 diff --git a/examples/basecoin/x/cool/types.go b/examples/basecoin/x/cool/types.go index a3fa6ca48e..76e7bb2920 100644 --- a/examples/basecoin/x/cool/types.go +++ b/examples/basecoin/x/cool/types.go @@ -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 diff --git a/types/rational.go b/types/rational.go index 857a6696eb..907ff2f319 100644 --- a/types/rational.go +++ b/types/rational.go @@ -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 { diff --git a/types/tx_msg.go b/types/tx_msg.go index 79272f3862..16e5c59eb8 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -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) } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 76bf5753dd..c264dfd80a 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -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) } diff --git a/x/stake/errors.go b/x/stake/errors.go index 1f5ab2fc61..fae872f1c1 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -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") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 5e79dd586b..ae5dc42836 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -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) } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 4cae995604..4e5a1ee4ca 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -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) diff --git a/x/stake/mapper.go b/x/stake/mapper.go index 296fe0aa32..22a9f96e53 100644 --- a/x/stake/mapper.go +++ b/x/stake/mapper.go @@ -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) diff --git a/x/stake/tx.go b/x/stake/tx.go index 8ed6089870..3bc58b0dd8 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -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 -} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go index f828782cda..156a8f120e 100644 --- a/x/stake/tx_test.go +++ b/x/stake/tx_test.go @@ -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)}, } diff --git a/x/stake/types.go b/x/stake/types.go index 7bf2546ff3..fe7a4ebf0a 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -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 }