diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ef49d35..7ced59c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.13.1 (April 3, 2018) + +BUG FIXES + +* [x/ibc] Fix CLI and relay for IBC txs +* [x/stake] Various fixes/improvements + ## 0.13.0 (April 2, 2018) BREAKING CHANGES @@ -7,7 +14,7 @@ BREAKING CHANGES * [basecoin] Remove cool/sketchy modules -> moved to new `democoin` * [basecoin] NewBasecoinApp takes a `map[string]dbm.DB` as temporary measure to allow mounting multiple stores with their own DB until they can share one -* [staking] Renamed to `simplestake` +* [x/staking] Renamed to `simplestake` * [builder] Functions don't take `passphrase` as argument * [server] GenAppState returns generated seed and address * [basecoind] `init` command outputs JSON of everything necessary for testnet @@ -18,7 +25,7 @@ FEATURES * [types] `Coin` supports direct arithmetic operations * [basecoind] Add `show_validator` and `show_node_id` commands -* [staking] Initial merge of full staking module! +* [x/stake] Initial merge of full staking module! * [democoin] New example application to demo custom modules IMPROVEMENTS @@ -42,9 +49,9 @@ BREAKING CHANGES * [types] Replace tx.GetFeePayer with FeePayer(tx) - returns the first signer * [types] NewStdTx takes the Fee * [types] ParseAccount -> AccountDecoder; ErrTxParse -> ErrTxDecoder -* [auth] AnteHandler deducts fees -* [bank] Move some errors to `types` -* [bank] Remove sequence and signature from Input +* [x/auth] AnteHandler deducts fees +* [x/bank] Move some errors to `types` +* [x/bank] Remove sequence and signature from Input FEATURES @@ -68,8 +75,8 @@ IMPROVEMENTS * [specs] Staking BUG FIXES -* [auth] Fix setting pubkey on new account -* [auth] Require signatures to include the sequences +* [x/auth] Fix setting pubkey on new account +* [x/auth] Require signatures to include the sequences * [baseapp] Dont panic on nil handler * [basecoin] Check for empty bytes in account and tx diff --git a/version/version.go b/version/version.go index 28e4bea17..2a71771cb 100644 --- a/version/version.go +++ b/version/version.go @@ -7,9 +7,9 @@ package version const Maj = "0" const Min = "13" -const Fix = "0" +const Fix = "1" -const Version = "0.13.0" +const Version = "0.13.1" // GitCommit set by build flags var GitCommit = "" diff --git a/x/ibc/commands/README.md b/x/ibc/commands/README.md index 11c46c326..ed9652fa3 100644 --- a/x/ibc/commands/README.md +++ b/x/ibc/commands/README.md @@ -1,25 +1,157 @@ -# IBC CLI Usage +# IBC Doubble Hubble -## initialize +## Remove remaining data -```bash -basecoind init # copy the recover key -basecli keys add keyname --recover -basecoind start +```console +> rm -r ~/.chain1 +> rm -r ~/.chain2 +> rm -r ~/.basecli ``` -## transfer +## Initialize both chains -`transfer` sends coins from one chain to another(or itself). +```console +> basecoind init --home ~/.chain1 +I[04-02|14:03:33.704] Generated private validator module=main path=/home/mossid/.chain1/config/priv_validator.json +I[04-02|14:03:33.705] Generated genesis file module=main path=/home/mossid/.chain1/config/genesis.json +{ + "secret": "crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon", + "account": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC", + "validator": { + "pub_key": { + "type": "ed25519", + "data": "8C9917D5E982E221F5A1450103102B44BBFC1E8768126C606246CB37B5794F4D" + }, + "power": 10, + "name": "" + }, + "node_id": "3ac8e6242315fd62143dc3e52c161edaaa6b1a64", + "chain_id": "test-chain-ZajMfr" +} +> ADDR1=C69FEB398A29AAB1B3C4F07DE22208F35E711BCC +> ID1=test-chain-ZajMfr +> NODE1=tcp://0.0.0.0:36657 +> basecli keys add key1 --recover +Enter a passphrase for your key: +Repeat the passphrase: +Enter your recovery seed phrase: +crunch ignore trigger neither differ dance cheap brick situate floor luxury citizen husband decline arrow abandon +key1 C69FEB398A29AAB1B3C4F07DE22208F35E711BCC + + +> basecoind init --home ~/.chain2 +I[04-02|14:09:14.453] Generated private validator module=main path=/home/mossid/.chain2/config/priv_validator.json +I[04-02|14:09:14.453] Generated genesis file module=main path=/home/mossid/.chain2/config/genesis.json +{ + "secret": "age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon", + "account": "DC26002735D3AA9573707CFA6D77C12349E49868", + "validator": { + "pub_key": { + "type": "ed25519", + "data": "A94FE4B9AD763D301F4DD5A2766009812495FB7A79F1275FB8A5AF09B44FD5F3" + }, + "power": 10, + "name": "" + }, + "node_id": "ad26831330e1c72b85276d53c20f0680e6fd4cf5" + "chain_id": "test-chain-4XHTPn" +} +> ADDR2=DC26002735D3AA9573707CFA6D77C12349E49868 +> ID2=test-chain-4XHTPn +> NODE2=tcp://0.0.0.0:46657 +> basecli keys add key2 --recover +Enter a passphrase for your key: +Repeat the passphrase: +Enter your recovery seed phrase: +age guide awesome month female left oxygen soccer define high grocery work desert dinner arena abandon +key2 DC26002735D3AA9573707CFA6D77C12349E49868 + + +> basecoind start --home ~/.chain1 --address tcp://0.0.0.0:36658 --rpc.laddr tcp://0.0.0.0:36657 --p2p.laddr tcp://0.0.0.0:36656 +... + +> basecoind start --home ~/.chain2 # --address tcp://0.0.0.0:46658 --rpc.laddr tcp://0.0.0.0:46657 --p2p.laddr tcp://0.0.0.0:46656 +... +``` +## Check balance + +```console +> basecli account $ADDR1 --node $NODE1 +{ + "address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ], + "public_key": null, + "sequence": 0, + "name": "" +} + +> basecli account $ADDR2 --node $NODE2 +{ + "address": "DC26002735D3AA9573707CFA6D77C12349E49868", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ], + "public_key": null, + "sequence": 0, + "name": "" +} -```bash -basecli transfer --name keyname --to address_of_destination --amount 10mycoin --chain test-chain-AAAAAA --chain-id AAAAAA ``` -The id of the chain can be found in `$HOME/.basecoind/config/genesis.json` +## Transfer coins (addr1:chain1 -> addr2:chain2) + +```console +> basecli transfer --name key1 --to $ADDR2 --amount 10mycoin --chain $ID2 --chain-id $ID1 --node $NODE1 +Password to sign with 'key1': +Committed at block 1022. Hash: E16019DCC4AA08CA70AFCFBC96028ABCC51B6AD0 +> basecli account $ADDR1 --node $NODE1 +{ + "address": "C69FEB398A29AAB1B3C4F07DE22208F35E711BCC", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740982 + } + ], + "public_key": { + "type": "ed25519", + "data": "9828FF1780A066A0D93D840737566B697035448D6C880807322BED8919348B2B" + }, + "sequence": 1, + "name": "" +} +``` + +## Relay IBC packets + +```console +> basecli relay --name key2 --from-chain-id $ID1 --from-chain-node $NODE1 --to-chain-id $ID2 --to-chain-node $NODE2 --chain-id $ID2 +Password to sign with 'key2': +I[04-03|16:18:59.984] Detected IBC packet number=0 +I[04-03|16:19:00.869] Relayed IBC packet number=0 +> basecli account $ADDR2 --node $NODE2 +{ + "address": "DC26002735D3AA9573707CFA6D77C12349E49868", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254741002 + } + ], + "public_key": { + "type": "ed25519", + "data": "F52B4FA545F4E9BFE5D7AF1DD2236899FDEF905F9B3057C38D7C01BF1B8EB52E" + }, + "sequence": 1, + "name": "" +} -## relay - -```bash -basecli relay --name keyname --from-chain-id test-chain-AAAAAA --from-chain-node=tcp://0.0.0.0:46657 --to-chain-id test-chain-AAAAAA --to-chain-node=tcp://0.0.0.0:46657 ``` diff --git a/x/ibc/commands/ibctx.go b/x/ibc/commands/ibctx.go index e0186b717..4d8476ff1 100644 --- a/x/ibc/commands/ibctx.go +++ b/x/ibc/commands/ibctx.go @@ -77,7 +77,7 @@ func buildMsg(from sdk.Address) (sdk.Msg, error) { } to := sdk.Address(bz) - packet := ibc.NewIBCPacket(from, to, coins, client.FlagChainID, + packet := ibc.NewIBCPacket(from, to, coins, viper.GetString(client.FlagChainID), viper.GetString(flagChain)) msg := ibc.IBCTransferMsg{ diff --git a/x/ibc/commands/relay.go b/x/ibc/commands/relay.go index 9f6647ba5..917024811 100644 --- a/x/ibc/commands/relay.go +++ b/x/ibc/commands/relay.go @@ -1,12 +1,14 @@ package commands import ( - "fmt" + "os" "time" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tmlibs/log" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" @@ -30,6 +32,8 @@ type relayCommander struct { decoder sdk.AccountDecoder mainStore string ibcStore string + + logger log.Logger } func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { @@ -38,6 +42,8 @@ func IBCRelayCmd(cdc *wire.Codec) *cobra.Command { decoder: authcmd.GetAccountDecoder(cdc), ibcStore: "ibc", mainStore: "main", + + logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)), } cmd := &cobra.Command{ @@ -86,27 +92,26 @@ func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode } ingressKey := ibc.IngressSequenceKey(fromChainID) - - processedbz, err := query(toChainNode, ingressKey, c.ibcStore) - if err != nil { - panic(err) - } - - var processed int64 - if processedbz == nil { - processed = 0 - } else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil { - panic(err) - } - OUTER: for { - time.Sleep(time.Second) + time.Sleep(5 * time.Second) + + processedbz, err := query(toChainNode, ingressKey, c.ibcStore) + if err != nil { + panic(err) + } + + var processed int64 + if processedbz == nil { + processed = 0 + } else if err = c.cdc.UnmarshalBinary(processedbz, &processed); err != nil { + panic(err) + } lengthKey := ibc.EgressLengthKey(toChainID) egressLengthbz, err := query(fromChainNode, lengthKey, c.ibcStore) if err != nil { - fmt.Printf("Error querying outgoing packet list length: '%s'\n", err) + c.logger.Error("Error querying outgoing packet list length", "err", err) continue OUTER } var egressLength int64 @@ -115,25 +120,30 @@ OUTER: } else if err = c.cdc.UnmarshalBinary(egressLengthbz, &egressLength); err != nil { panic(err) } - fmt.Printf("egressLength queried: %d\n", egressLength) + if egressLength > processed { + c.logger.Info("Detected IBC packet", "number", egressLength-1) + } + + seq := c.getSequence(toChainNode) for i := processed; i < egressLength; i++ { egressbz, err := query(fromChainNode, ibc.EgressKey(toChainID, i), c.ibcStore) if err != nil { - fmt.Printf("Error querying egress packet: '%s'\n", err) + c.logger.Error("Error querying egress packet", "err", err) continue OUTER } + viper.Set(client.FlagSequence, seq) + seq++ + err = c.broadcastTx(toChainNode, c.refine(egressbz, i, passphrase)) if err != nil { - fmt.Printf("Error broadcasting ingress packet: '%s'\n", err) + c.logger.Error("Error broadcasting ingress packet", "err", err) continue OUTER } - fmt.Printf("Relayed packet: %d\n", i) + c.logger.Info("Relayed IBC packet", "number", i) } - - processed = egressLength } } @@ -148,8 +158,6 @@ func query(node string, key []byte, storeName string) (res []byte, err error) { func (c relayCommander) broadcastTx(node string, tx []byte) error { orig := viper.GetString(client.FlagNode) viper.Set(client.FlagNode, node) - seq := c.getSequence(node) + 1 - viper.Set(client.FlagSequence, seq) _, err := builder.BroadcastTx(tx) viper.Set(client.FlagNode, orig) return err @@ -160,6 +168,7 @@ func (c relayCommander) getSequence(node string) int64 { if err != nil { panic(err) } + account, err := c.decoder(res) if err != nil { panic(err) @@ -168,6 +177,10 @@ func (c relayCommander) getSequence(node string) int64 { return account.GetSequence() } +func setSequence(seq int64) { + viper.Set(client.FlagSequence, seq) +} + func (c relayCommander) refine(bz []byte, sequence int64, passphrase string) []byte { var packet ibc.IBCPacket if err := c.cdc.UnmarshalBinary(bz, &packet); err != nil { diff --git a/x/stake/handler.go b/x/stake/handler.go index 7449141aa..6e3b6ff72 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -71,17 +71,6 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { //_____________________________________________________________________ -// XXX should be send in the msg (init in CLI) -//func getSender() sdk.Address { -//signers := msg.GetSigners() -//if len(signers) != 1 { -//return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result() -//} -//sender := signers[0] -//} - -//_____________________________________________________________________ - // These functions assume everything has been authenticated, // now we just perform action and save @@ -187,8 +176,11 @@ func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidat if err != nil { return err } - newShares := k.candidateAddTokens(ctx, candidate, amount.Amount) + p := k.GetPool(ctx) + p, candidate, newShares := p.candidateAddTokens(candidate, amount.Amount) bond.Shares = bond.Shares.Add(newShares) + k.setPool(ctx, p) + k.setCandidate(ctx, candidate) k.setDelegatorBond(ctx, bond) return nil } @@ -258,7 +250,9 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } // Add the coins - returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + p := k.GetPool(ctx) + var returnAmount int64 + p, candidate, returnAmount = p.candidateRemoveShares(candidate, shares) returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) @@ -267,7 +261,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // change the share types to unbonded if they were not already if candidate.Status == Bonded { - k.bondedToUnbondedPool(ctx, candidate) + p, candidate = p.bondedToUnbondedPool(candidate) } // lastly update the status @@ -280,6 +274,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } else { k.setCandidate(ctx, candidate) } + k.setPool(ctx, p) return sdk.Result{} } @@ -293,12 +288,16 @@ func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candid } bond.Shares = bond.Shares.Sub(shares) - returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + p := k.GetPool(ctx) + var returnAmount int64 + p, 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 } + k.setPool(ctx, p) + k.setCandidate(ctx, candidate) return nil } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index b6953df5c..b435b754d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,248 +1,245 @@ package stake -//import ( -//"strconv" -//"testing" +/* +import ( + "strconv" + "testing" -//"github.com/stretchr/testify/assert" -//"github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" -//crypto "github.com/tendermint/go-crypto" + crypto "github.com/tendermint/go-crypto" -//sdk "github.com/cosmos/cosmos-sdk/types" -//) + sdk "github.com/cosmos/cosmos-sdk/types" +) -////______________________________________________________________________ +//______________________________________________________________________ -//func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { -//return MsgDeclareCandidacy{ -//Description: Description{}, -//CandidateAddr: address, -//Bond: sdk.Coin{"fermion", amt}, -//PubKey: pubKey, -//} -//} +func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { + return MsgDeclareCandidacy{ + Description: Description{}, + CandidateAddr: address, + Bond: sdk.Coin{"fermion", amt}, + PubKey: pubKey, + } +} -//func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate { -//return MsgDelegate{ -//DelegatorAddr: delegatorAddr, -//CandidateAddr: candidateAddr, -//Bond: sdk.Coin{"fermion", amt}, -//} -//} +func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidateAddr, + Bond: sdk.Coin{"fermion", amt}, + } +} -//func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { -//ctxDeliver, _, keeper := createTestInput(t, addrs[0], false, 1000) -//ctxCheck, _, keeper := createTestInput(t, addrs[0], true, 1000) +func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 1000) -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + assert.True(t, got.IsOK(), "%v", got) -//// one sender can bond to two different addresses -//msgDeclareCandidacy.Address = addrs[1] -//err := checker.declareCandidacy(msgDeclareCandidacy) -//assert.Nil(t, err, "didn't expected error on checkTx") + // one sender cannot bond twice + msgDeclareCandidacy.PubKey = pks[1] + got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + assert.False(t, got.IsOK(), "%v", got) +} -//// two addrs cant bond to the same pubkey -//checker.sender = addrs[1] -//msgDeclareCandidacy.Address = addrs[0] -//err = checker.declareCandidacy(msgDeclareCandidacy) -//assert.NotNil(t, err, "expected error on checkTx") -//} +func TestIncrementsMsgDelegate(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 1000) -//func TestIncrementsMsgDelegate(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + // first declare candidacy + bondAmount := int64(10) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got) + expectedBond := bondAmount // 1 since we send 1 at the start of loop, -//// first declare candidacy -//bondAmount := int64(10) -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got) -//expectedBond := bondAmount // 1 since we send 1 at the start of loop, + // just send the same msgbond multiple times + msgDelegate := newTestMsgDelegate(bondAmount, addrs[0]) + for i := 0; i < 5; i++ { + got := deliverer.delegate(msgDelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// just send the same msgbond multiple times -//msgDelegate := newTestMsgDelegate(bondAmount, addrs[0]) -//for i := 0; i < 5; i++ { -//got := deliverer.delegate(msgDelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the accounts and the bond account have the appropriate values + candidates := mapper.GetCandidates() + expectedBond += bondAmount + //expectedSender := initSender - expectedBond + gotBonded := candidates[0].Liabilities.Evaluate() + //gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper + assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) + //assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix + } +} -////Check that the accounts and the bond account have the appropriate values -//candidates := mapper.GetCandidates() -//expectedBond += bondAmount -////expectedSender := initSender - expectedBond -//gotBonded := candidates[0].Liabilities.Evaluate() -////gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper -//assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) -////assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix -//} -//} +func TestIncrementsMsgUnbond(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 0) -//func TestIncrementsMsgUnbond(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 0) + // set initial bond + initBond := int64(1000) + //accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper + got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) + assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got) -//// set initial bond -//initBond := int64(1000) -////accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper -//got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) -//assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got) + // just send the same msgunbond multiple times + // XXX use decimals here + unbondShares, unbondSharesStr := int64(10), "10" + msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) + nUnbonds := 5 + for i := 0; i < nUnbonds; i++ { + got := deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// just send the same msgunbond multiple times -//// XXX use decimals here -//unbondShares, unbondSharesStr := int64(10), "10" -//msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) -//nUnbonds := 5 -//for i := 0; i < nUnbonds; i++ { -//got := deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the accounts and the bond account have the appropriate values + candidates := mapper.GetCandidates() + expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop + //expectedSender := initSender + (initBond - expectedBond) + gotBonded := candidates[0].Liabilities.Evaluate() + //gotSender := accStore[string(deliverer.sender)] // XXX use storemapper -////Check that the accounts and the bond account have the appropriate values -//candidates := mapper.GetCandidates() -//expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop -////expectedSender := initSender + (initBond - expectedBond) -//gotBonded := candidates[0].Liabilities.Evaluate() -////gotSender := accStore[string(deliverer.sender)] // XXX use storemapper + assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) + //assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix + } -//assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) -////assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix -//} + // these are more than we have bonded now + errorCases := []int64{ + //1<<64 - 1, // more than int64 + //1<<63 + 1, // more than int64 + 1<<63 - 1, + 1 << 31, + initBond, + } + for _, c := range errorCases { + unbondShares := strconv.Itoa(int(c)) + msgUndelegate := NewMsgUnbond(addrs[0], unbondShares) + got = deliverer.unbond(msgUndelegate) + assert.Error(t, got, "expected unbond msg to fail") + } -//// these are more than we have bonded now -//errorCases := []int64{ -////1<<64 - 1, // more than int64 -////1<<63 + 1, // more than int64 -//1<<63 - 1, -//1 << 31, -//initBond, -//} -//for _, c := range errorCases { -//unbondShares := strconv.Itoa(int(c)) -//msgUndelegate := NewMsgUnbond(addrs[0], unbondShares) -//got = deliverer.unbond(msgUndelegate) -//assert.Error(t, got, "expected unbond msg to fail") -//} + leftBonded := initBond - unbondShares*int64(nUnbonds) -//leftBonded := initBond - unbondShares*int64(nUnbonds) + // should be unable to unbond one more than we have + msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) + got = deliverer.unbond(msgUndelegate) + assert.Error(t, got, "expected unbond msg to fail") -//// should be unable to unbond one more than we have -//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) -//got = deliverer.unbond(msgUndelegate) -//assert.Error(t, got, "expected unbond msg to fail") + // should be able to unbond just what we have + msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) + got = deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected unbond msg to pass") +} -//// should be able to unbond just what we have -//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) -//got = deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected unbond msg to pass") -//} +func TestMultipleMsgDeclareCandidacy(t *testing.T) { + initSender := int64(1000) + //ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender) + ctx, mapper, keeper := createTestInput(t, addrs[0], false, initSender) + addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} -//func TestMultipleMsgDeclareCandidacy(t *testing.T) { -//initSender := int64(1000) -//ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender) -//addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + // bond them all + for i, addr := range addrs { + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) + deliverer.sender = addr + got := deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// bond them all -//for i, addr := range addrs { -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) -//deliverer.sender = addr -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is bonded + candidates := mapper.GetCandidates() + require.Equal(t, i, len(candidates)) + val := candidates[i] + balanceExpd := initSender - 10 + balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins() + assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) + assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) + assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } -////Check that the account is bonded -//candidates := mapper.GetCandidates() -//require.Equal(t, i, len(candidates)) -//val := candidates[i] -//balanceExpd := initSender - 10 -//balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins() -//assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) -//assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) -//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) -//} + // unbond them all + for i, addr := range addrs { + candidatePre := mapper.GetCandidate(addrs[i]) + msgUndelegate := NewMsgUnbond(addrs[i], "10") + deliverer.sender = addr + got := deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// unbond them all -//for i, addr := range addrs { -//candidatePre := mapper.GetCandidate(addrs[i]) -//msgUndelegate := NewMsgUnbond(addrs[i], "10") -//deliverer.sender = addr -//got := deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is unbonded + candidates := mapper.GetCandidates() + assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) -////Check that the account is unbonded -//candidates := mapper.GetCandidates() -//assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) + candidatePost := mapper.GetCandidate(addrs[i]) + balanceExpd := initSender + balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins() + assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) + assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } +} -//candidatePost := mapper.GetCandidate(addrs[i]) -//balanceExpd := initSender -//balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins() -//assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) -//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) -//} -//} +func TestMultipleMsgDelegate(t *testing.T) { + sender, delegators := addrs[0], addrs[1:] + _, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + ctx, _, keeper := createTestInput(t, addrs[0], false, 0) -//func TestMultipleMsgDelegate(t *testing.T) { -//sender, delegators := addrs[0], addrs[1:] -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + //first make a candidate + msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + require.NoError(t, got, "expected msg to be ok, got %v", got) -////first make a candidate -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//require.NoError(t, got, "expected msg to be ok, got %v", got) + // delegate multiple parties + for i, delegator := range delegators { + msgDelegate := newTestMsgDelegate(10, sender) + deliverer.sender = delegator + got := deliverer.delegate(msgDelegate) + require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// delegate multiple parties -//for i, delegator := range delegators { -//msgDelegate := newTestMsgDelegate(10, sender) -//deliverer.sender = delegator -//got := deliverer.delegate(msgDelegate) -//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is bonded + bond := mapper.getDelegatorBond(delegator, sender) + assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) + } -////Check that the account is bonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) -//} + // unbond them all + for i, delegator := range delegators { + msgUndelegate := NewMsgUnbond(sender, "10") + deliverer.sender = delegator + got := deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// unbond them all -//for i, delegator := range delegators { -//msgUndelegate := NewMsgUnbond(sender, "10") -//deliverer.sender = delegator -//got := deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is unbonded + bond := mapper.getDelegatorBond(delegator, sender) + assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) + } +} -////Check that the account is unbonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) -//} -//} +func TestVoidCandidacy(t *testing.T) { + sender, delegator := addrs[0], addrs[1] + _, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) -//func TestVoidCandidacy(t *testing.T) { -//sender, delegator := addrs[0], addrs[1] -//_, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) + // create the candidate + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// create the candidate -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + // bond a delegator + msgDelegate := newTestMsgDelegate(10, addrs[0]) + deliverer.sender = delegator + got = deliverer.delegate(msgDelegate) + require.NoError(t, got, "expected ok, got %v", got) -//// bond a delegator -//msgDelegate := newTestMsgDelegate(10, addrs[0]) -//deliverer.sender = delegator -//got = deliverer.delegate(msgDelegate) -//require.NoError(t, got, "expected ok, got %v", got) + // unbond the candidates bond portion + msgUndelegate := NewMsgUnbond(addrs[0], "10") + deliverer.sender = sender + got = deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// unbond the candidates bond portion -//msgUndelegate := NewMsgUnbond(addrs[0], "10") -//deliverer.sender = sender -//got = deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + // test that this pubkey cannot yet be bonded too + deliverer.sender = delegator + got = deliverer.delegate(msgDelegate) + assert.Error(t, got, "expected error, got %v", got) -//// test that this pubkey cannot yet be bonded too -//deliverer.sender = delegator -//got = deliverer.delegate(msgDelegate) -//assert.Error(t, got, "expected error, got %v", got) + // test that the delegator can still withdraw their bonds + got = deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// test that the delegator can still withdraw their bonds -//got = deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - -//// verify that the pubkey can now be reused -//got = deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected ok, got %v", got) -//} + // verify that the pubkey can now be reused + got = deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected ok, got %v", got) +} +*/ diff --git a/x/stake/keeper.go b/x/stake/keeper.go index af2015fe8..95eb85e76 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -1,6 +1,8 @@ package stake import ( + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank" @@ -87,6 +89,11 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { panic(err) } + // if the voting power is the same no need to update any of the other indexes + if oldFound && oldCandidate.Assets.Equal(candidate.Assets) { + return + } + // update the list ordered by voting power if oldFound { store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc)) @@ -94,11 +101,19 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store.Set(GetValidatorKey(address, validator.VotingPower, k.cdc), bz) // add to the validators to update list if is already a validator - if store.Get(GetRecentValidatorKey(address)) == nil { - return - } - store.Set(GetAccUpdateValidatorKey(validator.Address), bz) + // or is a new validator + setAcc := false + if store.Get(GetRecentValidatorKey(address)) != nil { + setAcc = true + // want to check in the else statement because inefficient + } else if k.isNewValidator(ctx, store, address) { + setAcc = true + } + if setAcc { + store.Set(GetAccUpdateValidatorKey(validator.Address), bz) + } + return } func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { @@ -112,6 +127,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { // delete the old candidate record store := ctx.KVStore(k.storeKey) store.Delete(GetCandidateKey(address)) + store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc)) // delete from recent and power weighted validator groups if the validator // exists and add validator with zero power to the validator updates @@ -124,7 +140,6 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { } store.Set(GetAccUpdateValidatorKey(address), bz) store.Delete(GetRecentValidatorKey(address)) - store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc)) } //___________________________________________________________________________ @@ -136,12 +151,18 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) - // clear the recent validators store - k.deleteSubSpace(store, RecentValidatorsKey) + // clear the recent validators store, add to the ToKickOut Temp store + iterator := store.Iterator(subspace(RecentValidatorsKey)) + for ; iterator.Valid(); iterator.Next() { + addr := AddrFromKey(iterator.Key()) + store.Set(GetToKickOutValidatorKey(addr), []byte{}) + store.Delete(iterator.Key()) + } + iterator.Close() // add the actual validator power sorted store maxVal := k.GetParams(ctx).MaxValidators - iterator := store.ReverseIterator(subspace(ValidatorsKey)) //smallest to largest + iterator = store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest validators = make([]Validator, maxVal) i := 0 for ; ; i++ { @@ -157,15 +178,58 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { } validators[i] = val + // remove from ToKickOut group + store.Delete(GetToKickOutValidatorKey(val.Address)) + // also add to the recent validators group - store.Set(GetRecentValidatorKey(val.Address), bz) + store.Set(GetRecentValidatorKey(val.Address), bz) // XXX should store nothing iterator.Next() } + // add any kicked out validators to the acc change + iterator = store.Iterator(subspace(ToKickOutValidatorsKey)) + for ; iterator.Valid(); iterator.Next() { + addr := AddrFromKey(iterator.Key()) + bz, err := k.cdc.MarshalBinary(Validator{addr, sdk.ZeroRat}) + if err != nil { + panic(err) + } + store.Set(GetAccUpdateValidatorKey(addr), bz) + store.Delete(iterator.Key()) + } + iterator.Close() + return validators[:i] // trim } +// TODO this is madly inefficient because need to call every time we set a candidate +// Should use something better than an iterator maybe? +// Used to determine if something has just been added to the actual validator set +func (k Keeper) isNewValidator(ctx sdk.Context, store sdk.KVStore, address sdk.Address) bool { + // add the actual validator power sorted store + maxVal := k.GetParams(ctx).MaxValidators + iterator := store.ReverseIterator(subspace(ValidatorsKey)) // largest to smallest + for i := 0; ; i++ { + if !iterator.Valid() || i > int(maxVal-1) { + iterator.Close() + break + } + bz := iterator.Value() + var val Validator + err := k.cdc.UnmarshalBinary(bz, &val) + if err != nil { + panic(err) + } + if bytes.Equal(val.Address, address) { + return true + } + iterator.Next() + } + + return false +} + // Is the address provided a part of the most recently saved validator group? func (k Keeper) IsRecentValidator(ctx sdk.Context, address sdk.Address) bool { store := ctx.KVStore(k.storeKey) @@ -298,3 +362,33 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) { store.Set(ParamKey, b) k.params = Params{} // clear the cache } + +//_______________________________________________________________________ + +// load/save the pool +func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) { + // check if cached before anything + if k.gs != (Pool{}) { + return k.gs + } + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + return initialPool() + } + err := k.cdc.UnmarshalBinary(b, &gs) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} + +func (k Keeper) setPool(ctx sdk.Context, p Pool) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalBinary(p) + if err != nil { + panic(err) + } + store.Set(PoolKey, b) + k.gs = Pool{} // clear the cache +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 051994456..45e0570bc 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -15,9 +15,11 @@ var ( CandidatesKey = []byte{0x02} // prefix for each key to a candidate ValidatorsKey = []byte{0x03} // prefix for each key to a validator AccUpdateValidatorsKey = []byte{0x04} // prefix for each key to a validator which is being updated - RecentValidatorsKey = []byte{0x04} // prefix for each key to the last updated validator group + RecentValidatorsKey = []byte{0x05} // prefix for each key to the last updated validator group - DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond + ToKickOutValidatorsKey = []byte{0x06} // prefix for each key to the last updated validator group + + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -43,6 +45,16 @@ func GetRecentValidatorKey(addr sdk.Address) []byte { return append(RecentValidatorsKey, addr.Bytes()...) } +// reverse operation of GetRecentValidatorKey +func AddrFromKey(key []byte) sdk.Address { + return key[1:] +} + +// get the key for the accumulated update validators +func GetToKickOutValidatorKey(addr sdk.Address) []byte { + return append(ToKickOutValidatorsKey, addr.Bytes()...) +} + // get the key for delegator bond with candidate func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 6e7478957..aa36cd585 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -5,39 +5,22 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( - addrDel1 = addrs[0] - addrDel2 = addrs[1] - addrVal1 = addrs[2] - addrVal2 = addrs[3] - addrVal3 = addrs[4] - pk1 = crypto.GenPrivKeyEd25519().PubKey() - pk2 = crypto.GenPrivKeyEd25519().PubKey() - pk3 = crypto.GenPrivKeyEd25519().PubKey() - - candidate1 = Candidate{ - Address: addrVal1, - PubKey: pk1, - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), + addrDels = []sdk.Address{ + addrs[0], + addrs[1], } - candidate2 = Candidate{ - Address: addrVal2, - PubKey: pk2, - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), - } - candidate3 = Candidate{ - Address: addrVal3, - PubKey: pk3, - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), + addrVals = []sdk.Address{ + addrs[2], + addrs[3], + addrs[4], + addrs[5], + addrs[6], } ) @@ -45,6 +28,18 @@ var ( func TestCandidate(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) + //construct the candidates + var candidates [3]Candidate + amts := []int64{9, 8, 7} + for i, amt := range amts { + candidates[i] = Candidate{ + Address: addrVals[i], + PubKey: pks[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + } + candidatesEqual := func(c1, c2 Candidate) bool { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && @@ -55,47 +50,47 @@ func TestCandidate(t *testing.T) { } // check the empty keeper first - _, found := keeper.GetCandidate(ctx, addrVal1) + _, found := keeper.GetCandidate(ctx, addrVals[0]) assert.False(t, found) resCands := keeper.GetCandidates(ctx, 100) assert.Zero(t, len(resCands)) // set and retrieve a record - keeper.setCandidate(ctx, candidate1) - resCand, found := keeper.GetCandidate(ctx, addrVal1) + keeper.setCandidate(ctx, candidates[0]) + resCand, found := keeper.GetCandidate(ctx, addrVals[0]) require.True(t, found) - assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1) + assert.True(t, candidatesEqual(candidates[0], resCand), "%v \n %v", resCand, candidates[0]) // modify a records, save, and retrieve - candidate1.Liabilities = sdk.NewRat(99) - keeper.setCandidate(ctx, candidate1) - resCand, found = keeper.GetCandidate(ctx, addrVal1) + candidates[0].Liabilities = sdk.NewRat(99) + keeper.setCandidate(ctx, candidates[0]) + resCand, found = keeper.GetCandidate(ctx, addrVals[0]) require.True(t, found) - assert.True(t, candidatesEqual(candidate1, resCand)) + assert.True(t, candidatesEqual(candidates[0], resCand)) // also test that the address has been added to address list resCands = keeper.GetCandidates(ctx, 100) require.Equal(t, 1, len(resCands)) - assert.Equal(t, addrVal1, resCands[0].Address) + assert.Equal(t, addrVals[0], resCands[0].Address) // add other candidates - keeper.setCandidate(ctx, candidate2) - keeper.setCandidate(ctx, candidate3) - resCand, found = keeper.GetCandidate(ctx, addrVal2) + keeper.setCandidate(ctx, candidates[1]) + keeper.setCandidate(ctx, candidates[2]) + resCand, found = keeper.GetCandidate(ctx, addrVals[1]) require.True(t, found) - assert.True(t, candidatesEqual(candidate2, resCand), "%v \n %v", resCand, candidate2) - resCand, found = keeper.GetCandidate(ctx, addrVal3) + assert.True(t, candidatesEqual(candidates[1], resCand), "%v \n %v", resCand, candidates[1]) + resCand, found = keeper.GetCandidate(ctx, addrVals[2]) require.True(t, found) - assert.True(t, candidatesEqual(candidate3, resCand), "%v \n %v", resCand, candidate3) + assert.True(t, candidatesEqual(candidates[2], resCand), "%v \n %v", resCand, candidates[2]) resCands = keeper.GetCandidates(ctx, 100) require.Equal(t, 3, len(resCands)) - assert.True(t, candidatesEqual(candidate1, resCands[0]), "%v \n %v", resCands[0], candidate1) - assert.True(t, candidatesEqual(candidate2, resCands[1]), "%v \n %v", resCands[1], candidate2) - assert.True(t, candidatesEqual(candidate3, resCands[2]), "%v \n %v", resCands[2], candidate3) + assert.True(t, candidatesEqual(candidates[0], resCands[0]), "%v \n %v", resCands[0], candidates[0]) + assert.True(t, candidatesEqual(candidates[1], resCands[1]), "%v \n %v", resCands[1], candidates[1]) + assert.True(t, candidatesEqual(candidates[2], resCands[2]), "%v \n %v", resCands[2], candidates[2]) // remove a record - keeper.removeCandidate(ctx, candidate2.Address) - _, found = keeper.GetCandidate(ctx, addrVal2) + keeper.removeCandidate(ctx, candidates[1].Address) + _, found = keeper.GetCandidate(ctx, addrVals[1]) assert.False(t, found) } @@ -103,12 +98,24 @@ func TestCandidate(t *testing.T) { func TestBond(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) - // first add a candidate1 to delegate too - keeper.setCandidate(ctx, candidate1) + //construct the candidates + amts := []int64{9, 8, 7} + var candidates [3]Candidate + for i, amt := range amts { + candidates[i] = Candidate{ + Address: addrVals[i], + PubKey: pks[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + } + + // first add a candidates[0] to delegate too + keeper.setCandidate(ctx, candidates[0]) bond1to1 := DelegatorBond{ - DelegatorAddr: addrDel1, - CandidateAddr: addrVal1, + DelegatorAddr: addrDels[0], + CandidateAddr: addrVals[0], Shares: sdk.NewRat(9), } @@ -119,30 +126,30 @@ func TestBond(t *testing.T) { } // check the empty keeper first - _, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) + _, found := keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0]) assert.False(t, found) // set and retrieve a record keeper.setDelegatorBond(ctx, bond1to1) - resBond, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) + resBond, found := keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bondsEqual(bond1to1, resBond)) // modify a records, save, and retrieve bond1to1.Shares = sdk.NewRat(99) keeper.setDelegatorBond(ctx, bond1to1) - resBond, found = keeper.getDelegatorBond(ctx, addrDel1, addrVal1) + resBond, found = keeper.getDelegatorBond(ctx, addrDels[0], addrVals[0]) assert.True(t, found) assert.True(t, bondsEqual(bond1to1, resBond)) // add some more records - keeper.setCandidate(ctx, candidate2) - keeper.setCandidate(ctx, candidate3) - bond1to2 := DelegatorBond{addrDel1, addrVal2, sdk.NewRat(9)} - bond1to3 := DelegatorBond{addrDel1, addrVal3, sdk.NewRat(9)} - bond2to1 := DelegatorBond{addrDel2, addrVal1, sdk.NewRat(9)} - bond2to2 := DelegatorBond{addrDel2, addrVal2, sdk.NewRat(9)} - bond2to3 := DelegatorBond{addrDel2, addrVal3, sdk.NewRat(9)} + keeper.setCandidate(ctx, candidates[1]) + keeper.setCandidate(ctx, candidates[2]) + bond1to2 := DelegatorBond{addrDels[0], addrVals[1], sdk.NewRat(9)} + bond1to3 := DelegatorBond{addrDels[0], addrVals[2], sdk.NewRat(9)} + bond2to1 := DelegatorBond{addrDels[1], addrVals[0], sdk.NewRat(9)} + bond2to2 := DelegatorBond{addrDels[1], addrVals[1], sdk.NewRat(9)} + bond2to3 := DelegatorBond{addrDels[1], addrVals[2], sdk.NewRat(9)} keeper.setDelegatorBond(ctx, bond1to2) keeper.setDelegatorBond(ctx, bond1to3) keeper.setDelegatorBond(ctx, bond2to1) @@ -150,16 +157,16 @@ func TestBond(t *testing.T) { keeper.setDelegatorBond(ctx, bond2to3) // test all bond retrieve capabilities - resBonds := keeper.getDelegatorBonds(ctx, addrDel1, 5) + resBonds := keeper.getDelegatorBonds(ctx, addrDels[0], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bondsEqual(bond1to1, resBonds[0])) assert.True(t, bondsEqual(bond1to2, resBonds[1])) assert.True(t, bondsEqual(bond1to3, resBonds[2])) - resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 3) + resBonds = keeper.getDelegatorBonds(ctx, addrDels[0], 3) require.Equal(t, 3, len(resBonds)) - resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 2) + resBonds = keeper.getDelegatorBonds(ctx, addrDels[0], 2) require.Equal(t, 2, len(resBonds)) - resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5) require.Equal(t, 3, len(resBonds)) assert.True(t, bondsEqual(bond2to1, resBonds[0])) assert.True(t, bondsEqual(bond2to2, resBonds[1])) @@ -167,9 +174,9 @@ func TestBond(t *testing.T) { // delete a record keeper.removeDelegatorBond(ctx, bond2to3) - _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal3) + _, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[2]) assert.False(t, found) - resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5) require.Equal(t, 2, len(resBonds)) assert.True(t, bondsEqual(bond2to1, resBonds[0])) assert.True(t, bondsEqual(bond2to2, resBonds[1])) @@ -177,11 +184,11 @@ func TestBond(t *testing.T) { // delete all the records from delegator 2 keeper.removeDelegatorBond(ctx, bond2to1) keeper.removeDelegatorBond(ctx, bond2to2) - _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal1) + _, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[0]) assert.False(t, found) - _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal2) + _, found = keeper.getDelegatorBond(ctx, addrDels[1], addrVals[1]) assert.False(t, found) - resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + resBonds = keeper.getDelegatorBonds(ctx, addrDels[1], 5) require.Equal(t, 0, len(resBonds)) } @@ -193,14 +200,14 @@ func TestGetValidators(t *testing.T) { // initialize some candidates into the state amts := []int64{0, 100, 1, 400, 200} n := len(amts) - candidates := make([]Candidate, n) - for i := 0; i < n; i++ { + var candidates [5]Candidate + for i, amt := range amts { c := Candidate{ Status: Unbonded, PubKey: pks[i], Address: addrs[i], - Assets: sdk.NewRat(amts[i]), - Liabilities: sdk.NewRat(amts[i]), + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), } keeper.setCandidate(ctx, c) candidates[i] = c @@ -259,36 +266,282 @@ func TestGetValidators(t *testing.T) { assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) } -// TODO -// test the mechanism which keeps track of a validator set change -func TestGetAccUpdateValidators(t *testing.T) { - //TODO - // test from nothing to something - // test from something to nothing - // test identical - // test single value change - // test multiple value change - // test validator added at the beginning - // test validator added in the middle - // test validator added at the end - // test multiple validators removed -} - // clear the tracked changes to the validator set func TestClearAccUpdateValidators(t *testing.T) { - //TODO + ctx, _, keeper := createTestInput(t, nil, false, 0) + + amts := []int64{100, 400, 200} + candidates := make([]Candidate, len(amts)) + for i, amt := range amts { + c := Candidate{ + Status: Unbonded, + PubKey: pks[i], + Address: addrs[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + candidates[i] = c + keeper.setCandidate(ctx, c) + } + + acc := keeper.getAccUpdateValidators(ctx) + assert.Equal(t, len(amts), len(acc)) + keeper.clearAccUpdateValidators(ctx) + acc = keeper.getAccUpdateValidators(ctx) + assert.Equal(t, 0, len(acc)) +} + +// test the mechanism which keeps track of a validator set change +func TestGetAccUpdateValidators(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := defaultParams() + params.MaxValidators = 4 + keeper.setParams(ctx, params) + + // TODO eliminate use of candidatesIn here + // tests could be clearer if they just + // created the candidate at time of use + // and were labelled by power in the comments + // outlining in each test + amts := []int64{10, 11, 12, 13, 1} + var candidatesIn [5]Candidate + for i, amt := range amts { + candidatesIn[i] = Candidate{ + Address: addrs[i], + PubKey: pks[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + } + + // test from nothing to something + // candidate set: {} -> {c1, c3} + // validator set: {} -> {c1, c3} + // accUpdate set: {} -> {c1, c3} + assert.Equal(t, 0, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.GetValidators(ctx))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.setCandidate(ctx, candidatesIn[1]) + keeper.setCandidate(ctx, candidatesIn[3]) + + vals := keeper.GetValidators(ctx) // to init recent validator set + require.Equal(t, 2, len(vals)) + acc := keeper.getAccUpdateValidators(ctx) + require.Equal(t, 2, len(acc)) + candidates := keeper.GetCandidates(ctx, 5) + require.Equal(t, 2, len(candidates)) + assert.Equal(t, candidates[0].validator(), acc[0]) + assert.Equal(t, candidates[1].validator(), acc[1]) + assert.Equal(t, candidates[0].validator(), vals[1]) + assert.Equal(t, candidates[1].validator(), vals[0]) + + // test identical, + // candidate set: {c1, c3} -> {c1, c3} + // accUpdate set: {} -> {} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.setCandidate(ctx, candidates[0]) + keeper.setCandidate(ctx, candidates[1]) + + require.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + // test single value change + // candidate set: {c1, c3} -> {c1', c3} + // accUpdate set: {} -> {c1'} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + candidates[0].Assets = sdk.NewRat(600) + keeper.setCandidate(ctx, candidates[0]) + + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 2, len(candidates)) + assert.True(t, candidates[0].Assets.Equal(sdk.NewRat(600))) + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 1, len(acc)) + assert.Equal(t, candidates[0].validator(), acc[0]) + + // test multiple value change + // candidate set: {c1, c3} -> {c1', c3'} + // accUpdate set: {c1, c3} -> {c1', c3'} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + candidates[0].Assets = sdk.NewRat(200) + candidates[1].Assets = sdk.NewRat(100) + keeper.setCandidate(ctx, candidates[0]) + keeper.setCandidate(ctx, candidates[1]) + + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 2, len(acc)) + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 2, len(candidates)) + require.Equal(t, candidates[0].validator(), acc[0]) + require.Equal(t, candidates[1].validator(), acc[1]) + + // test validtor added at the beginning + // candidate set: {c1, c3} -> {c0, c1, c3} + // accUpdate set: {} -> {c0} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 2, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.setCandidate(ctx, candidatesIn[0]) + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 1, len(acc)) + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 3, len(candidates)) + assert.Equal(t, candidates[0].validator(), acc[0]) + + // test validator added at the middle + // candidate set: {c0, c1, c3} -> {c0, c1, c2, c3] + // accUpdate set: {} -> {c2} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 3, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.setCandidate(ctx, candidatesIn[2]) + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 1, len(acc)) + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 4, len(candidates)) + assert.Equal(t, candidates[2].validator(), acc[0]) + + // test candidate added at the end but not inserted in the valset + // candidate set: {c0, c1, c2, c3} -> {c0, c1, c2, c3, c4} + // validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3} + // accUpdate set: {} -> {} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 4, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.setCandidate(ctx, candidatesIn[4]) + + assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4 + + // test candidate change its power but still not in the valset + // candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4} + // validator set: {c0, c1, c2, c3} -> {c0, c1, c2, c3} + // accUpdate set: {} -> {} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + candidatesIn[4].Assets = sdk.NewRat(1) + keeper.setCandidate(ctx, candidatesIn[4]) + + assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + require.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) // max validator number is 4 + + // test candidate change its power and become a validator (pushing out an existing) + // candidate set: {c0, c1, c2, c3, c4} -> {c0, c1, c2, c3, c4} + // validator set: {c0, c1, c2, c3} -> {c1, c2, c3, c4} + // accUpdate set: {} -> {c0, c4} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + candidatesIn[4].Assets = sdk.NewRat(1000) + keeper.setCandidate(ctx, candidatesIn[4]) + + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 5, len(candidates)) + vals = keeper.GetValidators(ctx) + require.Equal(t, 4, len(vals)) + assert.Equal(t, candidatesIn[1].Address, vals[1].Address) + assert.Equal(t, candidatesIn[2].Address, vals[3].Address) + assert.Equal(t, candidatesIn[3].Address, vals[2].Address) + assert.Equal(t, candidatesIn[4].Address, vals[0].Address) + + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 2, len(acc), "%v", acc) + + assert.Equal(t, candidatesIn[0].Address, acc[0].Address) + assert.Equal(t, int64(0), acc[0].VotingPower.Evaluate()) + assert.Equal(t, vals[0], acc[1]) + + // test from something to nothing + // candidate set: {c0, c1, c2, c3, c4} -> {} + // validator set: {c1, c2, c3, c4} -> {} + // accUpdate set: {} -> {c1, c2, c3, c4} + keeper.clearAccUpdateValidators(ctx) + assert.Equal(t, 5, len(keeper.GetCandidates(ctx, 5))) + assert.Equal(t, 4, len(keeper.GetValidators(ctx))) + assert.Equal(t, 0, len(keeper.getAccUpdateValidators(ctx))) + + keeper.removeCandidate(ctx, candidatesIn[0].Address) + keeper.removeCandidate(ctx, candidatesIn[1].Address) + keeper.removeCandidate(ctx, candidatesIn[2].Address) + keeper.removeCandidate(ctx, candidatesIn[3].Address) + keeper.removeCandidate(ctx, candidatesIn[4].Address) + + vals = keeper.GetValidators(ctx) + assert.Equal(t, 0, len(vals), "%v", vals) + candidates = keeper.GetCandidates(ctx, 5) + require.Equal(t, 0, len(candidates)) + acc = keeper.getAccUpdateValidators(ctx) + require.Equal(t, 4, len(acc)) + assert.Equal(t, candidatesIn[1].Address, acc[0].Address) + assert.Equal(t, candidatesIn[2].Address, acc[1].Address) + assert.Equal(t, candidatesIn[3].Address, acc[2].Address) + assert.Equal(t, candidatesIn[4].Address, acc[3].Address) + assert.Equal(t, int64(0), acc[0].VotingPower.Evaluate()) + assert.Equal(t, int64(0), acc[1].VotingPower.Evaluate()) + assert.Equal(t, int64(0), acc[2].VotingPower.Evaluate()) + assert.Equal(t, int64(0), acc[3].VotingPower.Evaluate()) } // test if is a validator from the last update func TestIsRecentValidator(t *testing.T) { - //TODO + ctx, _, keeper := createTestInput(t, nil, false, 0) + + amts := []int64{9, 8, 7, 10, 6} + var candidatesIn [5]Candidate + for i, amt := range amts { + candidatesIn[i] = Candidate{ + Address: addrVals[i], + PubKey: pks[i], + Assets: sdk.NewRat(amt), + Liabilities: sdk.NewRat(amt), + } + } // test that an empty validator set doesn't have any validators + validators := keeper.GetValidators(ctx) + assert.Equal(t, 0, len(validators)) + // get the validators for the first time + keeper.setCandidate(ctx, candidatesIn[0]) + keeper.setCandidate(ctx, candidatesIn[1]) + validators = keeper.GetValidators(ctx) + require.Equal(t, 2, len(validators)) + assert.Equal(t, candidatesIn[0].validator(), validators[0]) + assert.Equal(t, candidatesIn[1].validator(), validators[1]) + // test a basic retrieve of something that should be a recent validator + assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address)) + assert.True(t, keeper.IsRecentValidator(ctx, candidatesIn[1].Address)) + // test a basic retrieve of something that should not be a recent validator + assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[2].Address)) + // remove that validator, but don't retrieve the recent validator group + keeper.removeCandidate(ctx, candidatesIn[0].Address) + // test that removed validator is not considered a recent validator + assert.False(t, keeper.IsRecentValidator(ctx, candidatesIn[0].Address)) } func TestParams(t *testing.T) { @@ -305,3 +558,18 @@ func TestParams(t *testing.T) { resParams = keeper.GetParams(ctx) assert.Equal(t, expParams, resParams) } + +func TestPool(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + expPool := initialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + assert.Equal(t, expPool, resPool) + + //modify a params, save, and retrieve + expPool.TotalSupply = 777 + keeper.setPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + assert.Equal(t, expPool, resPool) +} diff --git a/x/stake/pool.go b/x/stake/pool.go index 4c185580e..df6407bd8 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -4,132 +4,115 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// load/save the global staking state -func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) { - // check if cached before anything - if k.gs != (Pool{}) { - return k.gs +// get the bond ratio of the global state +func (p Pool) bondedRatio() sdk.Rat { + if p.TotalSupply > 0 { + return sdk.NewRat(p.BondedPool, p.TotalSupply) } - store := ctx.KVStore(k.storeKey) - b := store.Get(PoolKey) - if b == nil { - return initialPool() - } - err := k.cdc.UnmarshalBinary(b, &gs) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return + return sdk.ZeroRat } -func (k Keeper) setPool(ctx sdk.Context, p Pool) { - store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(p) - if err != nil { - panic(err) +// get the exchange rate of bonded token per issued share +func (p Pool) bondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { + return sdk.OneRat } - store.Set(PoolKey, b) - k.gs = Pool{} // clear the cache + return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) } -//_______________________________________________________________________ - -//TODO make these next two functions more efficient should be reading and writting to state ye know +// get the exchange rate of unbonded tokens held in candidates per issued share +func (p Pool) unbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { + return sdk.OneRat + } + return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) +} // move a candidates asset pool from bonded to unbonded pool -func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) { +func (p Pool) bondedToUnbondedPool(candidate Candidate) (Pool, Candidate) { // replace bonded shares with unbonded shares - tokens := k.removeSharesBonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensUnbonded(ctx, tokens) + p, tokens := p.removeSharesBonded(candidate.Assets) + p, candidate.Assets = p.addTokensUnbonded(tokens) candidate.Status = Unbonded - k.setCandidate(ctx, candidate) + return p, candidate } // move a candidates asset pool from unbonded to bonded pool -func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { +func (p Pool) unbondedToBondedPool(candidate Candidate) (Pool, Candidate) { // replace unbonded shares with bonded shares - tokens := k.removeSharesUnbonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensBonded(ctx, tokens) + p, tokens := p.removeSharesUnbonded(candidate.Assets) + p, candidate.Assets = p.addTokensBonded(tokens) candidate.Status = Bonded - k.setCandidate(ctx, candidate) + return p, candidate } //_______________________________________________________________________ -func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.GetPool(ctx) - issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens +func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // (tokens/shares)^-1 * tokens p.BondedPool += amount p.BondedShares = p.BondedShares.Add(issuedShares) - k.setPool(ctx, p) - return + return p, issuedShares } -func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.GetPool(ctx) +func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.BondedShares = p.BondedShares.Sub(shares) p.BondedPool -= removedTokens - k.setPool(ctx, p) - return + return p, removedTokens } -func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.GetPool(ctx) +func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens p.UnbondedShares = p.UnbondedShares.Add(issuedShares) p.UnbondedPool += amount - k.setPool(ctx, p) - return + return p, issuedShares } -func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.GetPool(ctx) +func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.UnbondedShares = p.UnbondedShares.Sub(shares) p.UnbondedPool -= removedTokens - k.setPool(ctx, p) - return + return p, removedTokens } //_______________________________________________________________________ // add tokens to a candidate -func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { +func (p Pool) candidateAddTokens(candidate Candidate, + amount int64) (p2 Pool, candidate2 Candidate, issuedDelegatorShares sdk.Rat) { - p := k.GetPool(ctx) exRate := candidate.delegatorShareExRate() var receivedGlobalShares sdk.Rat if candidate.Status == Bonded { - receivedGlobalShares = k.addTokensBonded(ctx, amount) + p, receivedGlobalShares = p.addTokensBonded(amount) } else { - receivedGlobalShares = k.addTokensUnbonded(ctx, amount) + p, receivedGlobalShares = p.addTokensUnbonded(amount) } candidate.Assets = candidate.Assets.Add(receivedGlobalShares) issuedDelegatorShares = exRate.Mul(receivedGlobalShares) candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - k.setPool(ctx, p) // TODO cache Pool? - return + + return p, candidate, issuedDelegatorShares } // remove shares from a candidate -func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { +func (p Pool) candidateRemoveShares(candidate Candidate, + shares sdk.Rat) (p2 Pool, candidate2 Candidate, createdCoins int64) { - p := k.GetPool(ctx) //exRate := candidate.delegatorShareExRate() //XXX make sure not used globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) if candidate.Status == Bonded { - createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove) + p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove) } else { - createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove) + p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove) } candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) candidate.Liabilities = candidate.Liabilities.Sub(shares) - k.setPool(ctx, p) // TODO cache Pool? - return + return p, candidate, createdCoins } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index 760a89a16..878288986 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -1,22 +1,374 @@ package stake import ( + "fmt" + "math/rand" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" ) -func TestPool(t *testing.T) { +func TestBondedToUnbondedPool(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) - expPool := initialPool() - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.OneRat, + Liabilities: sdk.OneRat, + } + poolB, candB := poolA.bondedToUnbondedPool(candA) - //modify a params, save, and retrieve - expPool.TotalSupply = 777 - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + // status unbonded + assert.Equal(t, candB.Status, Unbonded) + // same exchange rate, assets unchanged + assert.Equal(t, candB.Assets, candA.Assets) + // bonded pool decreased + assert.Equal(t, poolB.BondedPool, poolA.BondedPool-candA.Assets.Evaluate()) + // unbonded pool increased + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+candA.Assets.Evaluate()) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) +} + +func TestUnbonbedtoBondedPool(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.OneRat, + Liabilities: sdk.OneRat, + } + candA.Status = Unbonded + poolB, candB := poolA.unbondedToBondedPool(candA) + + // status bonded + assert.Equal(t, candB.Status, Bonded) + // same exchange rate, assets unchanged + assert.Equal(t, candB.Assets, candA.Assets) + // bonded pool increased + assert.Equal(t, poolB.BondedPool, poolA.BondedPool+candA.Assets.Evaluate()) + // unbonded pool decreased + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-candA.Assets.Evaluate()) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) +} + +func TestAddTokensBonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + poolB, sharesB := poolA.addTokensBonded(10) + assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB)) + assert.Equal(t, poolB.BondedPool, poolA.BondedPool+10) + + // same number of bonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool)) +} + +func TestRemoveSharesBonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) + assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.BondedPool, poolA.BondedPool-tokensB) + + // same number of bonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool)) +} + +func TestAddTokensUnbonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, sharesB := poolA.addTokensUnbonded(10) + assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat) + + // correct changes to unbonded shares and unbonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB)) + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+10) + + // same number of unbonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool)) +} + +func TestRemoveSharesUnbonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) + assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat) + + // correct changes to unbonded shares and bonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-tokensB) + + // same number of unbonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool)) +} + +func TestCandidateAddTokens(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + } + poolA.BondedPool = candA.Assets.Evaluate() + poolA.BondedShares = candA.Assets + assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, candB, sharesB := poolA.candidateAddTokens(candA, 10) + + // shares were issued + assert.Equal(t, sdk.NewRat(10).Mul(candA.delegatorShareExRate()), sharesB) + // pool shares were added + assert.Equal(t, candB.Assets, candA.Assets.Add(sdk.NewRat(10))) + // conservation of tokens + assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool) +} + +func TestCandidateRemoveShares(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + } + poolA.BondedPool = candA.Assets.Evaluate() + poolA.BondedShares = candA.Assets + assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, candB, coinsB := poolA.candidateRemoveShares(candA, sdk.NewRat(10)) + + // coins were created + assert.Equal(t, coinsB, int64(10)) + // pool shares were removed + assert.Equal(t, candB.Assets, candA.Assets.Sub(sdk.NewRat(10).Mul(candA.delegatorShareExRate()))) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool) +} + +///////////////////////////////////// +// TODO Make all random tests less obfuscated! + +// generate a random candidate +func randomCandidate(r *rand.Rand) Candidate { + var status CandidateStatus + if r.Float64() < float64(0.5) { + status = Bonded + } else { + status = Unbonded + } + assets := sdk.NewRat(int64(r.Int31n(10000))) + liabilities := sdk.NewRat(int64(r.Int31n(10000))) + return Candidate{ + Status: status, + Address: addrs[0], + PubKey: pks[0], + Assets: assets, + Liabilities: liabilities, + } +} + +// generate a random staking state +func randomSetup(r *rand.Rand) (Pool, Candidate) { + pool := Pool{ + TotalSupply: 0, + BondedShares: sdk.ZeroRat, + UnbondedShares: sdk.ZeroRat, + BondedPool: 0, + UnbondedPool: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + + candidate := randomCandidate(r) + if candidate.Status == Bonded { + pool.BondedShares = pool.BondedShares.Add(candidate.Assets) + pool.BondedPool += candidate.Assets.Evaluate() + } else { + pool.UnbondedShares = pool.UnbondedShares.Add(candidate.Assets) + pool.UnbondedPool += candidate.Assets.Evaluate() + } + return pool, candidate +} + +func randomTokens(r *rand.Rand) int64 { + return int64(r.Int31n(10000)) +} + +// operation that transforms staking state +type Operation func(p Pool, c Candidate) (Pool, Candidate, int64, string) + +// pick a random staking operation +func randomOperation(r *rand.Rand) Operation { + operations := []Operation{ + + // bond/unbond + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + var msg string + if cand.Status == Bonded { + msg = fmt.Sprintf("Unbonded previously bonded candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand = p.bondedToUnbondedPool(cand) + } else { + msg = fmt.Sprintf("Bonded previously unbonded candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand = p.unbondedToBondedPool(cand) + } + return p, cand, 0, msg + }, + + // add some tokens to a candidate + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + tokens := int64(r.Int31n(1000)) + + msg := fmt.Sprintf("candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + + p, cand, _ = p.candidateAddTokens(cand, tokens) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + return p, cand, -1 * tokens, msg // tokens are removed so for accounting must be negative + }, + + // remove some shares from a candidate + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + shares := sdk.NewRat(int64(r.Int31n(1000))) + + if shares.GT(cand.Liabilities) { + shares = cand.Liabilities.Quo(sdk.NewRat(2)) + } + + msg := fmt.Sprintf("candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand, tokens := p.candidateRemoveShares(cand, shares) + + msg = fmt.Sprintf("Removed %d shares from %s", shares.Evaluate(), msg) + + return p, cand, tokens, msg + }, + } + r.Shuffle(len(operations), func(i, j int) { + operations[i], operations[j] = operations[j], operations[i] + }) + return operations[0] +} + +// ensure invariants that should always be true are true +func assertInvariants(t *testing.T, msg string, + pOrig Pool, cOrig Candidate, pMod Pool, cMod Candidate, tokens int64) { + + // total tokens conserved + require.Equal(t, + pOrig.UnbondedPool+pOrig.BondedPool, + pMod.UnbondedPool+pMod.BondedPool+tokens, + "msg: %v\n, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n", + msg, + pOrig.UnbondedPool, pOrig.BondedPool, + pMod.UnbondedPool, pMod.BondedPool, tokens) + + // nonnegative shares + require.False(t, pMod.BondedShares.LT(sdk.ZeroRat), + "msg: %v\n, pOrig: %v\n, pMod: %v\n, cOrig: %v\n, cMod %v, tokens: %v\n", + msg, pOrig, pMod, cOrig, cMod, tokens) + require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat), + "msg: %v\n, pOrig: %v\n, pMod: %v\n, cOrig: %v\n, cMod %v, tokens: %v\n", + msg, pOrig, pMod, cOrig, cMod, tokens) + + // nonnegative ex rates + require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative bondedShareExRate: %d", + msg, pMod.bondedShareExRate().Evaluate()) + + require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative unbondedShareExRate: %d", + msg, pMod.unbondedShareExRate().Evaluate()) + + // nonnegative ex rate + require.False(t, cMod.delegatorShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.delegatorShareExRate(): %v (candidate.PubKey: %s)", + msg, + cMod.delegatorShareExRate(), + cMod.PubKey, + ) + + // nonnegative assets / liabilities + require.False(t, cMod.Assets.LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.Assets: %d (candidate.Liabilities: %d, candidate.PubKey: %s)", + msg, + cMod.Assets.Evaluate(), + cMod.Liabilities.Evaluate(), + cMod.PubKey, + ) + + require.False(t, cMod.Liabilities.LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.Liabilities: %d (candidate.Assets: %d, candidate.PubKey: %s)", + msg, + cMod.Liabilities.Evaluate(), + cMod.Assets.Evaluate(), + cMod.PubKey, + ) +} + +// run random operations in a random order on a random state, assert invariants hold +func TestIntegrationInvariants(t *testing.T) { + for i := 0; i < 10; i++ { + + r1 := rand.New(rand.NewSource(time.Now().UnixNano())) + pool, candidates := randomSetup(r1) + initialPool, initialCandidates := pool, candidates + + assertInvariants(t, "no operation", + initialPool, initialCandidates, + pool, candidates, 0) + + for j := 0; j < 100; j++ { + + r2 := rand.New(rand.NewSource(time.Now().UnixNano())) + pool, candidates, tokens, msg := randomOperation(r2)(pool, candidates) + + assertInvariants(t, msg, + initialPool, initialCandidates, + pool, candidates, tokens) + } + } } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index aef425581..03f9fe92c 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -124,12 +124,11 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins ck := bank.NewCoinKeeper(accountMapper) keeper := NewKeeper(ctx, cdc, keyStake, ck) - //params := paramsNoInflation() - params := keeper.GetParams(ctx) - // fill all the addresses with some coins for _, addr := range addrs { - ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}}) + ck.AddCoins(ctx, addr, sdk.Coins{ + {keeper.GetParams(ctx).BondDenom, initCoins}, + }) } return ctx, accountMapper, keeper diff --git a/x/stake/tick.go b/x/stake/tick.go index 6aa2da95d..0a85cd865 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -2,42 +2,38 @@ package stake import ( sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" ) const ( - hrsPerYear = 8766 // as defined by a julian year of 365.25 days - precision = 1000000000 + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + precision = 1000000000 ) -var hrsPerYrRat = sdk.NewRat(hrsPerYear) // as defined by a julian year of 365.25 days +var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days // Tick - called at the end of every block -func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) { - - // retrieve params +func (k Keeper) Tick(ctx sdk.Context) (change []Validator) { p := k.GetPool(ctx) - height := ctx.BlockHeight() // Process Validator Provisions - // XXX right now just process every 5 blocks, in new SDK make hourly - if p.InflationLastTime+5 <= height { - p.InflationLastTime = height - k.processProvisions(ctx) + blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + if p.InflationLastTime+blockTime >= 3600 { + p.InflationLastTime = blockTime + p = k.processProvisions(ctx) } - newVals := k.GetValidators(ctx) + // save the params + k.setPool(ctx, p) - // XXX determine change from old validators, set to change - _ = newVals - return change, nil + change = k.getAccUpdateValidators(ctx) + return } // process provisions for an hour period -func (k Keeper) processProvisions(ctx sdk.Context) { +func (k Keeper) processProvisions(ctx sdk.Context) Pool { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx).Round(precision) + pool.Inflation = k.nextInflation(ctx) // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term @@ -46,9 +42,7 @@ func (k Keeper) processProvisions(ctx sdk.Context) { provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate() pool.BondedPool += provisions pool.TotalSupply += provisions - - // save the params - k.setPool(ctx, pool) + return pool } // get the next inflation rate for the hour @@ -75,5 +69,5 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { inflation = params.InflationMin } - return + return inflation.Round(precision) } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 540ce4699..24d95809f 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -1,116 +1,134 @@ package stake -//import ( -//"testing" +import ( + "testing" -//sdk "github.com/cosmos/cosmos-sdk/types" -//"github.com/stretchr/testify/assert" -//) + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) -//func TestGetInflation(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//params := defaultParams() -//keeper.setParams(ctx, params) -//gs := keeper.GetPool(ctx) +func TestGetInflation(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + hrsPerYrRat := sdk.NewRat(hrsPerYr) -//// Governing Mechanism: -//// bondedRatio = BondedPool / TotalSupply -//// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + // Governing Mechanism: + // bondedRatio = BondedPool / TotalSupply + // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange -//tests := []struct { -//setBondedPool, setTotalSupply int64 -//setInflation, expectedChange sdk.Rat -//}{ -//// with 0% bonded atom supply the inflation should increase by InflationRateChange -//{0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + tests := []struct { + name string + setBondedPool, setTotalSupply int64 + setInflation, expectedChange sdk.Rat + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, -//// 100% bonded, starting at 20% inflation and being reduced -//{1, 1, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {"test 2", 1, 1, sdk.NewRat(20, 100), + sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, -//// 50% bonded, starting at 10% inflation and being increased -//{1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + // 50% bonded, starting at 10% inflation and being increased + {"test 3", 1, 2, sdk.NewRat(10, 100), + sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, -//// test 7% minimum stop (testing with 100% bonded) -//{1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, -//{1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)}, + // test 7% minimum stop (testing with 100% bonded) + {"test 4", 1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, + {"test 5", 1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, -//// test 20% maximum stop (testing with 0% bonded) -//{0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, -//{0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)}, + // test 20% maximum stop (testing with 0% bonded) + {"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, + {"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, -//// perfect balance shouldn't change inflation -//{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, -//} -//for _, tc := range tests { -//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply -//gs.Inflation = tc.setInflation + // perfect balance shouldn't change inflation + {"test 8", 67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, + } + for _, tc := range tests { + pool.BondedPool, pool.TotalSupply = tc.setBondedPool, tc.setTotalSupply + pool.Inflation = tc.setInflation + keeper.setPool(ctx, pool) -//inflation := nextInflation(gs, params) -//diffInflation := inflation.Sub(tc.setInflation) + inflation := keeper.nextInflation(ctx) + diffInflation := inflation.Sub(tc.setInflation) -//assert.True(t, diffInflation.Equal(tc.expectedChange), -//"%v, %v", diffInflation, tc.expectedChange) -//} -//} + assert.True(t, diffInflation.Equal(tc.expectedChange), + "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) + } +} -//func TestProcessProvisions(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//params := defaultParams() -//keeper.setParams(ctx, params) -//gs := keeper.GetPool(ctx) +func TestProcessProvisions(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := defaultParams() + keeper.setParams(ctx, params) + pool := keeper.GetPool(ctx) -//// create some candidates some bonded, some unbonded -//candidates := candidatesFromAddrsEmpty(addrs) -//for i, candidate := range candidates { -//if i < 5 { -//candidate.Status = Bonded -//} -//mintedTokens := int64((i + 1) * 10000000) -//gs.TotalSupply += mintedTokens -//keeper.candidateAddTokens(ctx, candidate, mintedTokens) -//keeper.setCandidate(ctx, candidate) -//} -//var totalSupply int64 = 550000000 -//var bondedShares int64 = 150000000 -//var unbondedShares int64 = 400000000 + // create some candidates some bonded, some unbonded + candidates := make([]Candidate, 10) + for i := 0; i < 10; i++ { + c := Candidate{ + Status: Unbonded, + PubKey: pks[i], + Address: addrs[i], + Assets: sdk.NewRat(0), + Liabilities: sdk.NewRat(0), + } + if i < 5 { + c.Status = Bonded + } + mintedTokens := int64((i + 1) * 10000000) + pool.TotalSupply += mintedTokens + pool, c, _ = pool.candidateAddTokens(c, mintedTokens) -//// initial bonded ratio ~ 27% -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio()) + keeper.setCandidate(ctx, c) + candidates[i] = c + } + keeper.setPool(ctx, pool) + var totalSupply int64 = 550000000 + var bondedShares int64 = 150000000 + var unbondedShares int64 = 400000000 + assert.Equal(t, totalSupply, pool.TotalSupply) + assert.Equal(t, bondedShares, pool.BondedPool) + assert.Equal(t, unbondedShares, pool.UnbondedPool) -//// Supplies -//assert.Equal(t, totalSupply, p.TotalSupply) -//assert.Equal(t, bondedShares, p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) + // initial bonded ratio ~ 27% + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", pool.bondedRatio()) -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate()) + // test the value of candidate shares + assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat), "%v", pool.bondedShareExRate()) -//initialSupply := p.TotalSupply -//initialUnbonded := p.TotalSupply - p.BondedPool + initialSupply := pool.TotalSupply + initialUnbonded := pool.TotalSupply - pool.BondedPool -//// process the provisions a year -//for hr := 0; hr < 8766; hr++ { -//expInflation := nextInflation(gs, params).Round(1000000000) -//expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() -//startBondedPool := p.BondedPool -//startTotalSupply := p.TotalSupply -//processProvisions(ctx, keeper, p, params) -//assert.Equal(t, startBondedPool+expProvisions, p.BondedPool) -//assert.Equal(t, startTotalSupply+expProvisions, p.TotalSupply) -//} -//assert.NotEqual(t, initialSupply, p.TotalSupply) -//assert.Equal(t, initialUnbonded, p.UnbondedPool) -////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, p.TotalSupply-gs.BondedPool)) + // process the provisions a year + for hr := 0; hr < 8766; hr++ { + pool := keeper.GetPool(ctx) + expInflation := keeper.nextInflation(ctx).Round(1000000000) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat)).Evaluate() + startBondedPool := pool.BondedPool + startTotalSupply := pool.TotalSupply + pool = keeper.processProvisions(ctx) + keeper.setPool(ctx, pool) + //fmt.Printf("hr %v, startBondedPool %v, expProvisions %v, pool.BondedPool %v\n", hr, startBondedPool, expProvisions, pool.BondedPool) + require.Equal(t, startBondedPool+expProvisions, pool.BondedPool, "hr %v", hr) + require.Equal(t, startTotalSupply+expProvisions, pool.TotalSupply) + } + pool = keeper.GetPool(ctx) + assert.NotEqual(t, initialSupply, pool.TotalSupply) + assert.Equal(t, initialUnbonded, pool.UnbondedPool) + //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, pool.TotalSupply-pool.BondedPool)) -//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio()) + // initial bonded ratio ~ from 27% to 40% increase for bonded holders ownership of total supply + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(271734723, 671734723)), "%v", pool.bondedRatio()) -//// global supply -//assert.Equal(t, int64(611813022), p.TotalSupply) -//assert.Equal(t, int64(211813022), p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) + // global supply + assert.Equal(t, int64(671734723), pool.TotalSupply) + assert.Equal(t, int64(271734723), pool.BondedPool) + assert.Equal(t, unbondedShares, pool.UnbondedPool) -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate()) + // test the value of candidate shares + assert.True(t, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(271734723)), "%v", pool.bondedShareExRate()) -//} +} diff --git a/x/stake/types.go b/x/stake/types.go index 2799e1d76..4ba7c59d0 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -42,8 +42,6 @@ type Pool struct { Inflation sdk.Rat `json:"inflation"` // current annual inflation rate } -// XXX define globalstate interface? - func initialPool() Pool { return Pool{ TotalSupply: 0, @@ -56,30 +54,6 @@ func initialPool() Pool { } } -// get the bond ratio of the global state -func (p Pool) bondedRatio() sdk.Rat { - if p.TotalSupply > 0 { - return sdk.NewRat(p.BondedPool, p.TotalSupply) - } - return sdk.ZeroRat -} - -// get the exchange rate of bonded token per issued share -func (p Pool) bondedShareExRate() sdk.Rat { - if p.BondedShares.IsZero() { - return sdk.OneRat - } - return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) -} - -// get the exchange rate of unbonded tokens held in candidates per issued share -func (p Pool) unbondedShareExRate() sdk.Rat { - if p.UnbondedShares.IsZero() { - return sdk.OneRat - } - return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) -} - //_______________________________________________________________________________________________________ // CandidateStatus - status of a validator-candidate