diff --git a/x/stake/errors.go b/x/stake/errors.go index bd1992959..e5b3e0cb3 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -78,6 +78,9 @@ func ErrBadDelegatorAddr() sdk.Error { func ErrCandidateExistsAddr() sdk.Error { return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy") } +func ErrCandidateRevoked() sdk.Error { + return newError(CodeInvalidValidator, "Candidacy for this address is currently revoked") +} func ErrMissingSignature() sdk.Error { return newError(CodeInvalidValidator, "Missing signature") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 16db47b01..094d01aea 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -15,40 +15,6 @@ const ( GasUnbond int64 = 20 ) -//XXX fix initstater -// separated for testing -//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error { - -//params := k.GetParams(ctx) -//switch key { -//case "allowed_bond_denom": -//params.BondDenom = value -//case "max_vals", "gas_bond", "gas_unbond": - -//i, err := strconv.Atoi(value) -//if err != nil { -//return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error())) -//} - -//switch key { -//case "max_vals": -//if i < 0 { -//return sdk.ErrUnknownRequest("cannot designate negative max validators") -//} -//params.MaxValidators = uint16(i) -//case "gas_bond": -//GasDelegate = int64(i) -//case "gas_unbound": -//GasUnbond = int64(i) -//} -//default: -//return sdk.ErrUnknownRequest(key) -//} - -//k.setParams(params) -//return nil -//} - //_______________________________________________________________________ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { @@ -138,6 +104,9 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { if msg.Bond.Denom != k.GetParams(ctx).BondDenom { return ErrBadBondingDenom().Result() } + if candidate.Status == Revoked { + return ErrCandidateRevoked().Result() + } if ctx.IsCheckTx() { return sdk.Result{ GasUsed: GasDelegate, @@ -150,17 +119,14 @@ func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { return sdk.Result{} } +// common functionality between handlers func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, bondAmt sdk.Coin, candidate Candidate) sdk.Error { - if candidate.Status == Revoked { //candidate has been withdrawn - return ErrBondNotNominated() - } - // Get or create the delegator bond - existingBond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address) + bond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address) if !found { - existingBond = DelegatorBond{ + bond = DelegatorBond{ DelegatorAddr: delegatorAddr, CandidateAddr: candidate.Address, Shares: sdk.ZeroRat, @@ -168,28 +134,17 @@ func delegate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, } // Account new shares, save - err := BondCoins(ctx, k, existingBond, candidate, bondAmt) + pool := k.GetPool(ctx) + _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{bondAmt}) if err != nil { return err } - k.setDelegatorBond(ctx, existingBond) - k.setCandidate(ctx, candidate) - return nil -} - -// Perform all the actions required to bond tokens to a delegator bond from their account -func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error { - - _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount}) - if err != nil { - return err - } - p := k.GetPool(ctx) - p, candidate, newShares := p.candidateAddTokens(candidate, amount.Amount) + pool, candidate, newShares := pool.candidateAddTokens(candidate, bondAmt.Amount) bond.Shares = bond.Shares.Add(newShares) - k.setPool(ctx, p) - k.setCandidate(ctx, candidate) + k.setDelegatorBond(ctx, bond) + k.setCandidate(ctx, candidate) + k.setPool(ctx, pool) return nil } @@ -216,7 +171,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { return ErrNotEnoughBondShares(msg.Shares).Result() } } else { - if !bond.Shares.GT(shares) { + if bond.Shares.LT(shares) { return ErrNotEnoughBondShares(msg.Shares).Result() } } @@ -259,11 +214,12 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // Add the coins p := k.GetPool(ctx) - var returnAmount int64 - p, candidate, returnAmount = p.candidateRemoveShares(candidate, shares) + p, candidate, returnAmount := p.candidateRemoveShares(candidate, shares) returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) + ///////////////////////////////////// + // revoke candidate if necessary if revokeCandidacy { @@ -286,26 +242,39 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { return sdk.Result{} } -// XXX where this used -// Perform all the actions required to bond tokens to a delegator bond from their account -func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error { +// TODO use or remove +//// Perform all the actions required to bond tokens to a delegator bond from their account +//func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, +//candidate Candidate, amount sdk.Coin) (DelegatorBond, Candidate, Pool, sdk.Error) { - // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { - return sdk.ErrInsufficientFunds("") //XXX variables inside - } - bond.Shares = bond.Shares.Sub(shares) +//pool := k.GetPool(ctx) +//_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount}) +//if err != nil { +//return bond, candidate, pool, err +//} +//pool, candidate, newShares := pool.candidateAddTokens(candidate, amount.Amount) +//bond.Shares = bond.Shares.Add(newShares) +//return bond, candidate, pool, nil +//} +//// Perform all the actions required to bond tokens to a delegator bond from their account +//func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, +//candidate Candidate, shares sdk.Rat) (DelegatorBond, Candidate, Pool, sdk.Error) { - p := k.GetPool(ctx) - var returnAmount int64 - p, candidate, returnAmount = p.candidateRemoveShares(candidate, shares) - returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} +//pool := k.GetPool(ctx) - _, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) - if err != nil { - return err - } - k.setPool(ctx, p) - k.setCandidate(ctx, candidate) - return nil -} +//// subtract bond tokens from delegator bond +//if bond.Shares.LT(shares) { +//errMsg := fmt.Sprintf("cannot unbond %v shares, only have %v shares available", shares, bond.Shares) +//return bond, candidate, pool, sdk.ErrInsufficientFunds(errMsg) +//} +//bond.Shares = bond.Shares.Sub(shares) + +//pool, candidate, returnAmount := p.candidateRemoveShares(candidate, shares) +//returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} + +//_, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) +//if err != nil { +//return err +//} +//return bond, candidate, pool, nil +//} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index d81379491..1f0bc6415 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -23,7 +23,7 @@ func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt i } } -func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate { +func newTestMsgDelegate(delegatorAddr, candidateAddr sdk.Address, amt int64) MsgDelegate { return MsgDelegate{ DelegatorAddr: delegatorAddr, CandidateAddr: candidateAddr, @@ -31,12 +31,24 @@ func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) Msg } } +//______________________________________________________________________ + func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { ctx, _, keeper := createTestInput(t, false, 1000) - msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + candidateAddr := addrs[0] + pk := pks[0] + msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pk, 10) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "%v", got) + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + assert.Equal(t, Unbonded, candidate.Status) + assert.Equal(t, candidateAddr, candidate.Address) + assert.Equal(t, pk, candidate.PubKey) + assert.Equal(t, sdk.NewRat(10), candidate.Assets) + assert.Equal(t, sdk.NewRat(10), candidate.Liabilities) + assert.Equal(t, Description{}, candidate.Description) // one candidate cannot bond twice msgDeclareCandidacy.PubKey = pks[1] @@ -56,22 +68,41 @@ func TestIncrementsMsgDelegate(t *testing.T) { msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], bondAmount) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "expected declare candidacy msg to be ok, got %v", got) - expectedBond := bondAmount + + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + assert.Equal(t, bondAmount, candidate.Liabilities.Evaluate()) + assert.Equal(t, bondAmount, candidate.Assets.Evaluate()) // just send the same msgbond multiple times - msgDelegate := newTestMsgDelegate(bondAmount, delegatorAddr, candidateAddr) + msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, bondAmount) for i := 0; i < 5; i++ { got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values - candidates := keeper.GetCandidates(ctx, 100) - expectedBond += bondAmount - expectedDelegator := initBond - expectedBond - gotBonded := candidates[0].Liabilities.Evaluate() - gotDelegator := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) - require.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) - require.Equal(t, expectedDelegator, gotDelegator, "i: %v, %v, %v", i, expectedDelegator, gotDelegator) // XXX fix + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + bond, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr) + require.True(t, found) + + expBond := int64(i+1) * bondAmount + expLiabilities := int64(i+2) * bondAmount // (1 self delegation) + expDelegatorAcc := initBond - expBond + + gotBond := bond.Shares.Evaluate() + gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + require.Equal(t, expBond, gotBond, + "i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n", + i, expBond, gotBond, candidate, bond) + require.Equal(t, expLiabilities, gotLiabilities, + "i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n", + i, expLiabilities, gotLiabilities, candidate, bond) + require.Equal(t, expDelegatorAcc, gotDelegatorAcc, + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, candidate, bond) } } @@ -82,13 +113,20 @@ func TestIncrementsMsgUnbond(t *testing.T) { // declare candidacy, delegate candidateAddr, delegatorAddr := addrs[0], addrs[1] + msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], initBond) got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) assert.True(t, got.IsOK(), "expected declare-candidacy to be ok, got %v", got) - msgDelegate := newTestMsgDelegate(initBond, delegatorAddr, candidateAddr) + + msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) assert.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + assert.Equal(t, initBond*2, candidate.Liabilities.Evaluate()) + assert.Equal(t, initBond*2, candidate.Assets.Evaluate()) + // just send the same msgUnbond multiple times // TODO use decimals here unbondShares, unbondSharesStr := int64(10), "10" @@ -99,15 +137,28 @@ func TestIncrementsMsgUnbond(t *testing.T) { require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values - candidate, found := keeper.GetCandidate(ctx, candidateAddr) + candidate, found = keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + bond, found := keeper.getDelegatorBond(ctx, delegatorAddr, candidateAddr) require.True(t, found) - expectedBond := initBond - int64(i+1)*unbondShares - expectedDelegator := initBond - expectedBond - gotBonded := candidate.Liabilities.Evaluate() - gotDelegator := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) - require.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) - require.Equal(t, expectedDelegator, gotDelegator, "%v, %v", expectedDelegator, gotDelegator) + expBond := initBond - int64(i+1)*unbondShares + expLiabilities := 2*initBond - int64(i+1)*unbondShares + expDelegatorAcc := initBond - expBond + + gotBond := bond.Shares.Evaluate() + gotLiabilities := candidate.Liabilities.Evaluate() + gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) + + require.Equal(t, expBond, gotBond, + "i: %v\nexpBond: %v\ngotBond: %v\ncandidate: %v\nbond: %v\n", + i, expBond, gotBond, candidate, bond) + require.Equal(t, expLiabilities, gotLiabilities, + "i: %v\nexpLiabilities: %v\ngotLiabilities: %v\ncandidate: %v\nbond: %v\n", + i, expLiabilities, gotLiabilities, candidate, bond) + require.Equal(t, expDelegatorAcc, gotDelegatorAcc, + "i: %v\nexpDelegatorAcc: %v\ngotDelegatorAcc: %v\ncandidate: %v\nbond: %v\n", + i, expDelegatorAcc, gotDelegatorAcc, candidate, bond) } // these are more than we have bonded now @@ -128,14 +179,18 @@ func TestIncrementsMsgUnbond(t *testing.T) { leftBonded := initBond - unbondShares*int64(numUnbonds) // should be unable to unbond one more than we have - msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, strconv.Itoa(int(leftBonded)+1)) + unbondSharesStr = strconv.Itoa(int(leftBonded) + 1) + msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr) got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.False(t, got.IsOK(), "expected unbond msg to fail") + assert.False(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) // should be able to unbond just what we have - msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, strconv.Itoa(int(leftBonded))) + unbondSharesStr = strconv.Itoa(int(leftBonded)) + msgUnbond = NewMsgUnbond(delegatorAddr, candidateAddr, unbondSharesStr) got = handleMsgUnbond(ctx, msgUnbond, keeper) - assert.True(t, got.IsOK(), "expected unbond msg to pass") + assert.True(t, got.IsOK(), + "got: %v\nmsgUnbond: %v\nshares: %v\nleftBonded: %v\n", got, msgUnbond, unbondSharesStr, leftBonded) } func TestMultipleMsgDeclareCandidacy(t *testing.T) { @@ -152,7 +207,7 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { //Check that the account is bonded candidates := keeper.GetCandidates(ctx, 100) - require.Equal(t, i, len(candidates)) + require.Equal(t, (i + 1), len(candidates)) val := candidates[i] balanceExpd := initBond - 10 balanceGot := accMapper.GetAccount(ctx, val.Address).GetCoins().AmountOf(params.BondDenom) @@ -174,17 +229,17 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { require.Equal(t, len(candidateAddrs)-(i+1), len(candidates), "expected %d candidates got %d", len(candidateAddrs)-(i+1), len(candidates)) - candidatePost, found := keeper.GetCandidate(ctx, candidateAddr) - require.True(t, found) - balanceExpd := initBond - balanceGot := accMapper.GetAccount(ctx, candidatePre.Address).GetCoins().AmountOf(params.BondDenom) - require.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) - require.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + _, found = keeper.GetCandidate(ctx, candidateAddr) + require.False(t, found) + + expBalance := initBond + gotBalance := accMapper.GetAccount(ctx, candidatePre.Address).GetCoins().AmountOf(params.BondDenom) + require.Equal(t, expBalance, gotBalance, "expected account to have %d, got %d", expBalance, gotBalance) } } func TestMultipleMsgDelegate(t *testing.T) { - ctx, _, keeper := createTestInput(t, false, 0) + ctx, _, keeper := createTestInput(t, false, 1000) candidateAddr, delegatorAddrs := addrs[0], addrs[1:] //first make a candidate @@ -194,7 +249,7 @@ func TestMultipleMsgDelegate(t *testing.T) { // delegate multiple parties for i, delegatorAddr := range delegatorAddrs { - msgDelegate := newTestMsgDelegate(10, delegatorAddr, candidateAddr) + msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, 10) got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -217,8 +272,8 @@ func TestMultipleMsgDelegate(t *testing.T) { } func TestVoidCandidacy(t *testing.T) { + ctx, _, keeper := createTestInput(t, false, 1000) candidateAddr, delegatorAddr := addrs[0], addrs[1] - ctx, _, keeper := createTestInput(t, false, 0) // create the candidate msgDeclareCandidacy := newTestMsgDeclareCandidacy(candidateAddr, pks[0], 10) @@ -226,21 +281,25 @@ func TestVoidCandidacy(t *testing.T) { require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") // bond a delegator - msgDelegate := newTestMsgDelegate(10, delegatorAddr, candidateAddr) + msgDelegate := newTestMsgDelegate(delegatorAddr, candidateAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) // unbond the candidates bond portion - msgUnbond := NewMsgUnbond(delegatorAddr, candidateAddr, "10") - got = handleMsgUnbond(ctx, msgUnbond, keeper) + msgUnbondCandidate := NewMsgUnbond(candidateAddr, candidateAddr, "10") + got = handleMsgUnbond(ctx, msgUnbondCandidate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") + candidate, found := keeper.GetCandidate(ctx, candidateAddr) + require.True(t, found) + require.Equal(t, Revoked, candidate.Status) - // test that this pubkey cannot yet be bonded too + // test that this address cannot yet be bonded too because is revoked got = handleMsgDelegate(ctx, msgDelegate, keeper) assert.False(t, got.IsOK(), "expected error, got %v", got) // test that the delegator can still withdraw their bonds - got = handleMsgUnbond(ctx, msgUnbond, keeper) + msgUnbondDelegator := NewMsgUnbond(delegatorAddr, candidateAddr, "10") + got = handleMsgUnbond(ctx, msgUnbondDelegator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDeclareCandidacy") // verify that the pubkey can now be reused