From 266a8392d3e643fb33d84567c217c7a5146c0e83 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 12 Jan 2018 05:30:39 +0000 Subject: [PATCH 01/54] initial add gaia working --- examples/gaia/client.go | 97 ++++++ examples/gaia/main.go | 50 +++ examples/gaia/node.go | 69 +++++ examples/gaia/rest.go | 89 ++++++ examples/gaia/sh_tests/stake.sh | 275 +++++++++++++++++ x/stake/commands/query.go | 136 ++++++++ x/stake/commands/tx.go | 195 ++++++++++++ x/stake/errors.go | 53 ++++ x/stake/handler.go | 529 ++++++++++++++++++++++++++++++++ x/stake/handler_test.go | 336 ++++++++++++++++++++ x/stake/rest/query.go | 188 ++++++++++++ x/stake/rest/tx.go | 159 ++++++++++ x/stake/state.go | 273 ++++++++++++++++ x/stake/state_test.go | 116 +++++++ x/stake/test_common.go | 91 ++++++ x/stake/tick.go | 75 +++++ x/stake/tick_test.go | 120 ++++++++ x/stake/tx.go | 147 +++++++++ x/stake/tx_test.go | 104 +++++++ x/stake/types.go | 414 +++++++++++++++++++++++++ x/stake/types_test.go | 251 +++++++++++++++ 21 files changed, 3767 insertions(+) create mode 100644 examples/gaia/client.go create mode 100644 examples/gaia/main.go create mode 100644 examples/gaia/node.go create mode 100644 examples/gaia/rest.go create mode 100644 examples/gaia/sh_tests/stake.sh create mode 100644 x/stake/commands/query.go create mode 100644 x/stake/commands/tx.go create mode 100644 x/stake/errors.go create mode 100644 x/stake/handler.go create mode 100644 x/stake/handler_test.go create mode 100644 x/stake/rest/query.go create mode 100644 x/stake/rest/tx.go create mode 100644 x/stake/state.go create mode 100644 x/stake/state_test.go create mode 100644 x/stake/test_common.go create mode 100644 x/stake/tick.go create mode 100644 x/stake/tick_test.go create mode 100644 x/stake/tx.go create mode 100644 x/stake/tx_test.go create mode 100644 x/stake/types.go create mode 100644 x/stake/types_test.go diff --git a/examples/gaia/client.go b/examples/gaia/client.go new file mode 100644 index 0000000000..6344fc3489 --- /dev/null +++ b/examples/gaia/client.go @@ -0,0 +1,97 @@ +package main + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/commands" + "github.com/cosmos/cosmos-sdk/client/commands/commits" + "github.com/cosmos/cosmos-sdk/client/commands/keys" + "github.com/cosmos/cosmos-sdk/client/commands/proxy" + "github.com/cosmos/cosmos-sdk/client/commands/query" + rpccmd "github.com/cosmos/cosmos-sdk/client/commands/rpc" + txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" + authcmd "github.com/cosmos/cosmos-sdk/modules/auth/commands" + basecmd "github.com/cosmos/cosmos-sdk/modules/base/commands" + coincmd "github.com/cosmos/cosmos-sdk/modules/coin/commands" + feecmd "github.com/cosmos/cosmos-sdk/modules/fee/commands" + ibccmd "github.com/cosmos/cosmos-sdk/modules/ibc/commands" + noncecmd "github.com/cosmos/cosmos-sdk/modules/nonce/commands" + rolecmd "github.com/cosmos/cosmos-sdk/modules/roles/commands" + + stakecmd "github.com/cosmos/gaia/modules/stake/commands" +) + +// clientCmd is the entry point for this binary +var clientCmd = &cobra.Command{ + Use: "client", + Short: "Gaia light client", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +func prepareClientCommands() { + commands.AddBasicFlags(clientCmd) + + // Prepare queries + query.RootCmd.AddCommand( + // These are default parsers, but optional in your app (you can remove key) + query.TxQueryCmd, + query.KeyQueryCmd, + coincmd.AccountQueryCmd, + noncecmd.NonceQueryCmd, + rolecmd.RoleQueryCmd, + ibccmd.IBCQueryCmd, + + //stakecmd.CmdQueryValidator, + stakecmd.CmdQueryCandidates, + stakecmd.CmdQueryCandidate, + stakecmd.CmdQueryDelegatorBond, + stakecmd.CmdQueryDelegatorCandidates, + ) + + // set up the middleware + txcmd.Middleware = txcmd.Wrappers{ + feecmd.FeeWrapper{}, + rolecmd.RoleWrapper{}, + noncecmd.NonceWrapper{}, + basecmd.ChainWrapper{}, + authcmd.SigWrapper{}, + } + txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags()) + + // you will always want this for the base send command + txcmd.RootCmd.AddCommand( + // This is the default transaction, optional in your app + coincmd.SendTxCmd, + coincmd.CreditTxCmd, + // this enables creating roles + rolecmd.CreateRoleTxCmd, + // these are for handling ibc + ibccmd.RegisterChainTxCmd, + ibccmd.UpdateChainTxCmd, + ibccmd.PostPacketTxCmd, + + stakecmd.CmdDeclareCandidacy, + stakecmd.CmdEditCandidacy, + stakecmd.CmdDelegate, + stakecmd.CmdUnbond, + ) + + clientCmd.AddCommand( + proxy.RootCmd, + lineBreak, + + txcmd.RootCmd, + query.RootCmd, + rpccmd.RootCmd, + lineBreak, + + keys.RootCmd, + commands.InitCmd, + commands.ResetCmd, + commits.RootCmd, + lineBreak, + ) + +} diff --git a/examples/gaia/main.go b/examples/gaia/main.go new file mode 100644 index 0000000000..b631e5cba3 --- /dev/null +++ b/examples/gaia/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "os" + + "github.com/spf13/cobra" + + "github.com/tendermint/tmlibs/cli" + + basecmd "github.com/cosmos/cosmos-sdk/server/commands" + "github.com/cosmos/gaia/version" +) + +// GaiaCmd is the entry point for this binary +var ( + GaiaCmd = &cobra.Command{ + Use: "gaia", + Short: "The Cosmos Network delegation-game test", + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, + } + + lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} +) + +func main() { + // disable sorting + cobra.EnableCommandSorting = false + + // add commands + prepareNodeCommands() + prepareRestServerCommands() + prepareClientCommands() + + GaiaCmd.AddCommand( + nodeCmd, + restServerCmd, + clientCmd, + + lineBreak, + version.VersionCmd, + //auto.AutoCompleteCmd, + ) + + // prepare and add flags + basecmd.SetUpRoot(GaiaCmd) + executor := cli.PrepareMainCmd(GaiaCmd, "GA", os.ExpandEnv("$HOME/.cosmos-gaia-cli")) + executor.Execute() +} diff --git a/examples/gaia/node.go b/examples/gaia/node.go new file mode 100644 index 0000000000..aec74aeaf0 --- /dev/null +++ b/examples/gaia/node.go @@ -0,0 +1,69 @@ +package main + +import ( + "github.com/spf13/cobra" + + abci "github.com/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/auth" + "github.com/cosmos/cosmos-sdk/modules/base" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/modules/fee" + "github.com/cosmos/cosmos-sdk/modules/ibc" + "github.com/cosmos/cosmos-sdk/modules/nonce" + "github.com/cosmos/cosmos-sdk/modules/roles" + basecmd "github.com/cosmos/cosmos-sdk/server/commands" + "github.com/cosmos/cosmos-sdk/stack" + "github.com/cosmos/cosmos-sdk/state" + + "github.com/cosmos/gaia/modules/stake" +) + +// nodeCmd is the entry point for this binary +var nodeCmd = &cobra.Command{ + Use: "node", + Short: "The Cosmos Network delegation-game blockchain test", + Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, +} + +func prepareNodeCommands() { + + basecmd.Handler = stack.New( + base.Logger{}, + stack.Recovery{}, + auth.Signatures{}, + base.Chain{}, + stack.Checkpoint{OnCheck: true}, + nonce.ReplayCheck{}, + ). + IBC(ibc.NewMiddleware()). + Apps( + roles.NewMiddleware(), + fee.NewSimpleFeeMiddleware(coin.Coin{"fermion", 0}, fee.Bank), + stack.Checkpoint{OnDeliver: true}, + ). + Dispatch( + coin.NewHandler(), + stack.WrapHandler(roles.NewHandler()), + stack.WrapHandler(ibc.NewHandler()), + stake.NewHandler(), + ) + + nodeCmd.AddCommand( + basecmd.GetInitCmd("fermion", []string{"stake/allowed_bond_denom/fermion"}), + basecmd.GetTickStartCmd(sdk.TickerFunc(tickFn)), + basecmd.UnsafeResetAllCmd, + ) +} + +// Tick - Called every block even if no transaction, process all queues, +// validator rewards, and calculate the validator set difference +func tickFn(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) { + // first need to prefix the store, at this point it's a global store + store = stack.PrefixedStore(stake.Name(), store) + + // execute Tick + change, err = stake.Tick(ctx, store) + return +} diff --git a/examples/gaia/rest.go b/examples/gaia/rest.go new file mode 100644 index 0000000000..7a96978961 --- /dev/null +++ b/examples/gaia/rest.go @@ -0,0 +1,89 @@ +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/tendermint/tmlibs/cli" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/commands" + rest "github.com/cosmos/cosmos-sdk/client/rest" + coinrest "github.com/cosmos/cosmos-sdk/modules/coin/rest" + noncerest "github.com/cosmos/cosmos-sdk/modules/nonce/rest" + rolerest "github.com/cosmos/cosmos-sdk/modules/roles/rest" + + stakerest "github.com/cosmos/gaia/modules/stake/rest" +) + +const defaultAlgo = "ed25519" + +var ( + restServerCmd = &cobra.Command{ + Use: "rest-server", + Short: "REST client for gaia commands", + Long: `Gaiaserver presents a nice (not raw hex) interface to the gaia blockchain structure.`, + RunE: func(cmd *cobra.Command, args []string) error { + return cmdRestServer(cmd, args) + }, + } + + flagPort = "port" +) + +func prepareRestServerCommands() { + commands.AddBasicFlags(restServerCmd) + restServerCmd.PersistentFlags().IntP(flagPort, "p", 8998, "port to run the server on") +} + +func cmdRestServer(cmd *cobra.Command, args []string) error { + router := mux.NewRouter() + + rootDir := viper.GetString(cli.HomeFlag) + keyMan := client.GetKeyManager(rootDir) + serviceKeys := rest.NewServiceKeys(keyMan) + serviceTxs := rest.NewServiceTxs(commands.GetNode()) + + routeRegistrars := []func(*mux.Router) error{ + // rest.Keys handlers + serviceKeys.RegisterCRUD, + + // Coin handlers (Send, Query, SearchSent) + coinrest.RegisterAll, + + // Roles createRole handler + rolerest.RegisterCreateRole, + + // Gaia sign transactions handler + serviceKeys.RegisterSignTx, + // Gaia post transaction handler + serviceTxs.RegisterPostTx, + + // Nonce query handler + noncerest.RegisterQueryNonce, + + // Staking query handlers + stakerest.RegisterQueryCandidate, + stakerest.RegisterQueryCandidates, + stakerest.RegisterQueryDelegatorBond, + stakerest.RegisterQueryDelegatorCandidates, + // Staking tx builders + stakerest.RegisterDelegate, + stakerest.RegisterUnbond, + } + + for _, routeRegistrar := range routeRegistrars { + if err := routeRegistrar(router); err != nil { + log.Fatal(err) + } + } + + addr := fmt.Sprintf(":%d", viper.GetInt(flagPort)) + + log.Printf("Serving on %q", addr) + return http.ListenAndServe(addr, router) +} diff --git a/examples/gaia/sh_tests/stake.sh b/examples/gaia/sh_tests/stake.sh new file mode 100644 index 0000000000..4f5d1c7703 --- /dev/null +++ b/examples/gaia/sh_tests/stake.sh @@ -0,0 +1,275 @@ +#!/bin/bash +set -u + +# These global variables are required for common.sh +SERVER_EXE="gaia node" +CLIENT_EXE="gaia client" +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +DELEGATOR=${ACCOUNTS[2]} +POOR=${ACCOUNTS[4]} + +BASE_DIR=$HOME/stake_test +BASE_DIR2=$HOME/stake_test2 +SERVER1=$BASE_DIR/server +SERVER2=$BASE_DIR2/server + +oneTimeSetUp() { + #[ "$2" ] || echo "missing parameters, line=${LINENO}" ; exit 1; + + + # These are passed in as args + CHAIN_ID="stake_test" + + # TODO Make this more robust + if [ "$BASE_DIR" == "$HOME/" ]; then + echo "Must be called with argument, or it will wipe your home directory" + exit 1 + fi + + rm -rf $BASE_DIR 2>/dev/null + mkdir -p $BASE_DIR + + if [ "$BASE_DIR2" == "$HOME/" ]; then + echo "Must be called with argument, or it will wipe your home directory" + exit 1 + fi + rm -rf $BASE_DIR2 2>/dev/null + mkdir -p $BASE_DIR2 + + # Set up client - make sure you use the proper prefix if you set + # a custom CLIENT_EXE + export BC_HOME=${BASE_DIR}/client + prepareClient + + # start the node server + set +u ; initServer $BASE_DIR $CHAIN_ID ; set -u + if [ $? != 0 ]; then return 1; fi + + set +u ; initClient $CHAIN_ID ; set -u + if [ $? != 0 ]; then return 1; fi + + printf "...Testing may begin!\n\n\n" + +} + +oneTimeTearDown() { + kill -9 $PID_SERVER2 >/dev/null 2>&1 + set +u ; quickTearDown ; set -u +} + +# Ex Usage: checkCandidate $PUBKEY $EXPECTED_VOTING_POWER +checkCandidate() { + CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1) + if ! assertTrue "line=${LINENO}, bad query" $?; then + return 1 + fi + assertEquals "line=${LINENO}, proper voting power" "$2" $(echo $CANDIDATE | jq .data.voting_power) + return $? +} + +# Ex Usage: checkCandidate $PUBKEY +checkCandidateEmpty() { + CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1 2>/dev/null) + if ! assertFalse "line=${LINENO}, expected empty query" $?; then + return 1 + fi +} + +# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY $EXPECTED_SHARES +checkDelegatorBond() { + BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2) + if ! assertTrue "line=${LINENO}, account must exist" $?; then + return 1 + fi + assertEquals "line=${LINENO}, proper bond amount" "$3" $(echo $BOND | jq .data.Shares) + return $? +} + +# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY +checkDelegatorBondEmpty() { + BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2 2>/dev/null) + if ! assertFalse "line=${LINENO}, expected empty query" $?; then + return 1 + fi +} + +#______________________________________________________________________________________ + +test00GetAccount() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account" + + set +u ; checkAccount $SENDER "9007199254740992" ; set -u + + ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null) + assertFalse "line=${LINENO}, has no genesis account" $? +} + +test01SendTx() { + assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992fermion --sequence=1" + assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" "$RECV" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + set +u + checkAccount $SENDER "9007199254740000" $TX_HEIGHT + # make sure 0x prefix also works + checkAccount "0x$SENDER" "9007199254740000" $TX_HEIGHT + checkAccount $RECV "992" $TX_HEIGHT + + # Make sure tx is indexed + checkSendTx $HASH $TX_HEIGHT $SENDER "992" + set -u +} + +test02DeclareCandidacy() { + + # the premise of this test is to run a second validator (from rich) and then bond and unbond some tokens + # first create a second node to run and connect to the system + + # init the second node + SERVER_LOG2=$BASE_DIR2/node2.log + GENKEY=$(${CLIENT_EXE} keys get ${RICH} | awk '{print $2}') + ${SERVER_EXE} init $GENKEY --chain-id $CHAIN_ID --home=$SERVER2 >>$SERVER_LOG2 + if [ $? != 0 ]; then return 1; fi + + # copy in the genesis from the first initialization to the new server + cp $SERVER1/genesis.json $SERVER2/genesis.json + + # point the new config to the old server location + rm $SERVER2/config.toml + echo 'proxy_app = "tcp://127.0.0.1:46668" + moniker = "anonymous" + fast_sync = true + db_backend = "leveldb" + log_level = "state:info,*:error" + + [rpc] + laddr = "tcp://0.0.0.0:46667" + + [p2p] + laddr = "tcp://0.0.0.0:46666" + seeds = "0.0.0.0:46656"' >$SERVER2/config.toml + + # start the second node + ${SERVER_EXE} start --home=$SERVER2 >>$SERVER_LOG2 2>&1 & + sleep 1 + PID_SERVER2=$! + disown + if ! ps $PID_SERVER2 >/dev/null; then + echo "**FAILED**" + cat $SERVER_LOG2 + return 1 + fi + + # get the pubkey of the second validator + PK2=$(cat $SERVER2/priv_validator.json | jq -r .pub_key.data) + + CAND_ADDR=$(getAddr $POOR) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx declare-candidacy --sequence=1 --amount=10fermion --name=$POOR --pubkey=$PK2 --moniker=rigey) + if [ $? != 0 ]; then return 1; fi + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $CAND_ADDR "982" $TX_HEIGHT ; set -u + checkCandidate $PK2 "10" + checkDelegatorBond $CAND_ADDR $PK2 "10" +} + +test03Delegate() { + # send some coins to a delegator + DELA_ADDR=$(getAddr $DELEGATOR) + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --sequence=2 --amount=15fermion --to=$DELA_ADDR --name=$RICH) + txSucceeded $? "$TX" "$DELA_ADDR" + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u + + # delegate some coins to the new + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=1 --amount=10fermion --name=$DELEGATOR --pubkey=$PK2) + if [ $? != 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "5" $TX_HEIGHT ; set -u + checkCandidate $PK2 "20" + checkDelegatorBond $DELA_ADDR $PK2 "10" + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=2 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2) + if [ $? != 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u + checkCandidate $PK2 "23" + checkDelegatorBond $DELA_ADDR $PK2 "13" + + # attempt a delegation without enough funds + # NOTE the sequence number still increments here because it will fail + # only during DeliverTx - however this should be updated (TODO) in new + # SDK when we can fail in CheckTx + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=3 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2 2>/dev/null) + if [ $? == 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u + checkCandidate $PK2 "23" + checkDelegatorBond $DELA_ADDR $PK2 "13" + + # perform the final delegation which should empty the delegators account + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=4 --amount=2fermion --name=$DELEGATOR --pubkey=$PK2) + if [ $? != 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "null" $TX_HEIGHT ; set -u #empty account is null + checkCandidate $PK2 "25" +} + +test04Unbond() { + # unbond from the delegator a bit + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=5 --shares=10 --name=$DELEGATOR --pubkey=$PK2) + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u + checkCandidate $PK2 "15" + checkDelegatorBond $DELA_ADDR $PK2 "5" + + # attempt to unbond more shares than exist + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=10 --name=$DELEGATOR --pubkey=$PK2 2>/dev/null) + if [ $? == 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u + checkCandidate $PK2 "15" + checkDelegatorBond $DELA_ADDR $PK2 "5" + + # unbond entirely from the delegator + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=5 --name=$DELEGATOR --pubkey=$PK2) + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u + checkCandidate $PK2 "10" + checkDelegatorBondEmpty $DELA_ADDR $PK2 + + # unbond a bit from the owner + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=2 --shares=5 --name=$POOR --pubkey=$PK2) + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u + checkCandidate $PK2 "5" + checkDelegatorBond $CAND_ADDR $PK2 "5" + + # attempt to unbond more shares than exist + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=10 --name=$POOR --pubkey=$PK2 2>/dev/null) + if [ $? == 0 ]; then return 1; fi + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u + checkCandidate $PK2 "5" + checkDelegatorBond $CAND_ADDR $PK2 "5" + + # unbond entirely from the validator + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=5 --name=$POOR --pubkey=$PK2) + TX_HEIGHT=$(echo $TX | jq .height) + set +u ; checkAccount $CAND_ADDR "992" $TX_HEIGHT ; set -u + checkCandidateEmpty $PK2 + checkDelegatorBondEmpty $CAND_ADDR $PK2 +} + +# Load common then run these tests with shunit2! +CLI_DIR=$GOPATH/src/github.com/cosmos/gaia/vendor/github.com/cosmos/cosmos-sdk/tests/cli + +. $CLI_DIR/common.sh +. $CLI_DIR/shunit2 diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go new file mode 100644 index 0000000000..2528d07d10 --- /dev/null +++ b/x/stake/commands/query.go @@ -0,0 +1,136 @@ +package commands + +import ( + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + crypto "github.com/tendermint/go-crypto" + + "github.com/cosmos/cosmos-sdk/client/commands" + "github.com/cosmos/cosmos-sdk/client/commands/query" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/stack" + "github.com/cosmos/gaia/modules/stake" +) + +//nolint +var ( + CmdQueryCandidates = &cobra.Command{ + Use: "candidates", + Short: "Query for the set of validator-candidates pubkeys", + RunE: cmdQueryCandidates, + } + + CmdQueryCandidate = &cobra.Command{ + Use: "candidate", + Short: "Query a validator-candidate account", + RunE: cmdQueryCandidate, + } + + CmdQueryDelegatorBond = &cobra.Command{ + Use: "delegator-bond", + Short: "Query a delegators bond based on address and candidate pubkey", + RunE: cmdQueryDelegatorBond, + } + + CmdQueryDelegatorCandidates = &cobra.Command{ + Use: "delegator-candidates", + RunE: cmdQueryDelegatorCandidates, + Short: "Query all delegators candidates' pubkeys based on address", + } + + FlagDelegatorAddress = "delegator-address" +) + +func init() { + //Add Flags + fsPk := flag.NewFlagSet("", flag.ContinueOnError) + fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") + fsAddr := flag.NewFlagSet("", flag.ContinueOnError) + fsAddr.String(FlagDelegatorAddress, "", "Delegator Hex Address") + + CmdQueryCandidate.Flags().AddFlagSet(fsPk) + CmdQueryDelegatorBond.Flags().AddFlagSet(fsPk) + CmdQueryDelegatorBond.Flags().AddFlagSet(fsAddr) + CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr) +} + +func cmdQueryCandidates(cmd *cobra.Command, args []string) error { + + var pks []crypto.PubKey + + prove := !viper.GetBool(commands.FlagTrustNode) + key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) + height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(pks, height) +} + +func cmdQueryCandidate(cmd *cobra.Command, args []string) error { + + var candidate stake.Candidate + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + prove := !viper.GetBool(commands.FlagTrustNode) + key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) + height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(candidate, height) +} + +func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error { + + var bond stake.DelegatorBond + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + delegatorAddr := viper.GetString(FlagDelegatorAddress) + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + return err + } + delegator = coin.ChainAddr(delegator) + + prove := !viper.GetBool(commands.FlagTrustNode) + key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) + height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(bond, height) +} + +func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error { + + delegatorAddr := viper.GetString(FlagDelegatorAddress) + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + return err + } + delegator = coin.ChainAddr(delegator) + + prove := !viper.GetBool(commands.FlagTrustNode) + key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) + var candidates []crypto.PubKey + height, err := query.GetParsed(key, &candidates, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(candidates, height) +} diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go new file mode 100644 index 0000000000..e14ac8cec1 --- /dev/null +++ b/x/stake/commands/tx.go @@ -0,0 +1,195 @@ +package commands + +import ( + "encoding/hex" + "fmt" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/rational" + + txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" + "github.com/cosmos/cosmos-sdk/modules/coin" + + "github.com/cosmos/gaia/modules/stake" +) + +// nolint +const ( + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagShares = "shares" + + FlagMoniker = "moniker" + FlagIdentity = "keybase-sig" + FlagWebsite = "website" + FlagDetails = "details" +) + +// nolint +var ( + CmdDeclareCandidacy = &cobra.Command{ + Use: "declare-candidacy", + Short: "create new validator-candidate account and delegate some coins to it", + RunE: cmdDeclareCandidacy, + } + CmdEditCandidacy = &cobra.Command{ + Use: "edit-candidacy", + Short: "edit and existing validator-candidate account", + RunE: cmdEditCandidacy, + } + CmdDelegate = &cobra.Command{ + Use: "delegate", + Short: "delegate coins to an existing validator/candidate", + RunE: cmdDelegate, + } + CmdUnbond = &cobra.Command{ + Use: "unbond", + Short: "unbond coins from a validator/candidate", + RunE: cmdUnbond, + } +) + +func init() { + + // define the flags + fsPk := flag.NewFlagSet("", flag.ContinueOnError) + fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") + + fsAmount := flag.NewFlagSet("", flag.ContinueOnError) + fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond") + + fsShares := flag.NewFlagSet("", flag.ContinueOnError) + fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") + + fsCandidate := flag.NewFlagSet("", flag.ContinueOnError) + fsCandidate.String(FlagMoniker, "", "validator-candidate name") + fsCandidate.String(FlagIdentity, "", "optional keybase signature") + fsCandidate.String(FlagWebsite, "", "optional website") + fsCandidate.String(FlagDetails, "", "optional detailed description space") + + // add the flags + CmdDelegate.Flags().AddFlagSet(fsPk) + CmdDelegate.Flags().AddFlagSet(fsAmount) + + CmdUnbond.Flags().AddFlagSet(fsPk) + CmdUnbond.Flags().AddFlagSet(fsShares) + + CmdDeclareCandidacy.Flags().AddFlagSet(fsPk) + CmdDeclareCandidacy.Flags().AddFlagSet(fsAmount) + CmdDeclareCandidacy.Flags().AddFlagSet(fsCandidate) + + CmdEditCandidacy.Flags().AddFlagSet(fsPk) + CmdEditCandidacy.Flags().AddFlagSet(fsCandidate) +} + +func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { + amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + if viper.GetString(FlagMoniker) == "" { + return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") + } + + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + + tx := stake.NewTxDeclareCandidacy(amount, pk, description) + return txcmd.DoTx(tx) +} + +func cmdEditCandidacy(cmd *cobra.Command, args []string) error { + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + + tx := stake.NewTxEditCandidacy(pk, description) + return txcmd.DoTx(tx) +} + +func cmdDelegate(cmd *cobra.Command, args []string) error { + amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + tx := stake.NewTxDelegate(amount, pk) + return txcmd.DoTx(tx) +} + +func cmdUnbond(cmd *cobra.Command, args []string) error { + + // TODO once go-wire refactored the shares can be broadcast as a Rat instead of a string + + // check the shares before broadcasting + sharesStr := viper.GetString(FlagShares) + var shares rational.Rat + if sharesStr != "MAX" { + var err error + shares, err = rational.NewFromDecimal(sharesStr) + if err != nil { + return err + } + if !shares.GT(rational.Zero) { + return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") + } + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + tx := stake.NewTxUnbond(sharesStr, pk) + return txcmd.DoTx(tx) +} + +// GetPubKey - create the pubkey from a pubkey string +func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { + + if len(pubKeyStr) == 0 { + err = fmt.Errorf("must use --pubkey flag") + return + } + if len(pubKeyStr) != 64 { //if len(pkBytes) != 32 { + err = fmt.Errorf("pubkey must be Ed25519 hex encoded string which is 64 characters long") + return + } + var pkBytes []byte + pkBytes, err = hex.DecodeString(pubKeyStr) + if err != nil { + return + } + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + pk = pkEd.Wrap() + return +} diff --git a/x/stake/errors.go b/x/stake/errors.go new file mode 100644 index 0000000000..900077b125 --- /dev/null +++ b/x/stake/errors.go @@ -0,0 +1,53 @@ +// nolint +package stake + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/errors" +) + +var ( + errCandidateEmpty = fmt.Errorf("Cannot bond to an empty candidate") + errBadBondingDenom = fmt.Errorf("Invalid coin denomination") + errBadBondingAmount = fmt.Errorf("Amount must be > 0") + errNoBondingAcct = fmt.Errorf("No bond account for this (address, validator) pair") + errCommissionNegative = fmt.Errorf("Commission must be positive") + errCommissionHuge = fmt.Errorf("Commission cannot be more than 100%") + + errBadValidatorAddr = fmt.Errorf("Validator does not exist for that address") + errCandidateExistsAddr = fmt.Errorf("Candidate already exist, cannot re-declare candidacy") + errMissingSignature = fmt.Errorf("Missing signature") + errBondNotNominated = fmt.Errorf("Cannot bond to non-nominated account") + errNoCandidateForAddress = fmt.Errorf("Validator does not exist for that address") + errNoDelegatorForAddress = fmt.Errorf("Delegator does not contain validator bond") + errInsufficientFunds = fmt.Errorf("Insufficient bond shares") + errBadRemoveValidator = fmt.Errorf("Error removing validator") + + invalidInput = errors.CodeTypeBaseInvalidInput +) + +func ErrBadValidatorAddr() error { + return errors.WithCode(errBadValidatorAddr, errors.CodeTypeBaseUnknownAddress) +} +func ErrCandidateExistsAddr() error { + return errors.WithCode(errCandidateExistsAddr, errors.CodeTypeBaseInvalidInput) +} +func ErrMissingSignature() error { + return errors.WithCode(errMissingSignature, errors.CodeTypeUnauthorized) +} +func ErrBondNotNominated() error { + return errors.WithCode(errBondNotNominated, errors.CodeTypeBaseInvalidOutput) +} +func ErrNoCandidateForAddress() error { + return errors.WithCode(errNoCandidateForAddress, errors.CodeTypeBaseUnknownAddress) +} +func ErrNoDelegatorForAddress() error { + return errors.WithCode(errNoDelegatorForAddress, errors.CodeTypeBaseInvalidInput) +} +func ErrInsufficientFunds() error { + return errors.WithCode(errInsufficientFunds, errors.CodeTypeBaseInvalidInput) +} +func ErrBadRemoveValidator() error { + return errors.WithCode(errBadRemoveValidator, errors.CodeTypeInternalErr) +} diff --git a/x/stake/handler.go b/x/stake/handler.go new file mode 100644 index 0000000000..c60b408017 --- /dev/null +++ b/x/stake/handler.go @@ -0,0 +1,529 @@ +package stake + +import ( + "fmt" + "strconv" + + "github.com/spf13/viper" + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tmlibs/rational" + + "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/errors" + "github.com/cosmos/cosmos-sdk/modules/auth" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/stack" + "github.com/cosmos/cosmos-sdk/state" +) + +// nolint +const stakingModuleName = "stake" + +// Name is the name of the modules. +func Name() string { + return stakingModuleName +} + +//_______________________________________________________________________ + +// DelegatedProofOfStake - interface to enforce delegation stake +type delegatedProofOfStake interface { + declareCandidacy(TxDeclareCandidacy) error + editCandidacy(TxEditCandidacy) error + delegate(TxDelegate) error + unbond(TxUnbond) error +} + +type coinSend interface { + transferFn(sender, receiver sdk.Actor, coins coin.Coins) error +} + +//_______________________________________________________________________ + +// Handler - the transaction processing handler +type Handler struct { + stack.PassInitValidate +} + +var _ stack.Dispatchable = Handler{} // enforce interface at compile time + +// NewHandler returns a new Handler with the default Params +func NewHandler() Handler { + return Handler{} +} + +// Name - return stake namespace +func (Handler) Name() string { + return stakingModuleName +} + +// AssertDispatcher - placeholder for stack.Dispatchable +func (Handler) AssertDispatcher() {} + +// InitState - set genesis parameters for staking +func (h Handler) InitState(l log.Logger, store state.SimpleDB, + module, key, value string, cb sdk.InitStater) (log string, err error) { + return "", h.initState(module, key, value, store) +} + +// separated for testing +func (Handler) initState(module, key, value string, store state.SimpleDB) error { + if module != stakingModuleName { + return errors.ErrUnknownModule(module) + } + + params := loadParams(store) + switch key { + case "allowed_bond_denom": + params.AllowedBondDenom = value + case "max_vals", + "gas_bond", + "gas_unbond": + + // TODO: enforce non-negative integers in input + i, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("input must be integer, Error: %v", err.Error()) + } + + switch key { + case "max_vals": + params.MaxVals = uint16(i) + case "gas_bond": + params.GasDelegate = int64(i) + case "gas_unbound": + params.GasUnbond = int64(i) + } + default: + return errors.ErrUnknownKey(key) + } + + saveParams(store, params) + return nil +} + +// CheckTx checks if the tx is properly structured +func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB, + tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) { + + err = tx.ValidateBasic() + if err != nil { + return res, err + } + + // get the sender + sender, err := getTxSender(ctx) + if err != nil { + return res, err + } + + params := loadParams(store) + + // create the new checker object to + checker := check{ + store: store, + sender: sender, + } + + // return the fee for each tx type + switch txInner := tx.Unwrap().(type) { + case TxDeclareCandidacy: + return sdk.NewCheck(params.GasDeclareCandidacy, ""), + checker.declareCandidacy(txInner) + case TxEditCandidacy: + return sdk.NewCheck(params.GasEditCandidacy, ""), + checker.editCandidacy(txInner) + case TxDelegate: + return sdk.NewCheck(params.GasDelegate, ""), + checker.delegate(txInner) + case TxUnbond: + return sdk.NewCheck(params.GasUnbond, ""), + checker.unbond(txInner) + } + + return res, errors.ErrUnknownTxType(tx) +} + +// DeliverTx executes the tx if valid +func (h Handler) DeliverTx(ctx sdk.Context, store state.SimpleDB, + tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) { + + // TODO: remove redundancy + // also we don't need to check the res - gas is already deducted in sdk + _, err = h.CheckTx(ctx, store, tx, nil) + if err != nil { + return + } + + sender, err := getTxSender(ctx) + if err != nil { + return + } + + params := loadParams(store) + deliverer := deliver{ + store: store, + sender: sender, + params: params, + transfer: coinSender{ + store: store, + dispatch: dispatch, + ctx: ctx, + }.transferFn, + } + + // Run the transaction + switch _tx := tx.Unwrap().(type) { + case TxDeclareCandidacy: + res.GasUsed = params.GasDeclareCandidacy + return res, deliverer.declareCandidacy(_tx) + case TxEditCandidacy: + res.GasUsed = params.GasEditCandidacy + return res, deliverer.editCandidacy(_tx) + case TxDelegate: + res.GasUsed = params.GasDelegate + return res, deliverer.delegate(_tx) + case TxUnbond: + //context with hold account permissions + params := loadParams(store) + res.GasUsed = params.GasUnbond + ctx2 := ctx.WithPermissions(params.HoldBonded) + deliverer.transfer = coinSender{ + store: store, + dispatch: dispatch, + ctx: ctx2, + }.transferFn + return res, deliverer.unbond(_tx) + } + return +} + +// get the sender from the ctx and ensure it matches the tx pubkey +func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) { + senders := ctx.GetPermissions("", auth.NameSigs) + if len(senders) != 1 { + return sender, ErrMissingSignature() + } + return senders[0], nil +} + +//_______________________________________________________________________ + +type coinSender struct { + store state.SimpleDB + dispatch sdk.Deliver + ctx sdk.Context +} + +var _ coinSend = coinSender{} // enforce interface at compile time + +func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { + send := coin.NewSendOneTx(sender, receiver, coins) + + // If the deduction fails (too high), abort the command + _, err := c.dispatch.DeliverTx(c.ctx, c.store, send) + return err +} + +//_____________________________________________________________________ + +type check struct { + store state.SimpleDB + sender sdk.Actor +} + +var _ delegatedProofOfStake = check{} // enforce interface at compile time + +func (c check) declareCandidacy(tx TxDeclareCandidacy) error { + + // check to see if the pubkey or sender has been registered before + candidate := loadCandidate(c.store, tx.PubKey) + if candidate != nil { + return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ + " PubKey %v already registered with %v candidate address", + candidate.PubKey, candidate.Owner) + } + + return checkDenom(tx.BondUpdate, c.store) +} + +func (c check) editCandidacy(tx TxEditCandidacy) error { + + // candidate must already be registered + candidate := loadCandidate(c.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + return nil +} + +func (c check) delegate(tx TxDelegate) error { + + candidate := loadCandidate(c.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + return checkDenom(tx.BondUpdate, c.store) +} + +func (c check) unbond(tx TxUnbond) error { + + // check if bond has any shares in it unbond + bond := loadDelegatorBond(c.store, c.sender, tx.PubKey) + sharesStr := viper.GetString(tx.Shares) + if bond.Shares.LT(rational.Zero) { // bond shares < tx shares + return fmt.Errorf("no shares in account to unbond") + } + + // if shares set to maximum shares then we're good + if sharesStr == "MAX" { + return nil + } + + // test getting rational number from decimal provided + shares, err := rational.NewFromDecimal(sharesStr) + if err != nil { + return err + } + + // test that there are enough shares to unbond + if bond.Shares.LT(shares) { + return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", + bond.Shares, tx.Shares) + } + return nil +} + +func checkDenom(tx BondUpdate, store state.SimpleDB) error { + if tx.Bond.Denom != loadParams(store).AllowedBondDenom { + return fmt.Errorf("Invalid coin denomination") + } + return nil +} + +//_____________________________________________________________________ + +type deliver struct { + store state.SimpleDB + sender sdk.Actor + params Params + gs *GlobalState + transfer transferFn +} + +type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error + +var _ delegatedProofOfStake = deliver{} // enforce interface at compile time + +//_____________________________________________________________________ +// deliver helper functions + +// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK + +// move a candidates asset pool from bonded to unbonded pool +func (d deliver) bondedToUnbondedPool(candidate *Candidate) error { + + // replace bonded shares with unbonded shares + tokens := d.gs.removeSharesBonded(candidate.Assets) + candidate.Assets = d.gs.addTokensUnbonded(tokens) + candidate.Status = Unbonded + + return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded, + coin.Coins{{d.params.AllowedBondDenom, tokens}}) +} + +// move a candidates asset pool from unbonded to bonded pool +func (d deliver) unbondedToBondedPool(candidate *Candidate) error { + + // replace bonded shares with unbonded shares + tokens := d.gs.removeSharesUnbonded(candidate.Assets) + candidate.Assets = d.gs.addTokensBonded(tokens) + candidate.Status = Bonded + + return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded, + coin.Coins{{d.params.AllowedBondDenom, tokens}}) +} + +//_____________________________________________________________________ + +// These functions assume everything has been authenticated, +// now we just perform action and save +func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { + + // create and save the empty candidate + bond := loadCandidate(d.store, tx.PubKey) + if bond != nil { + return ErrCandidateExistsAddr() + } + candidate := NewCandidate(tx.PubKey, d.sender, tx.Description) + saveCandidate(d.store, candidate) + + // move coins from the d.sender account to a (self-bond) delegator account + // the candidate account and global shares are updated within here + txDelegate := TxDelegate{tx.BondUpdate} + return d.delegateWithCandidate(txDelegate, candidate) +} + +func (d deliver) editCandidacy(tx TxEditCandidacy) error { + + // Get the pubKey bond account + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrBondNotNominated() + } + if candidate.Status == Unbonded { //candidate has been withdrawn + return ErrBondNotNominated() + } + + //check and edit any of the editable terms + if tx.Description.Moniker != "" { + candidate.Description.Moniker = tx.Description.Moniker + } + if tx.Description.Identity != "" { + candidate.Description.Identity = tx.Description.Identity + } + if tx.Description.Website != "" { + candidate.Description.Website = tx.Description.Website + } + if tx.Description.Details != "" { + candidate.Description.Details = tx.Description.Details + } + + saveCandidate(d.store, candidate) + return nil +} + +func (d deliver) delegate(tx TxDelegate) error { + // Get the pubKey bond account + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrBondNotNominated() + } + return d.delegateWithCandidate(tx, candidate) +} + +func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { + + if candidate.Status == Revoked { //candidate has been withdrawn + return ErrBondNotNominated() + } + + var poolAccount sdk.Actor + if candidate.Status == Bonded { + poolAccount = d.params.HoldBonded + } else { + poolAccount = d.params.HoldUnbonded + } + + // TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK + // Move coins from the delegator account to the bonded pool account + err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond}) + if err != nil { + return err + } + + // Get or create the delegator bond + bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + if bond == nil { + bond = &DelegatorBond{ + PubKey: tx.PubKey, + Shares: rational.Zero, + } + } + + // Account new shares, save + bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs)) + saveCandidate(d.store, candidate) + saveDelegatorBond(d.store, d.sender, bond) + saveGlobalState(d.store, d.gs) + return nil +} + +func (d deliver) unbond(tx TxUnbond) error { + + // get delegator bond + bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + if bond == nil { + return ErrNoDelegatorForAddress() + } + + // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) + var shares rational.Rat + if tx.Shares == "MAX" { + shares = bond.Shares + } else { + var err error + shares, err = rational.NewFromDecimal(tx.Shares) + if err != nil { + return err + } + } + + // subtract bond tokens from delegator bond + if bond.Shares.LT(shares) { // bond shares < tx shares + return ErrInsufficientFunds() + } + bond.Shares = bond.Shares.Sub(shares) + + // get pubKey candidate + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrNoCandidateForAddress() + } + + revokeCandidacy := false + if bond.Shares.IsZero() { + + // if the bond is the owner of the candidate then + // trigger a revoke candidacy + if d.sender.Equals(candidate.Owner) && + candidate.Status != Revoked { + revokeCandidacy = true + } + + // remove the bond + removeDelegatorBond(d.store, d.sender, tx.PubKey) + } else { + saveDelegatorBond(d.store, d.sender, bond) + } + + // transfer coins back to account + var poolAccount sdk.Actor + if candidate.Status == Bonded { + poolAccount = d.params.HoldBonded + } else { + poolAccount = d.params.HoldUnbonded + } + + returnCoins := candidate.removeShares(shares, d.gs) + err := d.transfer(poolAccount, d.sender, + coin.Coins{{d.params.AllowedBondDenom, returnCoins}}) + if err != nil { + return err + } + + // lastly if an revoke candidate if necessary + if revokeCandidacy { + + // change the share types to unbonded if they were not already + if candidate.Status == Bonded { + err = d.bondedToUnbondedPool(candidate) + if err != nil { + return err + } + } + + // lastly update the status + candidate.Status = Revoked + } + + // deduct shares from the candidate and save + if candidate.Liabilities.IsZero() { + removeCandidate(d.store, tx.PubKey) + } else { + saveCandidate(d.store, candidate) + } + + saveGlobalState(d.store, d.gs) + return nil +} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go new file mode 100644 index 0000000000..690e7710cf --- /dev/null +++ b/x/stake/handler_test.go @@ -0,0 +1,336 @@ +package stake + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/rational" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/state" +) + +//______________________________________________________________________ + +// dummy transfer functions, represents store operations on account balances + +type testCoinSender struct { + store map[string]int64 +} + +var _ coinSend = testCoinSender{} // enforce interface at compile time + +func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { + c.store[string(sender.Address)] -= coins[0].Amount + c.store[string(receiver.Address)] += coins[0].Amount + return nil +} + +//______________________________________________________________________ + +func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) { + accStore := map[string]int64{} + senders := newActors(n) + for _, sender := range senders { + accStore[string(sender.Address)] = amount + } + return senders, accStore +} + +func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy { + return TxDeclareCandidacy{ + BondUpdate{ + PubKey: pubKey, + Bond: coin.Coin{"fermion", amt}, + }, + Description{}, + } +} + +func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate { + return TxDelegate{BondUpdate{ + PubKey: pubKey, + Bond: coin.Coin{"fermion", amt}, + }} +} + +func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond { + return TxUnbond{ + PubKey: pubKey, + Shares: shares, + } +} + +func paramsNoInflation() Params { + return Params{ + HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), + HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), + InflationRateChange: rational.Zero, + InflationMax: rational.Zero, + InflationMin: rational.Zero, + GoalBonded: rational.New(67, 100), + MaxVals: 100, + AllowedBondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +func newDeliver(sender sdk.Actor, accStore map[string]int64) deliver { + store := state.NewMemKVStore() + params := paramsNoInflation() + saveParams(store, params) + return deliver{ + store: store, + sender: sender, + params: params, + gs: loadGlobalState(store), + transfer: testCoinSender{accStore}.transferFn, + } +} + +func TestDuplicatesTxDeclareCandidacy(t *testing.T) { + assert := assert.New(t) + senders, accStore := initAccounts(2, 1000) // for accounts + + deliverer := newDeliver(senders[0], accStore) + checker := check{ + store: deliverer.store, + sender: senders[0], + } + + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(got, "expected no error on runTxDeclareCandidacy") + + // one sender can bond to two different pubKeys + txDeclareCandidacy.PubKey = pks[1] + err := checker.declareCandidacy(txDeclareCandidacy) + assert.Nil(err, "didn't expected error on checkTx") + + // two senders cant bond to the same pubkey + checker.sender = senders[1] + txDeclareCandidacy.PubKey = pks[0] + err = checker.declareCandidacy(txDeclareCandidacy) + assert.NotNil(err, "expected error on checkTx") +} + +func TestIncrementsTxDelegate(t *testing.T) { + assert := assert.New(t) + initSender := int64(1000) + senders, accStore := initAccounts(1, initSender) // for accounts + deliverer := newDeliver(senders[0], accStore) + + // first declare candidacy + bondAmount := int64(10) + txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(got, "expected declare candidacy tx to be ok, got %v", got) + expectedBond := bondAmount // 1 since we send 1 at the start of loop, + + // just send the same txbond multiple times + holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates + txDelegate := newTxDelegate(bondAmount, pks[0]) + for i := 0; i < 5; i++ { + got := deliverer.delegate(txDelegate) + assert.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the accounts and the bond account have the appropriate values + candidates := loadCandidates(deliverer.store) + expectedBond += bondAmount + expectedSender := initSender - expectedBond + gotBonded := candidates[0].Liabilities.Evaluate() + gotHolder := accStore[string(holder.Address)] + gotSender := accStore[string(deliverer.sender.Address)] + assert.Equal(expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) + assert.Equal(expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder) + assert.Equal(expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) + } +} + +func TestIncrementsTxUnbond(t *testing.T) { + assert := assert.New(t) + initSender := int64(0) + senders, accStore := initAccounts(1, initSender) // for accounts + deliverer := newDeliver(senders[0], accStore) + + // set initial bond + initBond := int64(1000) + accStore[string(deliverer.sender.Address)] = initBond + got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0])) + assert.NoError(got, "expected initial bond tx to be ok, got %v", got) + + // just send the same txunbond multiple times + holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded + + // XXX use decimals here + unbondShares, unbondSharesStr := int64(10), "10" + txUndelegate := newTxUnbond(unbondSharesStr, pks[0]) + nUnbonds := 5 + for i := 0; i < nUnbonds; i++ { + got := deliverer.unbond(txUndelegate) + assert.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the accounts and the bond account have the appropriate values + candidates := loadCandidates(deliverer.store) + 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() + gotHolder := accStore[string(holder.Address)] + gotSender := accStore[string(deliverer.sender.Address)] + + assert.Equal(expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) + assert.Equal(expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder) + assert.Equal(expectedSender, gotSender, "%v, %v", expectedSender, gotSender) + } + + // 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)) + txUndelegate := newTxUnbond(unbondShares, pks[0]) + got = deliverer.unbond(txUndelegate) + assert.Error(got, "expected unbond tx to fail") + } + + leftBonded := initBond - unbondShares*int64(nUnbonds) + + // should be unable to unbond one more than we have + txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) + got = deliverer.unbond(txUndelegate) + assert.Error(got, "expected unbond tx to fail") + + // should be able to unbond just what we have + txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0]) + got = deliverer.unbond(txUndelegate) + assert.NoError(got, "expected unbond tx to pass") +} + +func TestMultipleTxDeclareCandidacy(t *testing.T) { + assert := assert.New(t) + initSender := int64(1000) + senders, accStore := initAccounts(3, initSender) + pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]} + deliverer := newDeliver(senders[0], accStore) + + // bond them all + for i, sender := range senders { + txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i]) + deliverer.sender = sender + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is bonded + candidates := loadCandidates(deliverer.store) + val := candidates[i] + balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10 + assert.Equal(i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) + assert.Equal(10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) + assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } + + // unbond them all + for i, sender := range senders { + candidatePre := loadCandidate(deliverer.store, pubKeys[i]) + txUndelegate := newTxUnbond("10", pubKeys[i]) + deliverer.sender = sender + got := deliverer.unbond(txUndelegate) + assert.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is unbonded + candidates := loadCandidates(deliverer.store) + assert.Equal(len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates)) + + candidatePost := loadCandidate(deliverer.store, pubKeys[i]) + balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender + assert.Nil(candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) + assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } +} + +func TestMultipleTxDelegate(t *testing.T) { + assert, require := assert.New(t), require.New(t) + accounts, accStore := initAccounts(3, 1000) + sender, delegators := accounts[0], accounts[1:] + deliverer := newDeliver(sender, accStore) + + //first make a candidate + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + require.NoError(got, "expected tx to be ok, got %v", got) + + // delegate multiple parties + for i, delegator := range delegators { + txDelegate := newTxDelegate(10, pks[0]) + deliverer.sender = delegator + got := deliverer.delegate(txDelegate) + require.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is bonded + bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + assert.NotNil(bond, "expected delegatee bond %d to exist", bond) + } + + // unbond them all + for i, delegator := range delegators { + txUndelegate := newTxUnbond("10", pks[0]) + deliverer.sender = delegator + got := deliverer.unbond(txUndelegate) + require.NoError(got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is unbonded + bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + assert.Nil(bond, "expected delegatee bond %d to be nil", bond) + } +} + +func TestVoidCandidacy(t *testing.T) { + assert, require := assert.New(t), require.New(t) + accounts, accStore := initAccounts(2, 1000) // for accounts + sender, delegator := accounts[0], accounts[1] + deliverer := newDeliver(sender, accStore) + + // create the candidate + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + require.NoError(got, "expected no error on runTxDeclareCandidacy") + + // bond a delegator + txDelegate := newTxDelegate(10, pks[0]) + deliverer.sender = delegator + got = deliverer.delegate(txDelegate) + require.NoError(got, "expected ok, got %v", got) + + // unbond the candidates bond portion + txUndelegate := newTxUnbond("10", pks[0]) + deliverer.sender = sender + got = deliverer.unbond(txUndelegate) + require.NoError(got, "expected no error on runTxDeclareCandidacy") + + // test that this pubkey cannot yet be bonded too + deliverer.sender = delegator + got = deliverer.delegate(txDelegate) + assert.Error(got, "expected error, got %v", got) + + // test that the delegator can still withdraw their bonds + got = deliverer.unbond(txUndelegate) + require.NoError(got, "expected no error on runTxDeclareCandidacy") + + // verify that the pubkey can now be reused + got = deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(got, "expected ok, got %v", got) + +} diff --git a/x/stake/rest/query.go b/x/stake/rest/query.go new file mode 100644 index 0000000000..48e54cfe81 --- /dev/null +++ b/x/stake/rest/query.go @@ -0,0 +1,188 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/gorilla/mux" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/commands" + "github.com/cosmos/cosmos-sdk/client/commands/query" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/stack" + + "github.com/cosmos/gaia/modules/stake" + scmds "github.com/cosmos/gaia/modules/stake/commands" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/common" +) + +// RegisterQueryCandidate is a mux.Router handler that exposes GET +// method access on route /query/stake/candidate/{pubkey} to query a candidate +func RegisterQueryCandidate(r *mux.Router) error { + r.HandleFunc("/query/stake/candidate/{pubkey}", queryCandidate).Methods("GET") + return nil +} + +// RegisterQueryCandidates is a mux.Router handler that exposes GET +// method access on route /query/stake/candidate to query the group of all candidates +func RegisterQueryCandidates(r *mux.Router) error { + r.HandleFunc("/query/stake/candidates", queryCandidates).Methods("GET") + return nil +} + +// RegisterQueryDelegatorBond is a mux.Router handler that exposes GET +// method access on route /query/stake/candidate/{pubkey} to query a candidate +func RegisterQueryDelegatorBond(r *mux.Router) error { + r.HandleFunc("/query/stake/delegator/{address}/{pubkey}", queryDelegatorBond).Methods("GET") + return nil +} + +// RegisterQueryDelegatorCandidates is a mux.Router handler that exposes GET +// method access on route /query/stake/candidate to query the group of all candidates +func RegisterQueryDelegatorCandidates(r *mux.Router) error { + r.HandleFunc("/query/stake/delegator_candidates/{address}", queryDelegatorCandidates).Methods("GET") + return nil +} + +//--------------------------------------------------------------------- + +// queryCandidate is the HTTP handlerfunc to query a candidate +// it expects a query string +func queryCandidate(w http.ResponseWriter, r *http.Request) { + + // get the arguments object + args := mux.Vars(r) + prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server + + // get the pubkey + pkArg := args["pubkey"] + pk, err := scmds.GetPubKey(pkArg) + if err != nil { + common.WriteError(w, err) + return + } + + // get the candidate + var candidate stake.Candidate + key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) + height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) + if client.IsNoDataErr(err) { + err := fmt.Errorf("candidate bytes are empty for pubkey: %q", pkArg) + common.WriteError(w, err) + return + } else if err != nil { + common.WriteError(w, err) + return + } + + // write the output + err = query.FoutputProof(w, candidate, height) + if err != nil { + common.WriteError(w, err) + } +} + +// queryCandidates is the HTTP handlerfunc to query the group of all candidates +func queryCandidates(w http.ResponseWriter, r *http.Request) { + + var pks []crypto.PubKey + + prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server + key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) + height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) + if err != nil { + common.WriteError(w, err) + return + } + + err = query.FoutputProof(w, pks, height) + if err != nil { + common.WriteError(w, err) + } +} + +// queryDelegatorBond is the HTTP handlerfunc to query a delegator bond it +// expects a query string +func queryDelegatorBond(w http.ResponseWriter, r *http.Request) { + + // get the arguments object + args := mux.Vars(r) + prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server + + // get the pubkey + pkArg := args["pubkey"] + pk, err := scmds.GetPubKey(pkArg) + if err != nil { + common.WriteError(w, err) + return + } + + // get the delegator actor + delegatorAddr := args["address"] + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + common.WriteError(w, err) + return + } + delegator = coin.ChainAddr(delegator) + + // get the bond + var bond stake.DelegatorBond + key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) + height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) + if client.IsNoDataErr(err) { + err := fmt.Errorf("bond bytes are empty for pubkey: %q, address: %q", pkArg, delegatorAddr) + common.WriteError(w, err) + return + } else if err != nil { + common.WriteError(w, err) + return + } + + // write the output + err = query.FoutputProof(w, bond, height) + if err != nil { + common.WriteError(w, err) + } +} + +// queryDelegatorCandidates is the HTTP handlerfunc to query a delegator bond it +// expects a query string +func queryDelegatorCandidates(w http.ResponseWriter, r *http.Request) { + + // get the arguments object + args := mux.Vars(r) + prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server + + // get the delegator actor + delegatorAddr := args["address"] + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + common.WriteError(w, err) + return + } + delegator = coin.ChainAddr(delegator) + + // get the bond + var bond stake.DelegatorBond + key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) + height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) + if client.IsNoDataErr(err) { + err := fmt.Errorf("bond bytes are empty for address: %q", delegatorAddr) + common.WriteError(w, err) + return + } else if err != nil { + common.WriteError(w, err) + return + } + + // write the output + err = query.FoutputProof(w, bond, height) + if err != nil { + common.WriteError(w, err) + } +} diff --git a/x/stake/rest/tx.go b/x/stake/rest/tx.go new file mode 100644 index 0000000000..090ea5abbc --- /dev/null +++ b/x/stake/rest/tx.go @@ -0,0 +1,159 @@ +package rest + +import ( + "net/http" + "strings" + + "github.com/gorilla/mux" + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/common" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/client/commands" + "github.com/cosmos/cosmos-sdk/modules/auth" + "github.com/cosmos/cosmos-sdk/modules/base" + "github.com/cosmos/cosmos-sdk/modules/coin" + "github.com/cosmos/cosmos-sdk/modules/fee" + "github.com/cosmos/cosmos-sdk/modules/nonce" + "github.com/cosmos/gaia/modules/stake" +) + +const ( + //parameters used in urls + paramPubKey = "pubkey" + paramAmount = "amount" + paramShares = "shares" + + paramName = "name" + paramKeybase = "keybase" + paramWebsite = "website" + paramDetails = "details" +) + +type delegateInput struct { + Fees *coin.Coin `json:"fees"` + Sequence uint32 `json:"sequence"` + + Pubkey crypto.PubKey `json:"pub_key"` + From *sdk.Actor `json:"from"` + Amount coin.Coin `json:"amount"` +} + +type unbondInput struct { + Fees *coin.Coin `json:"fees"` + Sequence uint32 `json:"sequence"` + + Pubkey crypto.PubKey `json:"pub_key"` + From *sdk.Actor `json:"from"` + Shares string `json:"amount"` +} + +// RegisterDelegate is a mux.Router handler that exposes +// POST method access on route /tx/stake/delegate to create a +// transaction for delegate to a candidaate/validator +func RegisterDelegate(r *mux.Router) error { + r.HandleFunc("/build/stake/delegate", delegate).Methods("POST") + return nil +} + +// RegisterUnbond is a mux.Router handler that exposes +// POST method access on route /tx/stake/unbond to create a +// transaction for unbonding delegated coins +func RegisterUnbond(r *mux.Router) error { + r.HandleFunc("/build/stake/unbond", unbond).Methods("POST") + return nil +} + +func prepareDelegateTx(di *delegateInput) sdk.Tx { + tx := stake.NewTxDelegate(di.Amount, di.Pubkey) + // fees are optional + if di.Fees != nil && !di.Fees.IsZero() { + tx = fee.NewFee(tx, *di.Fees, *di.From) + } + // only add the actual signer to the nonce + signers := []sdk.Actor{*di.From} + tx = nonce.NewTx(di.Sequence, signers, tx) + tx = base.NewChainTx(commands.GetChainID(), 0, tx) + + tx = auth.NewSig(tx).Wrap() + return tx +} + +func delegate(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + di := new(delegateInput) + if err := common.ParseRequestAndValidateJSON(r, di); err != nil { + common.WriteError(w, err) + return + } + + var errsList []string + if di.From == nil { + errsList = append(errsList, `"from" cannot be nil`) + } + if di.Sequence <= 0 { + errsList = append(errsList, `"sequence" must be > 0`) + } + if di.Pubkey.Empty() { + errsList = append(errsList, `"pubkey" cannot be empty`) + } + if len(errsList) > 0 { + code := http.StatusBadRequest + err := &common.ErrorResponse{ + Err: strings.Join(errsList, ", "), + Code: code, + } + common.WriteCode(w, err, code) + return + } + + tx := prepareDelegateTx(di) + common.WriteSuccess(w, tx) +} + +func prepareUnbondTx(ui *unbondInput) sdk.Tx { + tx := stake.NewTxUnbond(ui.Shares, ui.Pubkey) + // fees are optional + if ui.Fees != nil && !ui.Fees.IsZero() { + tx = fee.NewFee(tx, *ui.Fees, *ui.From) + } + // only add the actual signer to the nonce + signers := []sdk.Actor{*ui.From} + tx = nonce.NewTx(ui.Sequence, signers, tx) + tx = base.NewChainTx(commands.GetChainID(), 0, tx) + + tx = auth.NewSig(tx).Wrap() + return tx +} + +func unbond(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + ui := new(unbondInput) + if err := common.ParseRequestAndValidateJSON(r, ui); err != nil { + common.WriteError(w, err) + return + } + + var errsList []string + if ui.From == nil { + errsList = append(errsList, `"from" cannot be nil`) + } + if ui.Sequence <= 0 { + errsList = append(errsList, `"sequence" must be > 0`) + } + if ui.Pubkey.Empty() { + errsList = append(errsList, `"pubkey" cannot be empty`) + } + if len(errsList) > 0 { + code := http.StatusBadRequest + err := &common.ErrorResponse{ + Err: strings.Join(errsList, ", "), + Code: code, + } + common.WriteCode(w, err, code) + return + } + + tx := prepareUnbondTx(ui) + common.WriteSuccess(w, tx) +} diff --git a/x/stake/state.go b/x/stake/state.go new file mode 100644 index 0000000000..e037d837b2 --- /dev/null +++ b/x/stake/state.go @@ -0,0 +1,273 @@ +package stake + +import ( + "encoding/json" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" +) + +// nolint +var ( + // Keys for store prefixes + CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys + ParamKey = []byte{0x02} // key for global parameters relating to staking + GlobalStateKey = []byte{0x03} // key for global parameters relating to staking + + // Key prefixes + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond + DelegatorBondsKeyPrefix = []byte{0x06} // prefix for each key to a delegator's bond +) + +// GetCandidateKey - get the key for the candidate with pubKey +func GetCandidateKey(pubKey crypto.PubKey) []byte { + return append(CandidateKeyPrefix, pubKey.Bytes()...) +} + +// GetDelegatorBondKey - get the key for delegator bond with candidate +func GetDelegatorBondKey(delegator sdk.Actor, candidate crypto.PubKey) []byte { + return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) +} + +// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates +func GetDelegatorBondKeyPrefix(delegator sdk.Actor) []byte { + return append(DelegatorBondKeyPrefix, wire.BinaryBytes(&delegator)...) +} + +// GetDelegatorBondsKey - get the key for list of all the delegator's bonds +func GetDelegatorBondsKey(delegator sdk.Actor) []byte { + return append(DelegatorBondsKeyPrefix, wire.BinaryBytes(&delegator)...) +} + +//--------------------------------------------------------------------- + +// Get the active list of all the candidate pubKeys and owners +func loadCandidatesPubKeys(store state.SimpleDB) (pubKeys []crypto.PubKey) { + bytes := store.Get(CandidatesPubKeysKey) + if bytes == nil { + return + } + err := wire.ReadBinaryBytes(bytes, &pubKeys) + if err != nil { + panic(err) + } + return +} +func saveCandidatesPubKeys(store state.SimpleDB, pubKeys []crypto.PubKey) { + b := wire.BinaryBytes(pubKeys) + store.Set(CandidatesPubKeysKey, b) +} + +// loadCandidates - get the active list of all candidates TODO replace with multistore +func loadCandidates(store state.SimpleDB) (candidates Candidates) { + pks := loadCandidatesPubKeys(store) + for _, pk := range pks { + candidates = append(candidates, loadCandidate(store, pk)) + } + return +} + +//--------------------------------------------------------------------- + +// loadCandidate - loads the candidate object for the provided pubkey +func loadCandidate(store state.SimpleDB, pubKey crypto.PubKey) *Candidate { + if pubKey.Empty() { + return nil + } + b := store.Get(GetCandidateKey(pubKey)) + if b == nil { + return nil + } + candidate := new(Candidate) + err := json.Unmarshal(b, candidate) + if err != nil { + panic(err) // This error should never occure big problem if does + } + return candidate +} + +func saveCandidate(store state.SimpleDB, candidate *Candidate) { + + if !store.Has(GetCandidateKey(candidate.PubKey)) { + // TODO to be replaced with iteration in the multistore? + pks := loadCandidatesPubKeys(store) + saveCandidatesPubKeys(store, append(pks, candidate.PubKey)) + } + + b, err := json.Marshal(*candidate) + if err != nil { + panic(err) + } + store.Set(GetCandidateKey(candidate.PubKey), b) +} + +func removeCandidate(store state.SimpleDB, pubKey crypto.PubKey) { + store.Remove(GetCandidateKey(pubKey)) + + // TODO to be replaced with iteration in the multistore? + pks := loadCandidatesPubKeys(store) + for i := range pks { + if pks[i].Equals(pubKey) { + saveCandidatesPubKeys(store, + append(pks[:i], pks[i+1:]...)) + break + } + } +} + +//--------------------------------------------------------------------- + +// load the pubkeys of all candidates a delegator is delegated too +func loadDelegatorCandidates(store state.SimpleDB, + delegator sdk.Actor) (candidates []crypto.PubKey) { + + candidateBytes := store.Get(GetDelegatorBondsKey(delegator)) + if candidateBytes == nil { + return nil + } + + err := wire.ReadBinaryBytes(candidateBytes, &candidates) + if err != nil { + panic(err) + } + return +} + +//--------------------------------------------------------------------- + +func loadDelegatorBond(store state.SimpleDB, + delegator sdk.Actor, candidate crypto.PubKey) *DelegatorBond { + + delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate)) + if delegatorBytes == nil { + return nil + } + + bond := new(DelegatorBond) + err := json.Unmarshal(delegatorBytes, bond) + if err != nil { + panic(err) + } + return bond +} + +func saveDelegatorBond(store state.SimpleDB, delegator sdk.Actor, bond *DelegatorBond) { + + // if a new bond add to the list of bonds + if loadDelegatorBond(store, delegator, bond.PubKey) == nil { + pks := loadDelegatorCandidates(store, delegator) + pks = append(pks, (*bond).PubKey) + b := wire.BinaryBytes(pks) + store.Set(GetDelegatorBondsKey(delegator), b) + } + + // now actually save the bond + b, err := json.Marshal(*bond) + if err != nil { + panic(err) + } + store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) + //updateDelegatorBonds(store, delegator) +} + +func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate crypto.PubKey) { + + // TODO use list queries on multistore to remove iterations here! + // first remove from the list of bonds + pks := loadDelegatorCandidates(store, delegator) + for i, pk := range pks { + if candidate.Equals(pk) { + pks = append(pks[:i], pks[i+1:]...) + } + } + b := wire.BinaryBytes(pks) + store.Set(GetDelegatorBondsKey(delegator), b) + + // now remove the actual bond + store.Remove(GetDelegatorBondKey(delegator, candidate)) + //updateDelegatorBonds(store, delegator) +} + +//func updateDelegatorBonds(store state.SimpleDB, +//delegator sdk.Actor) { + +//var bonds []*DelegatorBond + +//prefix := GetDelegatorBondKeyPrefix(delegator) +//l := len(prefix) +//delegatorsBytes := store.List(prefix, +//append(prefix[:l-1], (prefix[l-1]+1)), loadParams(store).MaxVals) + +//for _, delegatorBytesModel := range delegatorsBytes { +//delegatorBytes := delegatorBytesModel.Value +//if delegatorBytes == nil { +//continue +//} + +//bond := new(DelegatorBond) +//err := wire.ReadBinaryBytes(delegatorBytes, bond) +//if err != nil { +//panic(err) +//} +//bonds = append(bonds, bond) +//} + +//if len(bonds) == 0 { +//store.Remove(GetDelegatorBondsKey(delegator)) +//return +//} + +//b := wire.BinaryBytes(bonds) +//store.Set(GetDelegatorBondsKey(delegator), b) +//} + +//_______________________________________________________________________ + +// load/save the global staking params +func loadParams(store state.SimpleDB) (params Params) { + b := store.Get(ParamKey) + if b == nil { + return defaultParams() + } + + err := json.Unmarshal(b, ¶ms) + if err != nil { + panic(err) // This error should never occure big problem if does + } + + return +} +func saveParams(store state.SimpleDB, params Params) { + b, err := json.Marshal(params) + if err != nil { + panic(err) + } + store.Set(ParamKey, b) +} + +//_______________________________________________________________________ + +// load/save the global staking state +func loadGlobalState(store state.SimpleDB) (gs *GlobalState) { + b := store.Get(GlobalStateKey) + if b == nil { + return initialGlobalState() + } + gs = new(GlobalState) + err := json.Unmarshal(b, gs) + if err != nil { + panic(err) // This error should never occure big problem if does + } + return +} +func saveGlobalState(store state.SimpleDB, gs *GlobalState) { + b, err := json.Marshal(*gs) + if err != nil { + panic(err) + } + store.Set(GlobalStateKey, b) +} diff --git a/x/stake/state_test.go b/x/stake/state_test.go new file mode 100644 index 0000000000..919dfc4c44 --- /dev/null +++ b/x/stake/state_test.go @@ -0,0 +1,116 @@ +package stake + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/auth" + "github.com/cosmos/cosmos-sdk/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tmlibs/rational" +) + +func TestState(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data") + require.Nil(err) + mainLoader := store.NewIAVLStoreLoader(int64(100), 10000, numHistory) + var mainStoreKey = sdk.NewKVStoreKey("main") + multiStore := store.NewCommitMultiStore(db) + multiStore.SetSubstoreLoader(mainStoreKey, mainLoader) + var store = auth.NewAccountStore(mainStoreKey, bcm.AppAccountCodec{}) + + delegator := sdk.Actor{"testChain", "testapp", []byte("addressdelegator")} + validator := sdk.Actor{"testChain", "testapp", []byte("addressvalidator")} + + pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") + + //---------------------------------------------------------------------- + // Candidate checks + + // XXX expand to include both liabilities and assets use/test all candidate fields + candidate := &Candidate{ + Owner: validator, + PubKey: pk, + Assets: rational.New(9), + Liabilities: rational.New(9), + VotingPower: rational.Zero, + } + + candidatesEqual := func(c1, c2 *Candidate) bool { + return c1.Status == c2.Status && + c1.PubKey.Equals(c2.PubKey) && + c1.Owner.Equals(c2.Owner) && + c1.Assets.Equal(c2.Assets) && + c1.Liabilities.Equal(c2.Liabilities) && + c1.VotingPower.Equal(c2.VotingPower) && + c1.Description == c2.Description + } + + // check the empty store first + resCand := loadCandidate(store, pk) + assert.Nil(resCand) + resPks := loadCandidatesPubKeys(store) + assert.Zero(len(resPks)) + + // set and retrieve a record + saveCandidate(store, candidate) + resCand = loadCandidate(store, pk) + assert.True(candidatesEqual(candidate, resCand)) + + // modify a records, save, and retrieve + candidate.Liabilities = rational.New(99) + saveCandidate(store, candidate) + resCand = loadCandidate(store, pk) + assert.True(candidatesEqual(candidate, resCand)) + + // also test that the pubkey has been added to pubkey list + resPks = loadCandidatesPubKeys(store) + require.Equal(1, len(resPks)) + assert.Equal(pk, resPks[0]) + + //---------------------------------------------------------------------- + // Bond checks + + bond := &DelegatorBond{ + PubKey: pk, + Shares: rational.New(9), + } + + bondsEqual := func(b1, b2 *DelegatorBond) bool { + return b1.PubKey.Equals(b2.PubKey) && + b1.Shares.Equal(b2.Shares) + } + + //check the empty store first + resBond := loadDelegatorBond(store, delegator, pk) + assert.Nil(resBond) + + //Set and retrieve a record + saveDelegatorBond(store, delegator, bond) + resBond = loadDelegatorBond(store, delegator, pk) + assert.True(bondsEqual(bond, resBond)) + + //modify a records, save, and retrieve + bond.Shares = rational.New(99) + saveDelegatorBond(store, delegator, bond) + resBond = loadDelegatorBond(store, delegator, pk) + assert.True(bondsEqual(bond, resBond)) + + //---------------------------------------------------------------------- + // Param checks + + params := defaultParams() + + //check that the empty store loads the default + resParams := loadParams(store) + assert.Equal(params, resParams) + + //modify a params, save, and retrieve + params.MaxVals = 777 + saveParams(store, params) + resParams = loadParams(store) + assert.Equal(params, resParams) +} diff --git a/x/stake/test_common.go b/x/stake/test_common.go new file mode 100644 index 0000000000..17d26d9230 --- /dev/null +++ b/x/stake/test_common.go @@ -0,0 +1,91 @@ +package stake + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/rational" + + "github.com/cosmos/cosmos-sdk" +) + +func newActors(n int) (actors []sdk.Actor) { + for i := 0; i < n; i++ { + actors = append(actors, sdk.Actor{ + "testChain", "testapp", []byte(fmt.Sprintf("addr%d", i))}) + } + + return +} + +func newPubKey(pk string) crypto.PubKey { + pkBytes, _ := hex.DecodeString(pk) + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd.Wrap() +} + +// dummy pubkeys used for testing +var pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB60"), +} + +// NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey +// instead this is just being set the address here for testing purposes +func candidatesFromActors(actors []sdk.Actor, amts []int64) (candidates Candidates) { + for i := 0; i < len(actors); i++ { + c := &Candidate{ + Status: Unbonded, + PubKey: pks[i], + Owner: actors[i], + Assets: rational.New(amts[i]), + Liabilities: rational.New(amts[i]), + VotingPower: rational.New(amts[i]), + } + candidates = append(candidates, c) + } + + return +} + +func candidatesFromActorsEmpty(actors []sdk.Actor) (candidates Candidates) { + for i := 0; i < len(actors); i++ { + c := &Candidate{ + Status: Unbonded, + PubKey: pks[i], + Owner: actors[i], + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, + } + candidates = append(candidates, c) + } + return +} + +// helper function test if Candidate is changed asabci.Validator +func testChange(t *testing.T, val Validator, chg *abci.Validator) { + assert := assert.New(t) + assert.Equal(val.PubKey.Bytes(), chg.PubKey) + assert.Equal(val.VotingPower.Evaluate(), chg.Power) +} + +// helper function test if Candidate is removed as abci.Validator +func testRemove(t *testing.T, val Validator, chg *abci.Validator) { + assert := assert.New(t) + assert.Equal(val.PubKey.Bytes(), chg.PubKey) + assert.Equal(int64(0), chg.Power) +} diff --git a/x/stake/tick.go b/x/stake/tick.go new file mode 100644 index 0000000000..678a01ccd4 --- /dev/null +++ b/x/stake/tick.go @@ -0,0 +1,75 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/rational" +) + +// Tick - called at the end of every block +func Tick(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) { + + // retrieve params + params := loadParams(store) + gs := loadGlobalState(store) + height := ctx.BlockHeight() + + // Process Validator Provisions + // XXX right now just process every 5 blocks, in new SDK make hourly + if gs.InflationLastTime+5 <= height { + gs.InflationLastTime = height + processProvisions(store, gs, params) + } + + return UpdateValidatorSet(store, gs, params) +} + +var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days + +// process provisions for an hour period +func processProvisions(store state.SimpleDB, gs *GlobalState, params Params) { + + gs.Inflation = nextInflation(gs, params).Round(1000000000) + + // Because the validators hold a relative bonded share (`GlobalStakeShare`), when + // more bonded tokens are added proportionally to all validators the only term + // which needs to be updated is the `BondedPool`. So for each previsions cycle: + + provisions := gs.Inflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() + gs.BondedPool += provisions + gs.TotalSupply += provisions + + // XXX XXX XXX XXX XXX XXX XXX XXX XXX + // XXX Mint them to the hold account + // XXX XXX XXX XXX XXX XXX XXX XXX XXX + + // save the params + saveGlobalState(store, gs) +} + +// get the next inflation rate for the hour +func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) { + + // The target annual inflation rate is recalculated for each previsions cycle. The + // inflation is also subject to a rate change (positive of negative) depending or + // the distance from the desired ratio (67%). The maximum rate change possible is + // defined to be 13% per year, however the annual inflation is capped as between + // 7% and 20%. + + // (1 - bondedRatio/GoalBonded) * InflationRateChange + inflationRateChangePerYear := rational.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + + // increase the new annual inflation for this next cycle + inflation = gs.Inflation.Add(inflationRateChange) + if inflation.GT(params.InflationMax) { + inflation = params.InflationMax + } + if inflation.LT(params.InflationMin) { + inflation = params.InflationMin + } + + return +} diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go new file mode 100644 index 0000000000..3a80ae2a3b --- /dev/null +++ b/x/stake/tick_test.go @@ -0,0 +1,120 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/tmlibs/rational" + + "github.com/cosmos/cosmos-sdk/state" +) + +func TestGetInflation(t *testing.T) { + assert := assert.New(t) + store := state.NewMemKVStore() + params := loadParams(store) + gs := loadGlobalState(store) + + // Governing Mechanism: + // bondedRatio = BondedPool / TotalSupply + // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + setBondedPool, setTotalSupply int64 + setInflation, expectedChange rational.Rat + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + {1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {1, 1, rational.New(7, 100), rational.Zero}, + {1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)}, + + // test 20% maximum stop (testing with 0% bonded) + {0, 0, rational.New(20, 100), rational.Zero}, + {0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)}, + + // perfect balance shouldn't change inflation + {67, 100, rational.New(15, 100), rational.Zero}, + } + for _, tc := range tests { + gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply + gs.Inflation = tc.setInflation + + inflation := nextInflation(gs, params) + diffInflation := inflation.Sub(tc.setInflation) + + assert.True(diffInflation.Equal(tc.expectedChange), + "%v, %v", diffInflation, tc.expectedChange) + } +} + +func TestProcessProvisions(t *testing.T) { + assert := assert.New(t) + store := state.NewMemKVStore() + params := loadParams(store) + gs := loadGlobalState(store) + + // create some candidates some bonded, some unbonded + n := 10 + actors := newActors(n) + candidates := candidatesFromActorsEmpty(actors) + for i, candidate := range candidates { + if i < 5 { + candidate.Status = Bonded + } + mintedTokens := int64((i + 1) * 10000000) + gs.TotalSupply += mintedTokens + candidate.addTokens(mintedTokens, gs) + saveCandidate(store, candidate) + } + var totalSupply int64 = 550000000 + var bondedShares int64 = 150000000 + var unbondedShares int64 = 400000000 + + // initial bonded ratio ~ 27% + assert.True(gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) + + // Supplies + assert.Equal(totalSupply, gs.TotalSupply) + assert.Equal(bondedShares, gs.BondedPool) + assert.Equal(unbondedShares, gs.UnbondedPool) + + // test the value of candidate shares + assert.True(gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate()) + + initialSupply := gs.TotalSupply + initialUnbonded := gs.TotalSupply - gs.BondedPool + + // process the provisions a year + for hr := 0; hr < 8766; hr++ { + expInflation := nextInflation(gs, params).Round(1000000000) + expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() + startBondedPool := gs.BondedPool + startTotalSupply := gs.TotalSupply + processProvisions(store, gs, params) + assert.Equal(startBondedPool+expProvisions, gs.BondedPool) + assert.Equal(startTotalSupply+expProvisions, gs.TotalSupply) + } + assert.NotEqual(initialSupply, gs.TotalSupply) + assert.Equal(initialUnbonded, gs.UnbondedPool) + //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) + + // initial bonded ratio ~ 35% ~ 30% increase for bonded holders + assert.True(gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio()) + + // global supply + assert.Equal(int64(611813022), gs.TotalSupply) + assert.Equal(int64(211813022), gs.BondedPool) + assert.Equal(unbondedShares, gs.UnbondedPool) + + // test the value of candidate shares + assert.True(gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate()) + +} diff --git a/x/stake/tx.go b/x/stake/tx.go new file mode 100644 index 0000000000..951151dafd --- /dev/null +++ b/x/stake/tx.go @@ -0,0 +1,147 @@ +package stake + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/coin" + crypto "github.com/tendermint/go-crypto" +) + +// Tx +//-------------------------------------------------------------------------------- + +// register the tx type with its validation logic +// make sure to use the name of the handler as the prefix in the tx type, +// so it gets routed properly +const ( + ByteTxDeclareCandidacy = 0x55 + ByteTxEditCandidacy = 0x56 + ByteTxDelegate = 0x57 + ByteTxUnbond = 0x58 + TypeTxDeclareCandidacy = stakingModuleName + "/declareCandidacy" + TypeTxEditCandidacy = stakingModuleName + "/editCandidacy" + TypeTxDelegate = stakingModuleName + "/delegate" + TypeTxUnbond = stakingModuleName + "/unbond" +) + +func init() { + sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy) + sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy) + sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate) + sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond) +} + +//Verify interface at compile time +var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{} + +// BondUpdate - struct for bonding or unbonding transactions +type BondUpdate struct { + PubKey crypto.PubKey `json:"pub_key"` + Bond coin.Coin `json:"amount"` +} + +// ValidateBasic - Check for non-empty candidate, and valid coins +func (tx BondUpdate) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + + coins := coin.Coins{tx.Bond} + if !coins.IsValid() { + return coin.ErrInvalidCoins() + } + if !coins.IsPositive() { + return fmt.Errorf("Amount must be > 0") + } + return nil +} + +// TxDeclareCandidacy - struct for unbonding transactions +type TxDeclareCandidacy struct { + BondUpdate + Description +} + +// NewTxDeclareCandidacy - new TxDeclareCandidacy +func NewTxDeclareCandidacy(bond coin.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { + return TxDeclareCandidacy{ + BondUpdate{ + PubKey: pubKey, + Bond: bond, + }, + description, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// TxEditCandidacy - struct for editing a candidate +type TxEditCandidacy struct { + PubKey crypto.PubKey `json:"pub_key"` + Description +} + +// NewTxEditCandidacy - new TxEditCandidacy +func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx { + return TxEditCandidacy{ + PubKey: pubKey, + Description: description, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// ValidateBasic - Check for non-empty candidate, +func (tx TxEditCandidacy) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + + empty := Description{} + if tx.Description == empty { + return fmt.Errorf("Transaction must include some information to modify") + } + return nil +} + +// TxDelegate - struct for bonding transactions +type TxDelegate struct{ BondUpdate } + +// NewTxDelegate - new TxDelegate +func NewTxDelegate(bond coin.Coin, pubKey crypto.PubKey) sdk.Tx { + return TxDelegate{BondUpdate{ + PubKey: pubKey, + Bond: bond, + }}.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// TxUnbond - struct for unbonding transactions +type TxUnbond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares string `json:"amount"` +} + +// NewTxUnbond - new TxUnbond +func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx { + return TxUnbond{ + PubKey: pubKey, + Shares: shares, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// ValidateBasic - Check for non-empty candidate, positive shares +func (tx TxUnbond) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + return nil +} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go new file mode 100644 index 0000000000..f6d814589d --- /dev/null +++ b/x/stake/tx_test.go @@ -0,0 +1,104 @@ +package stake + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/coin" + + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" +) + +var ( + validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")} + empty sdk.Actor + + coinPos = coin.Coin{"fermion", 1000} + coinZero = coin.Coin{"fermion", 0} + coinNeg = coin.Coin{"fermion", -10000} + coinPosNotAtoms = coin.Coin{"foo", 10000} + coinZeroNotAtoms = coin.Coin{"foo", 0} + coinNegNotAtoms = coin.Coin{"foo", -10000} +) + +func TestBondUpdateValidateBasic(t *testing.T) { + tests := []struct { + name string + PubKey crypto.PubKey + Bond coin.Coin + wantErr bool + }{ + {"basic good", pks[0], coinPos, false}, + {"empty delegator", crypto.PubKey{}, coinPos, true}, + {"zero coin", pks[0], coinZero, true}, + {"neg coin", pks[0], coinNeg, true}, + } + + for _, tc := range tests { + tx := TxDelegate{BondUpdate{ + PubKey: tc.PubKey, + Bond: tc.Bond, + }} + assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) + } +} + +func TestAllAreTx(t *testing.T) { + assert := assert.New(t) + + // make sure all types construct properly + pubKey := newPubKey("1234567890") + bondAmt := 1234321 + bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + + // Note that Wrap is only defined on BondUpdate, so when you call it, + // you lose all info on the embedding type. Please add Wrap() + // method to all the parents + txDelegate := NewTxDelegate(bond, pubKey) + _, ok := txDelegate.Unwrap().(TxDelegate) + assert.True(ok, "%#v", txDelegate) + + txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey) + _, ok = txUnbond.Unwrap().(TxUnbond) + assert.True(ok, "%#v", txUnbond) + + txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{}) + _, ok = txDecl.Unwrap().(TxDeclareCandidacy) + assert.True(ok, "%#v", txDecl) + + txEditCan := NewTxEditCandidacy(pubKey, Description{}) + _, ok = txEditCan.Unwrap().(TxEditCandidacy) + assert.True(ok, "%#v", txEditCan) +} + +func TestSerializeTx(t *testing.T) { + assert := assert.New(t) + + // make sure all types construct properly + pubKey := newPubKey("1234567890") + bondAmt := 1234321 + bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + + tests := []struct { + tx sdk.Tx + }{ + {NewTxUnbond(strconv.Itoa(bondAmt), pubKey)}, + {NewTxDeclareCandidacy(bond, pubKey, Description{})}, + {NewTxDeclareCandidacy(bond, pubKey, Description{})}, + // {NewTxRevokeCandidacy(pubKey)}, + } + + for i, tc := range tests { + var tx sdk.Tx + bs := wire.BinaryBytes(tc.tx) + err := wire.ReadBinaryBytes(bs, &tx) + if assert.NoError(err, "%d", i) { + assert.Equal(tc.tx, tx, "%d", i) + } + } +} diff --git a/x/stake/types.go b/x/stake/types.go new file mode 100644 index 0000000000..0ca88ef11d --- /dev/null +++ b/x/stake/types.go @@ -0,0 +1,414 @@ +package stake + +import ( + "bytes" + "sort" + + "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/state" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" +) + +// Params defines the high level settings for staking +type Params struct { + HoldBonded sdk.Actor `json:"hold_bonded"` // account where all bonded coins are held + HoldUnbonded sdk.Actor `json:"hold_unbonded"` // account where all delegated but unbonded coins are held + + InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate + InflationMin rational.Rat `json:"inflation_min"` // minimum inflation rate + GoalBonded rational.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + + MaxVals uint16 `json:"max_vals"` // maximum number of validators + AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` + GasEditCandidacy int64 `json:"gas_edit_candidacy"` + GasDelegate int64 `json:"gas_delegate"` + GasUnbond int64 `json:"gas_unbond"` +} + +func defaultParams() Params { + return Params{ + HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), + HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), + InflationRateChange: rational.New(13, 100), + InflationMax: rational.New(20, 100), + InflationMin: rational.New(7, 100), + GoalBonded: rational.New(67, 100), + MaxVals: 100, + AllowedBondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +//_________________________________________________________________________ + +// GlobalState - dynamic parameters of the current state +type GlobalState struct { + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares rational.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares rational.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation rational.Rat `json:"inflation"` // current annual inflation rate +} + +// XXX define globalstate interface? + +func initialGlobalState() *GlobalState { + return &GlobalState{ + TotalSupply: 0, + BondedShares: rational.Zero, + UnbondedShares: rational.Zero, + BondedPool: 0, + UnbondedPool: 0, + InflationLastTime: 0, + Inflation: rational.New(7, 100), + } +} + +// get the bond ratio of the global state +func (gs *GlobalState) bondedRatio() rational.Rat { + if gs.TotalSupply > 0 { + return rational.New(gs.BondedPool, gs.TotalSupply) + } + return rational.Zero +} + +// get the exchange rate of bonded token per issued share +func (gs *GlobalState) bondedShareExRate() rational.Rat { + if gs.BondedShares.IsZero() { + return rational.One + } + return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) +} + +// get the exchange rate of unbonded tokens held in candidates per issued share +func (gs *GlobalState) unbondedShareExRate() rational.Rat { + if gs.UnbondedShares.IsZero() { + return rational.One + } + return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) +} + +func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rat) { + issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.BondedPool += amount + gs.BondedShares = gs.BondedShares.Add(issuedShares) + return +} + +func (gs *GlobalState) removeSharesBonded(shares rational.Rat) (removedTokens int64) { + removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.BondedShares = gs.BondedShares.Sub(shares) + gs.BondedPool -= removedTokens + return +} + +func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rat) { + issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + gs.UnbondedPool += amount + return +} + +func (gs *GlobalState) removeSharesUnbonded(shares rational.Rat) (removedTokens int64) { + removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.UnbondedShares = gs.UnbondedShares.Sub(shares) + gs.UnbondedPool -= removedTokens + return +} + +//_______________________________________________________________________________________________________ + +// CandidateStatus - status of a validator-candidate +type CandidateStatus byte + +const ( + // nolint + Bonded CandidateStatus = 0x00 + Unbonded CandidateStatus = 0x01 + Revoked CandidateStatus = 0x02 +) + +// Candidate defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// candidate, the candidate is credited with a DelegatorBond whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Candidate struct { + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner sdk.Actor `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate +} + +// Description - description fields for a candidate +type Description struct { + Moniker string `json:"moniker"` + Identity string `json:"identity"` + Website string `json:"website"` + Details string `json:"details"` +} + +// NewCandidate - initialize a new candidate +func NewCandidate(pubKey crypto.PubKey, owner sdk.Actor, description Description) *Candidate { + return &Candidate{ + Status: Unbonded, + PubKey: pubKey, + Owner: owner, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, + Description: description, + } +} + +// XXX define candidate interface? + +// get the exchange rate of global pool shares over delegator shares +func (c *Candidate) delegatorShareExRate() rational.Rat { + if c.Liabilities.IsZero() { + return rational.One + } + return c.Assets.Quo(c.Liabilities) +} + +// add tokens to a candidate +func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rat) { + + exRate := c.delegatorShareExRate() + + var receivedGlobalShares rational.Rat + if c.Status == Bonded { + receivedGlobalShares = gs.addTokensBonded(amount) + } else { + receivedGlobalShares = gs.addTokensUnbonded(amount) + } + c.Assets = c.Assets.Add(receivedGlobalShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) + return +} + +// remove shares from a candidate +func (c *Candidate) removeShares(shares rational.Rat, gs *GlobalState) (removedTokens int64) { + + globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) + + if c.Status == Bonded { + removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) + } else { + removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) + } + c.Assets = c.Assets.Sub(globalPoolSharesToRemove) + + c.Liabilities = c.Liabilities.Sub(shares) + return +} + +// Validator returns a copy of the Candidate as a Validator. +// Should only be called when the Candidate qualifies as a validator. +func (c *Candidate) validator() Validator { + return Validator(*c) +} + +// Validator is one of the top Candidates +type Validator Candidate + +// ABCIValidator - Get the validator from a bond value +func (v Validator) ABCIValidator() *abci.Validator { + return &abci.Validator{ + PubKey: wire.BinaryBytes(v.PubKey), + Power: v.VotingPower.Evaluate(), + } +} + +//_________________________________________________________________________ + +// TODO replace with sorted multistore functionality + +// Candidates - list of Candidates +type Candidates []*Candidate + +var _ sort.Interface = Candidates{} //enforce the sort interface at compile time + +// nolint - sort interface functions +func (cs Candidates) Len() int { return len(cs) } +func (cs Candidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } +func (cs Candidates) Less(i, j int) bool { + vp1, vp2 := cs[i].VotingPower, cs[j].VotingPower + pk1, pk2 := cs[i].PubKey.Bytes(), cs[j].PubKey.Bytes() + + //note that all ChainId and App must be the same for a group of candidates + if vp1 != vp2 { + return vp1.GT(vp2) + } + return bytes.Compare(pk1, pk2) == -1 +} + +// Sort - Sort the array of bonded values +func (cs Candidates) Sort() { + sort.Sort(cs) +} + +// update the voting power and save +func (cs Candidates) updateVotingPower(store state.SimpleDB, gs *GlobalState, params Params) Candidates { + + // update voting power + for _, c := range cs { + if !c.VotingPower.Equal(c.Assets) { + c.VotingPower = c.Assets + } + } + cs.Sort() + for i, c := range cs { + // truncate the power + if i >= int(params.MaxVals) { + c.VotingPower = rational.Zero + if c.Status == Bonded { + // XXX to replace this with handler.bondedToUnbondePool function + // XXX waiting for logic with new SDK to update account balance here + tokens := gs.removeSharesBonded(c.Assets) + c.Assets = gs.addTokensUnbonded(tokens) + c.Status = Unbonded + } + } else { + c.Status = Bonded + } + saveCandidate(store, c) + } + return cs +} + +// Validators - get the most recent updated validator set from the +// Candidates. These bonds are already sorted by VotingPower from +// the UpdateVotingPower function which is the only function which +// is to modify the VotingPower +func (cs Candidates) Validators() Validators { + + //test if empty + if len(cs) == 1 { + if cs[0].VotingPower.IsZero() { + return nil + } + } + + validators := make(Validators, len(cs)) + for i, c := range cs { + if c.VotingPower.IsZero() { //exit as soon as the first Voting power set to zero is found + return validators[:i] + } + validators[i] = c.validator() + } + + return validators +} + +//_________________________________________________________________________ + +// Validators - list of Validators +type Validators []Validator + +var _ sort.Interface = Validators{} //enforce the sort interface at compile time + +// nolint - sort interface functions +func (vs Validators) Len() int { return len(vs) } +func (vs Validators) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } +func (vs Validators) Less(i, j int) bool { + pk1, pk2 := vs[i].PubKey.Bytes(), vs[j].PubKey.Bytes() + return bytes.Compare(pk1, pk2) == -1 +} + +// Sort - Sort validators by pubkey +func (vs Validators) Sort() { + sort.Sort(vs) +} + +// determine all updated validators between two validator sets +func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validator) { + + //first sort the validator sets + vs.Sort() + vs2.Sort() + + max := len(vs) + len(vs2) + updated = make([]*abci.Validator, max) + i, j, n := 0, 0, 0 //counters for vs loop, vs2 loop, updated element + + for i < len(vs) && j < len(vs2) { + + if !vs[i].PubKey.Equals(vs2[j].PubKey) { + // pk1 > pk2, a new validator was introduced between these pubkeys + if bytes.Compare(vs[i].PubKey.Bytes(), vs2[j].PubKey.Bytes()) == 1 { + updated[n] = vs2[j].ABCIValidator() + n++ + j++ + continue + } // else, the old validator has been removed + updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} + n++ + i++ + continue + } + + if vs[i].VotingPower != vs2[j].VotingPower { + updated[n] = vs2[j].ABCIValidator() + n++ + } + j++ + i++ + } + + // add any excess validators in set 2 + for ; j < len(vs2); j, n = j+1, n+1 { + updated[n] = vs2[j].ABCIValidator() + } + + // remove any excess validators left in set 1 + for ; i < len(vs); i, n = i+1, n+1 { + updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} + } + + return updated[:n] +} + +// UpdateValidatorSet - Updates the voting power for the candidate set and +// returns the subset of validators which have been updated for Tendermint +func UpdateValidatorSet(store state.SimpleDB, gs *GlobalState, params Params) (change []*abci.Validator, err error) { + + // get the validators before update + candidates := loadCandidates(store) + + v1 := candidates.Validators() + v2 := candidates.updateVotingPower(store, gs, params).Validators() + + change = v1.validatorsUpdated(v2) + return +} + +//_________________________________________________________________________ + +// DelegatorBond represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type DelegatorBond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares rational.Rat `json:"shares"` +} diff --git a/x/stake/types_test.go b/x/stake/types_test.go new file mode 100644 index 0000000000..d78df60b00 --- /dev/null +++ b/x/stake/types_test.go @@ -0,0 +1,251 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tendermint/tmlibs/rational" + + "github.com/cosmos/cosmos-sdk/state" +) + +func TestCandidatesSort(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + N := 5 + actors := newActors(N) + candidates := candidatesFromActors(actors, []int64{10, 300, 123, 4, 200}) + expectedOrder := []int{1, 4, 2, 0, 3} + + // test basic sort + candidates.Sort() + + vals := candidates.Validators() + require.Equal(N, len(vals)) + + for i, val := range vals { + expectedIdx := expectedOrder[i] + assert.Equal(val.PubKey, pks[expectedIdx]) + } +} + +func TestValidatorsSort(t *testing.T) { + assert := assert.New(t) + + v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(25)}).validator() + v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(1234)}).validator() + v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(122)}).validator() + v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(13)}).validator() + v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(1111)}).validator() + + // test from nothing to something + vs := Validators{v4, v2, v5, v1, v3} + + // test basic sort + vs.Sort() + + for i, v := range vs { + assert.True(v.PubKey.Equals(pks[i])) + } +} + +func TestUpdateVotingPower(t *testing.T) { + assert := assert.New(t) + store := state.NewMemKVStore() + params := loadParams(store) + gs := loadGlobalState(store) + + N := 5 + actors := newActors(N) + candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + + // test a basic change in voting power + candidates[0].Assets = rational.New(500) + candidates.updateVotingPower(store, gs, params) + assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + + // test a swap in voting power + candidates[1].Assets = rational.New(600) + candidates.updateVotingPower(store, gs, params) + assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) + + // test the max validators term + params.MaxVals = 4 + saveParams(store, params) + candidates.updateVotingPower(store, gs, params) + assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) +} + +func TestGetValidators(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + N := 5 + actors := newActors(N) + candidates := candidatesFromActors(actors, []int64{400, 200, 0, 0, 0}) + + validators := candidates.Validators() + require.Equal(2, len(validators)) + assert.Equal(candidates[0].PubKey, validators[0].PubKey) + assert.Equal(candidates[1].PubKey, validators[1].PubKey) +} + +func TestValidatorsChanged(t *testing.T) { + require := require.New(t) + + v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator() + v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator() + v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator() + v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator() + v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator() + + // test from nothing to something + vs1 := Validators{} + vs2 := Validators{v1, v2} + changed := vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testChange(t, vs2[0], changed[0]) + testChange(t, vs2[1], changed[1]) + + // test from something to nothing + vs1 = Validators{v1, v2} + vs2 = Validators{} + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testRemove(t, vs1[0], changed[0]) + testRemove(t, vs1[1], changed[1]) + + // test identical + vs1 = Validators{v1, v2, v4} + vs2 = Validators{v1, v2, v4} + changed = vs1.validatorsUpdated(vs2) + require.Zero(len(changed)) + + // test single value change + vs2[2].VotingPower = rational.One + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[2], changed[0]) + + // test multiple value change + vs2[0].VotingPower = rational.New(11) + vs2[2].VotingPower = rational.New(5) + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testChange(t, vs2[0], changed[0]) + testChange(t, vs2[2], changed[1]) + + // test validator added at the beginning + vs1 = Validators{v2, v4} + vs2 = Validators{v2, v4, v1} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[0], changed[0]) + + // test validator added in the middle + vs1 = Validators{v1, v2, v4} + vs2 = Validators{v3, v1, v4, v2} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[2], changed[0]) + + // test validator added at the end + vs2 = Validators{v1, v2, v4, v5} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[3], changed[0]) + + // test multiple validators added + vs2 = Validators{v1, v2, v3, v4, v5} + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testChange(t, vs2[2], changed[0]) + testChange(t, vs2[4], changed[1]) + + // test validator removed at the beginning + vs2 = Validators{v2, v4} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testRemove(t, vs1[0], changed[0]) + + // test validator removed in the middle + vs2 = Validators{v1, v4} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testRemove(t, vs1[1], changed[0]) + + // test validator removed at the end + vs2 = Validators{v1, v2} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testRemove(t, vs1[2], changed[0]) + + // test multiple validators removed + vs2 = Validators{v1} + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testRemove(t, vs1[1], changed[0]) + testRemove(t, vs1[2], changed[1]) + + // test many types of changes + vs2 = Validators{v1, v3, v4, v5} + vs2[2].VotingPower = rational.New(11) + changed = vs1.validatorsUpdated(vs2) + require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 + testRemove(t, vs1[1], changed[0]) + testChange(t, vs2[1], changed[1]) + testChange(t, vs2[2], changed[2]) + testChange(t, vs2[3], changed[3]) + +} + +func TestUpdateValidatorSet(t *testing.T) { + assert, require := assert.New(t), require.New(t) + store := state.NewMemKVStore() + params := loadParams(store) + gs := loadGlobalState(store) + + N := 5 + actors := newActors(N) + candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + for _, c := range candidates { + saveCandidate(store, c) + } + + // they should all already be validators + change, err := UpdateValidatorSet(store, gs, params) + require.Nil(err) + require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 + + // test the max value and test again + params.MaxVals = 4 + saveParams(store, params) + change, err = UpdateValidatorSet(store, gs, params) + require.Nil(err) + require.Equal(1, len(change), "%v", change) + testRemove(t, candidates[4].validator(), change[0]) + candidates = loadCandidates(store) + assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) + + // mess with the power's of the candidates and test + candidates[0].Assets = rational.New(10) + candidates[1].Assets = rational.New(600) + candidates[2].Assets = rational.New(1000) + candidates[3].Assets = rational.One + candidates[4].Assets = rational.New(10) + for _, c := range candidates { + saveCandidate(store, c) + } + change, err = UpdateValidatorSet(store, gs, params) + require.Nil(err) + require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed + candidates = loadCandidates(store) + testChange(t, candidates[0].validator(), change[0]) + testChange(t, candidates[1].validator(), change[1]) + testChange(t, candidates[2].validator(), change[2]) + testRemove(t, candidates[3].validator(), change[3]) + testChange(t, candidates[4].validator(), change[4]) +} + +// XXX test global state functions, candidate exchange rate functions etc. From c2ddc582c44f9067b296de5778d73069a2c2f53e Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 18 Jan 2018 08:39:16 +0000 Subject: [PATCH 02/54] working gaia store --- examples/gaia/client.go | 97 ----- examples/gaia/main.go | 50 --- examples/gaia/node.go | 69 --- examples/gaia/rest.go | 89 ---- examples/gaia/sh_tests/stake.sh | 275 ------------ x/stake/commands/query.go | 136 ------ x/stake/commands/tx.go | 195 --------- x/stake/errors.go | 53 --- x/stake/handler.go | 529 ----------------------- x/stake/handler_test.go | 336 -------------- x/stake/rest/query.go | 188 -------- x/stake/rest/tx.go | 159 ------- x/stake/{state.go => store.go} | 90 ++-- x/stake/{state_test.go => store_test.go} | 44 +- x/stake/test_common.go | 91 ---- x/stake/tick.go | 75 ---- x/stake/tick_test.go | 120 ----- x/stake/tx.go | 147 ------- x/stake/tx_test.go | 104 ----- x/stake/types.go | 26 +- x/stake/types_test.go | 251 ----------- 21 files changed, 100 insertions(+), 3024 deletions(-) delete mode 100644 examples/gaia/client.go delete mode 100644 examples/gaia/main.go delete mode 100644 examples/gaia/node.go delete mode 100644 examples/gaia/rest.go delete mode 100644 examples/gaia/sh_tests/stake.sh delete mode 100644 x/stake/commands/query.go delete mode 100644 x/stake/commands/tx.go delete mode 100644 x/stake/errors.go delete mode 100644 x/stake/handler.go delete mode 100644 x/stake/handler_test.go delete mode 100644 x/stake/rest/query.go delete mode 100644 x/stake/rest/tx.go rename x/stake/{state.go => store.go} (71%) rename x/stake/{state_test.go => store_test.go} (70%) delete mode 100644 x/stake/test_common.go delete mode 100644 x/stake/tick.go delete mode 100644 x/stake/tick_test.go delete mode 100644 x/stake/tx.go delete mode 100644 x/stake/tx_test.go delete mode 100644 x/stake/types_test.go diff --git a/examples/gaia/client.go b/examples/gaia/client.go deleted file mode 100644 index 6344fc3489..0000000000 --- a/examples/gaia/client.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - "github.com/cosmos/cosmos-sdk/client/commands" - "github.com/cosmos/cosmos-sdk/client/commands/commits" - "github.com/cosmos/cosmos-sdk/client/commands/keys" - "github.com/cosmos/cosmos-sdk/client/commands/proxy" - "github.com/cosmos/cosmos-sdk/client/commands/query" - rpccmd "github.com/cosmos/cosmos-sdk/client/commands/rpc" - txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" - authcmd "github.com/cosmos/cosmos-sdk/modules/auth/commands" - basecmd "github.com/cosmos/cosmos-sdk/modules/base/commands" - coincmd "github.com/cosmos/cosmos-sdk/modules/coin/commands" - feecmd "github.com/cosmos/cosmos-sdk/modules/fee/commands" - ibccmd "github.com/cosmos/cosmos-sdk/modules/ibc/commands" - noncecmd "github.com/cosmos/cosmos-sdk/modules/nonce/commands" - rolecmd "github.com/cosmos/cosmos-sdk/modules/roles/commands" - - stakecmd "github.com/cosmos/gaia/modules/stake/commands" -) - -// clientCmd is the entry point for this binary -var clientCmd = &cobra.Command{ - Use: "client", - Short: "Gaia light client", - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - }, -} - -func prepareClientCommands() { - commands.AddBasicFlags(clientCmd) - - // Prepare queries - query.RootCmd.AddCommand( - // These are default parsers, but optional in your app (you can remove key) - query.TxQueryCmd, - query.KeyQueryCmd, - coincmd.AccountQueryCmd, - noncecmd.NonceQueryCmd, - rolecmd.RoleQueryCmd, - ibccmd.IBCQueryCmd, - - //stakecmd.CmdQueryValidator, - stakecmd.CmdQueryCandidates, - stakecmd.CmdQueryCandidate, - stakecmd.CmdQueryDelegatorBond, - stakecmd.CmdQueryDelegatorCandidates, - ) - - // set up the middleware - txcmd.Middleware = txcmd.Wrappers{ - feecmd.FeeWrapper{}, - rolecmd.RoleWrapper{}, - noncecmd.NonceWrapper{}, - basecmd.ChainWrapper{}, - authcmd.SigWrapper{}, - } - txcmd.Middleware.Register(txcmd.RootCmd.PersistentFlags()) - - // you will always want this for the base send command - txcmd.RootCmd.AddCommand( - // This is the default transaction, optional in your app - coincmd.SendTxCmd, - coincmd.CreditTxCmd, - // this enables creating roles - rolecmd.CreateRoleTxCmd, - // these are for handling ibc - ibccmd.RegisterChainTxCmd, - ibccmd.UpdateChainTxCmd, - ibccmd.PostPacketTxCmd, - - stakecmd.CmdDeclareCandidacy, - stakecmd.CmdEditCandidacy, - stakecmd.CmdDelegate, - stakecmd.CmdUnbond, - ) - - clientCmd.AddCommand( - proxy.RootCmd, - lineBreak, - - txcmd.RootCmd, - query.RootCmd, - rpccmd.RootCmd, - lineBreak, - - keys.RootCmd, - commands.InitCmd, - commands.ResetCmd, - commits.RootCmd, - lineBreak, - ) - -} diff --git a/examples/gaia/main.go b/examples/gaia/main.go deleted file mode 100644 index b631e5cba3..0000000000 --- a/examples/gaia/main.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/cobra" - - "github.com/tendermint/tmlibs/cli" - - basecmd "github.com/cosmos/cosmos-sdk/server/commands" - "github.com/cosmos/gaia/version" -) - -// GaiaCmd is the entry point for this binary -var ( - GaiaCmd = &cobra.Command{ - Use: "gaia", - Short: "The Cosmos Network delegation-game test", - Run: func(cmd *cobra.Command, args []string) { - cmd.Help() - }, - } - - lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} -) - -func main() { - // disable sorting - cobra.EnableCommandSorting = false - - // add commands - prepareNodeCommands() - prepareRestServerCommands() - prepareClientCommands() - - GaiaCmd.AddCommand( - nodeCmd, - restServerCmd, - clientCmd, - - lineBreak, - version.VersionCmd, - //auto.AutoCompleteCmd, - ) - - // prepare and add flags - basecmd.SetUpRoot(GaiaCmd) - executor := cli.PrepareMainCmd(GaiaCmd, "GA", os.ExpandEnv("$HOME/.cosmos-gaia-cli")) - executor.Execute() -} diff --git a/examples/gaia/node.go b/examples/gaia/node.go deleted file mode 100644 index aec74aeaf0..0000000000 --- a/examples/gaia/node.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - abci "github.com/tendermint/abci/types" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/auth" - "github.com/cosmos/cosmos-sdk/modules/base" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/modules/fee" - "github.com/cosmos/cosmos-sdk/modules/ibc" - "github.com/cosmos/cosmos-sdk/modules/nonce" - "github.com/cosmos/cosmos-sdk/modules/roles" - basecmd "github.com/cosmos/cosmos-sdk/server/commands" - "github.com/cosmos/cosmos-sdk/stack" - "github.com/cosmos/cosmos-sdk/state" - - "github.com/cosmos/gaia/modules/stake" -) - -// nodeCmd is the entry point for this binary -var nodeCmd = &cobra.Command{ - Use: "node", - Short: "The Cosmos Network delegation-game blockchain test", - Run: func(cmd *cobra.Command, args []string) { cmd.Help() }, -} - -func prepareNodeCommands() { - - basecmd.Handler = stack.New( - base.Logger{}, - stack.Recovery{}, - auth.Signatures{}, - base.Chain{}, - stack.Checkpoint{OnCheck: true}, - nonce.ReplayCheck{}, - ). - IBC(ibc.NewMiddleware()). - Apps( - roles.NewMiddleware(), - fee.NewSimpleFeeMiddleware(coin.Coin{"fermion", 0}, fee.Bank), - stack.Checkpoint{OnDeliver: true}, - ). - Dispatch( - coin.NewHandler(), - stack.WrapHandler(roles.NewHandler()), - stack.WrapHandler(ibc.NewHandler()), - stake.NewHandler(), - ) - - nodeCmd.AddCommand( - basecmd.GetInitCmd("fermion", []string{"stake/allowed_bond_denom/fermion"}), - basecmd.GetTickStartCmd(sdk.TickerFunc(tickFn)), - basecmd.UnsafeResetAllCmd, - ) -} - -// Tick - Called every block even if no transaction, process all queues, -// validator rewards, and calculate the validator set difference -func tickFn(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) { - // first need to prefix the store, at this point it's a global store - store = stack.PrefixedStore(stake.Name(), store) - - // execute Tick - change, err = stake.Tick(ctx, store) - return -} diff --git a/examples/gaia/rest.go b/examples/gaia/rest.go deleted file mode 100644 index 7a96978961..0000000000 --- a/examples/gaia/rest.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - - "github.com/gorilla/mux" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tmlibs/cli" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/commands" - rest "github.com/cosmos/cosmos-sdk/client/rest" - coinrest "github.com/cosmos/cosmos-sdk/modules/coin/rest" - noncerest "github.com/cosmos/cosmos-sdk/modules/nonce/rest" - rolerest "github.com/cosmos/cosmos-sdk/modules/roles/rest" - - stakerest "github.com/cosmos/gaia/modules/stake/rest" -) - -const defaultAlgo = "ed25519" - -var ( - restServerCmd = &cobra.Command{ - Use: "rest-server", - Short: "REST client for gaia commands", - Long: `Gaiaserver presents a nice (not raw hex) interface to the gaia blockchain structure.`, - RunE: func(cmd *cobra.Command, args []string) error { - return cmdRestServer(cmd, args) - }, - } - - flagPort = "port" -) - -func prepareRestServerCommands() { - commands.AddBasicFlags(restServerCmd) - restServerCmd.PersistentFlags().IntP(flagPort, "p", 8998, "port to run the server on") -} - -func cmdRestServer(cmd *cobra.Command, args []string) error { - router := mux.NewRouter() - - rootDir := viper.GetString(cli.HomeFlag) - keyMan := client.GetKeyManager(rootDir) - serviceKeys := rest.NewServiceKeys(keyMan) - serviceTxs := rest.NewServiceTxs(commands.GetNode()) - - routeRegistrars := []func(*mux.Router) error{ - // rest.Keys handlers - serviceKeys.RegisterCRUD, - - // Coin handlers (Send, Query, SearchSent) - coinrest.RegisterAll, - - // Roles createRole handler - rolerest.RegisterCreateRole, - - // Gaia sign transactions handler - serviceKeys.RegisterSignTx, - // Gaia post transaction handler - serviceTxs.RegisterPostTx, - - // Nonce query handler - noncerest.RegisterQueryNonce, - - // Staking query handlers - stakerest.RegisterQueryCandidate, - stakerest.RegisterQueryCandidates, - stakerest.RegisterQueryDelegatorBond, - stakerest.RegisterQueryDelegatorCandidates, - // Staking tx builders - stakerest.RegisterDelegate, - stakerest.RegisterUnbond, - } - - for _, routeRegistrar := range routeRegistrars { - if err := routeRegistrar(router); err != nil { - log.Fatal(err) - } - } - - addr := fmt.Sprintf(":%d", viper.GetInt(flagPort)) - - log.Printf("Serving on %q", addr) - return http.ListenAndServe(addr, router) -} diff --git a/examples/gaia/sh_tests/stake.sh b/examples/gaia/sh_tests/stake.sh deleted file mode 100644 index 4f5d1c7703..0000000000 --- a/examples/gaia/sh_tests/stake.sh +++ /dev/null @@ -1,275 +0,0 @@ -#!/bin/bash -set -u - -# These global variables are required for common.sh -SERVER_EXE="gaia node" -CLIENT_EXE="gaia client" -ACCOUNTS=(jae ethan bucky rigel igor) -RICH=${ACCOUNTS[0]} -DELEGATOR=${ACCOUNTS[2]} -POOR=${ACCOUNTS[4]} - -BASE_DIR=$HOME/stake_test -BASE_DIR2=$HOME/stake_test2 -SERVER1=$BASE_DIR/server -SERVER2=$BASE_DIR2/server - -oneTimeSetUp() { - #[ "$2" ] || echo "missing parameters, line=${LINENO}" ; exit 1; - - - # These are passed in as args - CHAIN_ID="stake_test" - - # TODO Make this more robust - if [ "$BASE_DIR" == "$HOME/" ]; then - echo "Must be called with argument, or it will wipe your home directory" - exit 1 - fi - - rm -rf $BASE_DIR 2>/dev/null - mkdir -p $BASE_DIR - - if [ "$BASE_DIR2" == "$HOME/" ]; then - echo "Must be called with argument, or it will wipe your home directory" - exit 1 - fi - rm -rf $BASE_DIR2 2>/dev/null - mkdir -p $BASE_DIR2 - - # Set up client - make sure you use the proper prefix if you set - # a custom CLIENT_EXE - export BC_HOME=${BASE_DIR}/client - prepareClient - - # start the node server - set +u ; initServer $BASE_DIR $CHAIN_ID ; set -u - if [ $? != 0 ]; then return 1; fi - - set +u ; initClient $CHAIN_ID ; set -u - if [ $? != 0 ]; then return 1; fi - - printf "...Testing may begin!\n\n\n" - -} - -oneTimeTearDown() { - kill -9 $PID_SERVER2 >/dev/null 2>&1 - set +u ; quickTearDown ; set -u -} - -# Ex Usage: checkCandidate $PUBKEY $EXPECTED_VOTING_POWER -checkCandidate() { - CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1) - if ! assertTrue "line=${LINENO}, bad query" $?; then - return 1 - fi - assertEquals "line=${LINENO}, proper voting power" "$2" $(echo $CANDIDATE | jq .data.voting_power) - return $? -} - -# Ex Usage: checkCandidate $PUBKEY -checkCandidateEmpty() { - CANDIDATE=$(${CLIENT_EXE} query candidate --pubkey=$1 2>/dev/null) - if ! assertFalse "line=${LINENO}, expected empty query" $?; then - return 1 - fi -} - -# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY $EXPECTED_SHARES -checkDelegatorBond() { - BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2) - if ! assertTrue "line=${LINENO}, account must exist" $?; then - return 1 - fi - assertEquals "line=${LINENO}, proper bond amount" "$3" $(echo $BOND | jq .data.Shares) - return $? -} - -# Ex Usage: checkCandidate $DELEGATOR_ADDR $PUBKEY -checkDelegatorBondEmpty() { - BOND=$(${CLIENT_EXE} query delegator-bond --delegator-address=$1 --pubkey=$2 2>/dev/null) - if ! assertFalse "line=${LINENO}, expected empty query" $?; then - return 1 - fi -} - -#______________________________________________________________________________________ - -test00GetAccount() { - SENDER=$(getAddr $RICH) - RECV=$(getAddr $POOR) - - assertFalse "line=${LINENO}, requires arg" "${CLIENT_EXE} query account" - - set +u ; checkAccount $SENDER "9007199254740992" ; set -u - - ACCT2=$(${CLIENT_EXE} query account $RECV 2>/dev/null) - assertFalse "line=${LINENO}, has no genesis account" $? -} - -test01SendTx() { - assertFalse "line=${LINENO}, missing dest" "${CLIENT_EXE} tx send --amount=992fermion --sequence=1" - assertFalse "line=${LINENO}, bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH" - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992fermion --sequence=1 --to=$RECV --name=$RICH) - txSucceeded $? "$TX" "$RECV" - HASH=$(echo $TX | jq .hash | tr -d \") - TX_HEIGHT=$(echo $TX | jq .height) - - set +u - checkAccount $SENDER "9007199254740000" $TX_HEIGHT - # make sure 0x prefix also works - checkAccount "0x$SENDER" "9007199254740000" $TX_HEIGHT - checkAccount $RECV "992" $TX_HEIGHT - - # Make sure tx is indexed - checkSendTx $HASH $TX_HEIGHT $SENDER "992" - set -u -} - -test02DeclareCandidacy() { - - # the premise of this test is to run a second validator (from rich) and then bond and unbond some tokens - # first create a second node to run and connect to the system - - # init the second node - SERVER_LOG2=$BASE_DIR2/node2.log - GENKEY=$(${CLIENT_EXE} keys get ${RICH} | awk '{print $2}') - ${SERVER_EXE} init $GENKEY --chain-id $CHAIN_ID --home=$SERVER2 >>$SERVER_LOG2 - if [ $? != 0 ]; then return 1; fi - - # copy in the genesis from the first initialization to the new server - cp $SERVER1/genesis.json $SERVER2/genesis.json - - # point the new config to the old server location - rm $SERVER2/config.toml - echo 'proxy_app = "tcp://127.0.0.1:46668" - moniker = "anonymous" - fast_sync = true - db_backend = "leveldb" - log_level = "state:info,*:error" - - [rpc] - laddr = "tcp://0.0.0.0:46667" - - [p2p] - laddr = "tcp://0.0.0.0:46666" - seeds = "0.0.0.0:46656"' >$SERVER2/config.toml - - # start the second node - ${SERVER_EXE} start --home=$SERVER2 >>$SERVER_LOG2 2>&1 & - sleep 1 - PID_SERVER2=$! - disown - if ! ps $PID_SERVER2 >/dev/null; then - echo "**FAILED**" - cat $SERVER_LOG2 - return 1 - fi - - # get the pubkey of the second validator - PK2=$(cat $SERVER2/priv_validator.json | jq -r .pub_key.data) - - CAND_ADDR=$(getAddr $POOR) - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx declare-candidacy --sequence=1 --amount=10fermion --name=$POOR --pubkey=$PK2 --moniker=rigey) - if [ $? != 0 ]; then return 1; fi - HASH=$(echo $TX | jq .hash | tr -d \") - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $CAND_ADDR "982" $TX_HEIGHT ; set -u - checkCandidate $PK2 "10" - checkDelegatorBond $CAND_ADDR $PK2 "10" -} - -test03Delegate() { - # send some coins to a delegator - DELA_ADDR=$(getAddr $DELEGATOR) - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --sequence=2 --amount=15fermion --to=$DELA_ADDR --name=$RICH) - txSucceeded $? "$TX" "$DELA_ADDR" - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u - - # delegate some coins to the new - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=1 --amount=10fermion --name=$DELEGATOR --pubkey=$PK2) - if [ $? != 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "5" $TX_HEIGHT ; set -u - checkCandidate $PK2 "20" - checkDelegatorBond $DELA_ADDR $PK2 "10" - - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=2 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2) - if [ $? != 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u - checkCandidate $PK2 "23" - checkDelegatorBond $DELA_ADDR $PK2 "13" - - # attempt a delegation without enough funds - # NOTE the sequence number still increments here because it will fail - # only during DeliverTx - however this should be updated (TODO) in new - # SDK when we can fail in CheckTx - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=3 --amount=3fermion --name=$DELEGATOR --pubkey=$PK2 2>/dev/null) - if [ $? == 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "2" $TX_HEIGHT ; set -u - checkCandidate $PK2 "23" - checkDelegatorBond $DELA_ADDR $PK2 "13" - - # perform the final delegation which should empty the delegators account - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx delegate --sequence=4 --amount=2fermion --name=$DELEGATOR --pubkey=$PK2) - if [ $? != 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "null" $TX_HEIGHT ; set -u #empty account is null - checkCandidate $PK2 "25" -} - -test04Unbond() { - # unbond from the delegator a bit - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=5 --shares=10 --name=$DELEGATOR --pubkey=$PK2) - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u - checkCandidate $PK2 "15" - checkDelegatorBond $DELA_ADDR $PK2 "5" - - # attempt to unbond more shares than exist - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=10 --name=$DELEGATOR --pubkey=$PK2 2>/dev/null) - if [ $? == 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "10" $TX_HEIGHT ; set -u - checkCandidate $PK2 "15" - checkDelegatorBond $DELA_ADDR $PK2 "5" - - # unbond entirely from the delegator - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=6 --shares=5 --name=$DELEGATOR --pubkey=$PK2) - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $DELA_ADDR "15" $TX_HEIGHT ; set -u - checkCandidate $PK2 "10" - checkDelegatorBondEmpty $DELA_ADDR $PK2 - - # unbond a bit from the owner - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=2 --shares=5 --name=$POOR --pubkey=$PK2) - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u - checkCandidate $PK2 "5" - checkDelegatorBond $CAND_ADDR $PK2 "5" - - # attempt to unbond more shares than exist - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=10 --name=$POOR --pubkey=$PK2 2>/dev/null) - if [ $? == 0 ]; then return 1; fi - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $CAND_ADDR "987" $TX_HEIGHT ; set -u - checkCandidate $PK2 "5" - checkDelegatorBond $CAND_ADDR $PK2 "5" - - # unbond entirely from the validator - TX=$(echo qwertyuiop | ${CLIENT_EXE} tx unbond --sequence=3 --shares=5 --name=$POOR --pubkey=$PK2) - TX_HEIGHT=$(echo $TX | jq .height) - set +u ; checkAccount $CAND_ADDR "992" $TX_HEIGHT ; set -u - checkCandidateEmpty $PK2 - checkDelegatorBondEmpty $CAND_ADDR $PK2 -} - -# Load common then run these tests with shunit2! -CLI_DIR=$GOPATH/src/github.com/cosmos/gaia/vendor/github.com/cosmos/cosmos-sdk/tests/cli - -. $CLI_DIR/common.sh -. $CLI_DIR/shunit2 diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go deleted file mode 100644 index 2528d07d10..0000000000 --- a/x/stake/commands/query.go +++ /dev/null @@ -1,136 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" - "github.com/spf13/viper" - - crypto "github.com/tendermint/go-crypto" - - "github.com/cosmos/cosmos-sdk/client/commands" - "github.com/cosmos/cosmos-sdk/client/commands/query" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/stack" - "github.com/cosmos/gaia/modules/stake" -) - -//nolint -var ( - CmdQueryCandidates = &cobra.Command{ - Use: "candidates", - Short: "Query for the set of validator-candidates pubkeys", - RunE: cmdQueryCandidates, - } - - CmdQueryCandidate = &cobra.Command{ - Use: "candidate", - Short: "Query a validator-candidate account", - RunE: cmdQueryCandidate, - } - - CmdQueryDelegatorBond = &cobra.Command{ - Use: "delegator-bond", - Short: "Query a delegators bond based on address and candidate pubkey", - RunE: cmdQueryDelegatorBond, - } - - CmdQueryDelegatorCandidates = &cobra.Command{ - Use: "delegator-candidates", - RunE: cmdQueryDelegatorCandidates, - Short: "Query all delegators candidates' pubkeys based on address", - } - - FlagDelegatorAddress = "delegator-address" -) - -func init() { - //Add Flags - fsPk := flag.NewFlagSet("", flag.ContinueOnError) - fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") - fsAddr := flag.NewFlagSet("", flag.ContinueOnError) - fsAddr.String(FlagDelegatorAddress, "", "Delegator Hex Address") - - CmdQueryCandidate.Flags().AddFlagSet(fsPk) - CmdQueryDelegatorBond.Flags().AddFlagSet(fsPk) - CmdQueryDelegatorBond.Flags().AddFlagSet(fsAddr) - CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr) -} - -func cmdQueryCandidates(cmd *cobra.Command, args []string) error { - - var pks []crypto.PubKey - - prove := !viper.GetBool(commands.FlagTrustNode) - key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) - height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) - if err != nil { - return err - } - - return query.OutputProof(pks, height) -} - -func cmdQueryCandidate(cmd *cobra.Command, args []string) error { - - var candidate stake.Candidate - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - prove := !viper.GetBool(commands.FlagTrustNode) - key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) - height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) - if err != nil { - return err - } - - return query.OutputProof(candidate, height) -} - -func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error { - - var bond stake.DelegatorBond - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - delegatorAddr := viper.GetString(FlagDelegatorAddress) - delegator, err := commands.ParseActor(delegatorAddr) - if err != nil { - return err - } - delegator = coin.ChainAddr(delegator) - - prove := !viper.GetBool(commands.FlagTrustNode) - key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) - height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) - if err != nil { - return err - } - - return query.OutputProof(bond, height) -} - -func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error { - - delegatorAddr := viper.GetString(FlagDelegatorAddress) - delegator, err := commands.ParseActor(delegatorAddr) - if err != nil { - return err - } - delegator = coin.ChainAddr(delegator) - - prove := !viper.GetBool(commands.FlagTrustNode) - key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) - var candidates []crypto.PubKey - height, err := query.GetParsed(key, &candidates, query.GetHeight(), prove) - if err != nil { - return err - } - - return query.OutputProof(candidates, height) -} diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go deleted file mode 100644 index e14ac8cec1..0000000000 --- a/x/stake/commands/tx.go +++ /dev/null @@ -1,195 +0,0 @@ -package commands - -import ( - "encoding/hex" - "fmt" - - "github.com/spf13/cobra" - flag "github.com/spf13/pflag" - "github.com/spf13/viper" - - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" - - txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" - "github.com/cosmos/cosmos-sdk/modules/coin" - - "github.com/cosmos/gaia/modules/stake" -) - -// nolint -const ( - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" - - FlagMoniker = "moniker" - FlagIdentity = "keybase-sig" - FlagWebsite = "website" - FlagDetails = "details" -) - -// nolint -var ( - CmdDeclareCandidacy = &cobra.Command{ - Use: "declare-candidacy", - Short: "create new validator-candidate account and delegate some coins to it", - RunE: cmdDeclareCandidacy, - } - CmdEditCandidacy = &cobra.Command{ - Use: "edit-candidacy", - Short: "edit and existing validator-candidate account", - RunE: cmdEditCandidacy, - } - CmdDelegate = &cobra.Command{ - Use: "delegate", - Short: "delegate coins to an existing validator/candidate", - RunE: cmdDelegate, - } - CmdUnbond = &cobra.Command{ - Use: "unbond", - Short: "unbond coins from a validator/candidate", - RunE: cmdUnbond, - } -) - -func init() { - - // define the flags - fsPk := flag.NewFlagSet("", flag.ContinueOnError) - fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") - - fsAmount := flag.NewFlagSet("", flag.ContinueOnError) - fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond") - - fsShares := flag.NewFlagSet("", flag.ContinueOnError) - fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") - - fsCandidate := flag.NewFlagSet("", flag.ContinueOnError) - fsCandidate.String(FlagMoniker, "", "validator-candidate name") - fsCandidate.String(FlagIdentity, "", "optional keybase signature") - fsCandidate.String(FlagWebsite, "", "optional website") - fsCandidate.String(FlagDetails, "", "optional detailed description space") - - // add the flags - CmdDelegate.Flags().AddFlagSet(fsPk) - CmdDelegate.Flags().AddFlagSet(fsAmount) - - CmdUnbond.Flags().AddFlagSet(fsPk) - CmdUnbond.Flags().AddFlagSet(fsShares) - - CmdDeclareCandidacy.Flags().AddFlagSet(fsPk) - CmdDeclareCandidacy.Flags().AddFlagSet(fsAmount) - CmdDeclareCandidacy.Flags().AddFlagSet(fsCandidate) - - CmdEditCandidacy.Flags().AddFlagSet(fsPk) - CmdEditCandidacy.Flags().AddFlagSet(fsCandidate) -} - -func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { - amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) - if err != nil { - return err - } - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - if viper.GetString(FlagMoniker) == "" { - return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") - } - - description := stake.Description{ - Moniker: viper.GetString(FlagMoniker), - Identity: viper.GetString(FlagIdentity), - Website: viper.GetString(FlagWebsite), - Details: viper.GetString(FlagDetails), - } - - tx := stake.NewTxDeclareCandidacy(amount, pk, description) - return txcmd.DoTx(tx) -} - -func cmdEditCandidacy(cmd *cobra.Command, args []string) error { - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - description := stake.Description{ - Moniker: viper.GetString(FlagMoniker), - Identity: viper.GetString(FlagIdentity), - Website: viper.GetString(FlagWebsite), - Details: viper.GetString(FlagDetails), - } - - tx := stake.NewTxEditCandidacy(pk, description) - return txcmd.DoTx(tx) -} - -func cmdDelegate(cmd *cobra.Command, args []string) error { - amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) - if err != nil { - return err - } - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - tx := stake.NewTxDelegate(amount, pk) - return txcmd.DoTx(tx) -} - -func cmdUnbond(cmd *cobra.Command, args []string) error { - - // TODO once go-wire refactored the shares can be broadcast as a Rat instead of a string - - // check the shares before broadcasting - sharesStr := viper.GetString(FlagShares) - var shares rational.Rat - if sharesStr != "MAX" { - var err error - shares, err = rational.NewFromDecimal(sharesStr) - if err != nil { - return err - } - if !shares.GT(rational.Zero) { - return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") - } - } - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - tx := stake.NewTxUnbond(sharesStr, pk) - return txcmd.DoTx(tx) -} - -// GetPubKey - create the pubkey from a pubkey string -func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { - - if len(pubKeyStr) == 0 { - err = fmt.Errorf("must use --pubkey flag") - return - } - if len(pubKeyStr) != 64 { //if len(pkBytes) != 32 { - err = fmt.Errorf("pubkey must be Ed25519 hex encoded string which is 64 characters long") - return - } - var pkBytes []byte - pkBytes, err = hex.DecodeString(pubKeyStr) - if err != nil { - return - } - var pkEd crypto.PubKeyEd25519 - copy(pkEd[:], pkBytes[:]) - pk = pkEd.Wrap() - return -} diff --git a/x/stake/errors.go b/x/stake/errors.go deleted file mode 100644 index 900077b125..0000000000 --- a/x/stake/errors.go +++ /dev/null @@ -1,53 +0,0 @@ -// nolint -package stake - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/errors" -) - -var ( - errCandidateEmpty = fmt.Errorf("Cannot bond to an empty candidate") - errBadBondingDenom = fmt.Errorf("Invalid coin denomination") - errBadBondingAmount = fmt.Errorf("Amount must be > 0") - errNoBondingAcct = fmt.Errorf("No bond account for this (address, validator) pair") - errCommissionNegative = fmt.Errorf("Commission must be positive") - errCommissionHuge = fmt.Errorf("Commission cannot be more than 100%") - - errBadValidatorAddr = fmt.Errorf("Validator does not exist for that address") - errCandidateExistsAddr = fmt.Errorf("Candidate already exist, cannot re-declare candidacy") - errMissingSignature = fmt.Errorf("Missing signature") - errBondNotNominated = fmt.Errorf("Cannot bond to non-nominated account") - errNoCandidateForAddress = fmt.Errorf("Validator does not exist for that address") - errNoDelegatorForAddress = fmt.Errorf("Delegator does not contain validator bond") - errInsufficientFunds = fmt.Errorf("Insufficient bond shares") - errBadRemoveValidator = fmt.Errorf("Error removing validator") - - invalidInput = errors.CodeTypeBaseInvalidInput -) - -func ErrBadValidatorAddr() error { - return errors.WithCode(errBadValidatorAddr, errors.CodeTypeBaseUnknownAddress) -} -func ErrCandidateExistsAddr() error { - return errors.WithCode(errCandidateExistsAddr, errors.CodeTypeBaseInvalidInput) -} -func ErrMissingSignature() error { - return errors.WithCode(errMissingSignature, errors.CodeTypeUnauthorized) -} -func ErrBondNotNominated() error { - return errors.WithCode(errBondNotNominated, errors.CodeTypeBaseInvalidOutput) -} -func ErrNoCandidateForAddress() error { - return errors.WithCode(errNoCandidateForAddress, errors.CodeTypeBaseUnknownAddress) -} -func ErrNoDelegatorForAddress() error { - return errors.WithCode(errNoDelegatorForAddress, errors.CodeTypeBaseInvalidInput) -} -func ErrInsufficientFunds() error { - return errors.WithCode(errInsufficientFunds, errors.CodeTypeBaseInvalidInput) -} -func ErrBadRemoveValidator() error { - return errors.WithCode(errBadRemoveValidator, errors.CodeTypeInternalErr) -} diff --git a/x/stake/handler.go b/x/stake/handler.go deleted file mode 100644 index c60b408017..0000000000 --- a/x/stake/handler.go +++ /dev/null @@ -1,529 +0,0 @@ -package stake - -import ( - "fmt" - "strconv" - - "github.com/spf13/viper" - "github.com/tendermint/tmlibs/log" - "github.com/tendermint/tmlibs/rational" - - "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/errors" - "github.com/cosmos/cosmos-sdk/modules/auth" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/stack" - "github.com/cosmos/cosmos-sdk/state" -) - -// nolint -const stakingModuleName = "stake" - -// Name is the name of the modules. -func Name() string { - return stakingModuleName -} - -//_______________________________________________________________________ - -// DelegatedProofOfStake - interface to enforce delegation stake -type delegatedProofOfStake interface { - declareCandidacy(TxDeclareCandidacy) error - editCandidacy(TxEditCandidacy) error - delegate(TxDelegate) error - unbond(TxUnbond) error -} - -type coinSend interface { - transferFn(sender, receiver sdk.Actor, coins coin.Coins) error -} - -//_______________________________________________________________________ - -// Handler - the transaction processing handler -type Handler struct { - stack.PassInitValidate -} - -var _ stack.Dispatchable = Handler{} // enforce interface at compile time - -// NewHandler returns a new Handler with the default Params -func NewHandler() Handler { - return Handler{} -} - -// Name - return stake namespace -func (Handler) Name() string { - return stakingModuleName -} - -// AssertDispatcher - placeholder for stack.Dispatchable -func (Handler) AssertDispatcher() {} - -// InitState - set genesis parameters for staking -func (h Handler) InitState(l log.Logger, store state.SimpleDB, - module, key, value string, cb sdk.InitStater) (log string, err error) { - return "", h.initState(module, key, value, store) -} - -// separated for testing -func (Handler) initState(module, key, value string, store state.SimpleDB) error { - if module != stakingModuleName { - return errors.ErrUnknownModule(module) - } - - params := loadParams(store) - switch key { - case "allowed_bond_denom": - params.AllowedBondDenom = value - case "max_vals", - "gas_bond", - "gas_unbond": - - // TODO: enforce non-negative integers in input - i, err := strconv.Atoi(value) - if err != nil { - return fmt.Errorf("input must be integer, Error: %v", err.Error()) - } - - switch key { - case "max_vals": - params.MaxVals = uint16(i) - case "gas_bond": - params.GasDelegate = int64(i) - case "gas_unbound": - params.GasUnbond = int64(i) - } - default: - return errors.ErrUnknownKey(key) - } - - saveParams(store, params) - return nil -} - -// CheckTx checks if the tx is properly structured -func (h Handler) CheckTx(ctx sdk.Context, store state.SimpleDB, - tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) { - - err = tx.ValidateBasic() - if err != nil { - return res, err - } - - // get the sender - sender, err := getTxSender(ctx) - if err != nil { - return res, err - } - - params := loadParams(store) - - // create the new checker object to - checker := check{ - store: store, - sender: sender, - } - - // return the fee for each tx type - switch txInner := tx.Unwrap().(type) { - case TxDeclareCandidacy: - return sdk.NewCheck(params.GasDeclareCandidacy, ""), - checker.declareCandidacy(txInner) - case TxEditCandidacy: - return sdk.NewCheck(params.GasEditCandidacy, ""), - checker.editCandidacy(txInner) - case TxDelegate: - return sdk.NewCheck(params.GasDelegate, ""), - checker.delegate(txInner) - case TxUnbond: - return sdk.NewCheck(params.GasUnbond, ""), - checker.unbond(txInner) - } - - return res, errors.ErrUnknownTxType(tx) -} - -// DeliverTx executes the tx if valid -func (h Handler) DeliverTx(ctx sdk.Context, store state.SimpleDB, - tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) { - - // TODO: remove redundancy - // also we don't need to check the res - gas is already deducted in sdk - _, err = h.CheckTx(ctx, store, tx, nil) - if err != nil { - return - } - - sender, err := getTxSender(ctx) - if err != nil { - return - } - - params := loadParams(store) - deliverer := deliver{ - store: store, - sender: sender, - params: params, - transfer: coinSender{ - store: store, - dispatch: dispatch, - ctx: ctx, - }.transferFn, - } - - // Run the transaction - switch _tx := tx.Unwrap().(type) { - case TxDeclareCandidacy: - res.GasUsed = params.GasDeclareCandidacy - return res, deliverer.declareCandidacy(_tx) - case TxEditCandidacy: - res.GasUsed = params.GasEditCandidacy - return res, deliverer.editCandidacy(_tx) - case TxDelegate: - res.GasUsed = params.GasDelegate - return res, deliverer.delegate(_tx) - case TxUnbond: - //context with hold account permissions - params := loadParams(store) - res.GasUsed = params.GasUnbond - ctx2 := ctx.WithPermissions(params.HoldBonded) - deliverer.transfer = coinSender{ - store: store, - dispatch: dispatch, - ctx: ctx2, - }.transferFn - return res, deliverer.unbond(_tx) - } - return -} - -// get the sender from the ctx and ensure it matches the tx pubkey -func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) { - senders := ctx.GetPermissions("", auth.NameSigs) - if len(senders) != 1 { - return sender, ErrMissingSignature() - } - return senders[0], nil -} - -//_______________________________________________________________________ - -type coinSender struct { - store state.SimpleDB - dispatch sdk.Deliver - ctx sdk.Context -} - -var _ coinSend = coinSender{} // enforce interface at compile time - -func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { - send := coin.NewSendOneTx(sender, receiver, coins) - - // If the deduction fails (too high), abort the command - _, err := c.dispatch.DeliverTx(c.ctx, c.store, send) - return err -} - -//_____________________________________________________________________ - -type check struct { - store state.SimpleDB - sender sdk.Actor -} - -var _ delegatedProofOfStake = check{} // enforce interface at compile time - -func (c check) declareCandidacy(tx TxDeclareCandidacy) error { - - // check to see if the pubkey or sender has been registered before - candidate := loadCandidate(c.store, tx.PubKey) - if candidate != nil { - return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ - " PubKey %v already registered with %v candidate address", - candidate.PubKey, candidate.Owner) - } - - return checkDenom(tx.BondUpdate, c.store) -} - -func (c check) editCandidacy(tx TxEditCandidacy) error { - - // candidate must already be registered - candidate := loadCandidate(c.store, tx.PubKey) - if candidate == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) - } - return nil -} - -func (c check) delegate(tx TxDelegate) error { - - candidate := loadCandidate(c.store, tx.PubKey) - if candidate == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) - } - return checkDenom(tx.BondUpdate, c.store) -} - -func (c check) unbond(tx TxUnbond) error { - - // check if bond has any shares in it unbond - bond := loadDelegatorBond(c.store, c.sender, tx.PubKey) - sharesStr := viper.GetString(tx.Shares) - if bond.Shares.LT(rational.Zero) { // bond shares < tx shares - return fmt.Errorf("no shares in account to unbond") - } - - // if shares set to maximum shares then we're good - if sharesStr == "MAX" { - return nil - } - - // test getting rational number from decimal provided - shares, err := rational.NewFromDecimal(sharesStr) - if err != nil { - return err - } - - // test that there are enough shares to unbond - if bond.Shares.LT(shares) { - return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", - bond.Shares, tx.Shares) - } - return nil -} - -func checkDenom(tx BondUpdate, store state.SimpleDB) error { - if tx.Bond.Denom != loadParams(store).AllowedBondDenom { - return fmt.Errorf("Invalid coin denomination") - } - return nil -} - -//_____________________________________________________________________ - -type deliver struct { - store state.SimpleDB - sender sdk.Actor - params Params - gs *GlobalState - transfer transferFn -} - -type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error - -var _ delegatedProofOfStake = deliver{} // enforce interface at compile time - -//_____________________________________________________________________ -// deliver helper functions - -// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK - -// move a candidates asset pool from bonded to unbonded pool -func (d deliver) bondedToUnbondedPool(candidate *Candidate) error { - - // replace bonded shares with unbonded shares - tokens := d.gs.removeSharesBonded(candidate.Assets) - candidate.Assets = d.gs.addTokensUnbonded(tokens) - candidate.Status = Unbonded - - return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded, - coin.Coins{{d.params.AllowedBondDenom, tokens}}) -} - -// move a candidates asset pool from unbonded to bonded pool -func (d deliver) unbondedToBondedPool(candidate *Candidate) error { - - // replace bonded shares with unbonded shares - tokens := d.gs.removeSharesUnbonded(candidate.Assets) - candidate.Assets = d.gs.addTokensBonded(tokens) - candidate.Status = Bonded - - return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded, - coin.Coins{{d.params.AllowedBondDenom, tokens}}) -} - -//_____________________________________________________________________ - -// These functions assume everything has been authenticated, -// now we just perform action and save -func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { - - // create and save the empty candidate - bond := loadCandidate(d.store, tx.PubKey) - if bond != nil { - return ErrCandidateExistsAddr() - } - candidate := NewCandidate(tx.PubKey, d.sender, tx.Description) - saveCandidate(d.store, candidate) - - // move coins from the d.sender account to a (self-bond) delegator account - // the candidate account and global shares are updated within here - txDelegate := TxDelegate{tx.BondUpdate} - return d.delegateWithCandidate(txDelegate, candidate) -} - -func (d deliver) editCandidacy(tx TxEditCandidacy) error { - - // Get the pubKey bond account - candidate := loadCandidate(d.store, tx.PubKey) - if candidate == nil { - return ErrBondNotNominated() - } - if candidate.Status == Unbonded { //candidate has been withdrawn - return ErrBondNotNominated() - } - - //check and edit any of the editable terms - if tx.Description.Moniker != "" { - candidate.Description.Moniker = tx.Description.Moniker - } - if tx.Description.Identity != "" { - candidate.Description.Identity = tx.Description.Identity - } - if tx.Description.Website != "" { - candidate.Description.Website = tx.Description.Website - } - if tx.Description.Details != "" { - candidate.Description.Details = tx.Description.Details - } - - saveCandidate(d.store, candidate) - return nil -} - -func (d deliver) delegate(tx TxDelegate) error { - // Get the pubKey bond account - candidate := loadCandidate(d.store, tx.PubKey) - if candidate == nil { - return ErrBondNotNominated() - } - return d.delegateWithCandidate(tx, candidate) -} - -func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { - - if candidate.Status == Revoked { //candidate has been withdrawn - return ErrBondNotNominated() - } - - var poolAccount sdk.Actor - if candidate.Status == Bonded { - poolAccount = d.params.HoldBonded - } else { - poolAccount = d.params.HoldUnbonded - } - - // TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK - // Move coins from the delegator account to the bonded pool account - err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond}) - if err != nil { - return err - } - - // Get or create the delegator bond - bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) - if bond == nil { - bond = &DelegatorBond{ - PubKey: tx.PubKey, - Shares: rational.Zero, - } - } - - // Account new shares, save - bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs)) - saveCandidate(d.store, candidate) - saveDelegatorBond(d.store, d.sender, bond) - saveGlobalState(d.store, d.gs) - return nil -} - -func (d deliver) unbond(tx TxUnbond) error { - - // get delegator bond - bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) - if bond == nil { - return ErrNoDelegatorForAddress() - } - - // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - var shares rational.Rat - if tx.Shares == "MAX" { - shares = bond.Shares - } else { - var err error - shares, err = rational.NewFromDecimal(tx.Shares) - if err != nil { - return err - } - } - - // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { // bond shares < tx shares - return ErrInsufficientFunds() - } - bond.Shares = bond.Shares.Sub(shares) - - // get pubKey candidate - candidate := loadCandidate(d.store, tx.PubKey) - if candidate == nil { - return ErrNoCandidateForAddress() - } - - revokeCandidacy := false - if bond.Shares.IsZero() { - - // if the bond is the owner of the candidate then - // trigger a revoke candidacy - if d.sender.Equals(candidate.Owner) && - candidate.Status != Revoked { - revokeCandidacy = true - } - - // remove the bond - removeDelegatorBond(d.store, d.sender, tx.PubKey) - } else { - saveDelegatorBond(d.store, d.sender, bond) - } - - // transfer coins back to account - var poolAccount sdk.Actor - if candidate.Status == Bonded { - poolAccount = d.params.HoldBonded - } else { - poolAccount = d.params.HoldUnbonded - } - - returnCoins := candidate.removeShares(shares, d.gs) - err := d.transfer(poolAccount, d.sender, - coin.Coins{{d.params.AllowedBondDenom, returnCoins}}) - if err != nil { - return err - } - - // lastly if an revoke candidate if necessary - if revokeCandidacy { - - // change the share types to unbonded if they were not already - if candidate.Status == Bonded { - err = d.bondedToUnbondedPool(candidate) - if err != nil { - return err - } - } - - // lastly update the status - candidate.Status = Revoked - } - - // deduct shares from the candidate and save - if candidate.Liabilities.IsZero() { - removeCandidate(d.store, tx.PubKey) - } else { - saveCandidate(d.store, candidate) - } - - saveGlobalState(d.store, d.gs) - return nil -} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go deleted file mode 100644 index 690e7710cf..0000000000 --- a/x/stake/handler_test.go +++ /dev/null @@ -1,336 +0,0 @@ -package stake - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/state" -) - -//______________________________________________________________________ - -// dummy transfer functions, represents store operations on account balances - -type testCoinSender struct { - store map[string]int64 -} - -var _ coinSend = testCoinSender{} // enforce interface at compile time - -func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { - c.store[string(sender.Address)] -= coins[0].Amount - c.store[string(receiver.Address)] += coins[0].Amount - return nil -} - -//______________________________________________________________________ - -func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) { - accStore := map[string]int64{} - senders := newActors(n) - for _, sender := range senders { - accStore[string(sender.Address)] = amount - } - return senders, accStore -} - -func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy { - return TxDeclareCandidacy{ - BondUpdate{ - PubKey: pubKey, - Bond: coin.Coin{"fermion", amt}, - }, - Description{}, - } -} - -func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate { - return TxDelegate{BondUpdate{ - PubKey: pubKey, - Bond: coin.Coin{"fermion", amt}, - }} -} - -func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond { - return TxUnbond{ - PubKey: pubKey, - Shares: shares, - } -} - -func paramsNoInflation() Params { - return Params{ - HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), - HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), - InflationRateChange: rational.Zero, - InflationMax: rational.Zero, - InflationMin: rational.Zero, - GoalBonded: rational.New(67, 100), - MaxVals: 100, - AllowedBondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, - } -} - -func newDeliver(sender sdk.Actor, accStore map[string]int64) deliver { - store := state.NewMemKVStore() - params := paramsNoInflation() - saveParams(store, params) - return deliver{ - store: store, - sender: sender, - params: params, - gs: loadGlobalState(store), - transfer: testCoinSender{accStore}.transferFn, - } -} - -func TestDuplicatesTxDeclareCandidacy(t *testing.T) { - assert := assert.New(t) - senders, accStore := initAccounts(2, 1000) // for accounts - - deliverer := newDeliver(senders[0], accStore) - checker := check{ - store: deliverer.store, - sender: senders[0], - } - - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) - got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(got, "expected no error on runTxDeclareCandidacy") - - // one sender can bond to two different pubKeys - txDeclareCandidacy.PubKey = pks[1] - err := checker.declareCandidacy(txDeclareCandidacy) - assert.Nil(err, "didn't expected error on checkTx") - - // two senders cant bond to the same pubkey - checker.sender = senders[1] - txDeclareCandidacy.PubKey = pks[0] - err = checker.declareCandidacy(txDeclareCandidacy) - assert.NotNil(err, "expected error on checkTx") -} - -func TestIncrementsTxDelegate(t *testing.T) { - assert := assert.New(t) - initSender := int64(1000) - senders, accStore := initAccounts(1, initSender) // for accounts - deliverer := newDeliver(senders[0], accStore) - - // first declare candidacy - bondAmount := int64(10) - txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0]) - got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(got, "expected declare candidacy tx to be ok, got %v", got) - expectedBond := bondAmount // 1 since we send 1 at the start of loop, - - // just send the same txbond multiple times - holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates - txDelegate := newTxDelegate(bondAmount, pks[0]) - for i := 0; i < 5; i++ { - got := deliverer.delegate(txDelegate) - assert.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the accounts and the bond account have the appropriate values - candidates := loadCandidates(deliverer.store) - expectedBond += bondAmount - expectedSender := initSender - expectedBond - gotBonded := candidates[0].Liabilities.Evaluate() - gotHolder := accStore[string(holder.Address)] - gotSender := accStore[string(deliverer.sender.Address)] - assert.Equal(expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) - assert.Equal(expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder) - assert.Equal(expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) - } -} - -func TestIncrementsTxUnbond(t *testing.T) { - assert := assert.New(t) - initSender := int64(0) - senders, accStore := initAccounts(1, initSender) // for accounts - deliverer := newDeliver(senders[0], accStore) - - // set initial bond - initBond := int64(1000) - accStore[string(deliverer.sender.Address)] = initBond - got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0])) - assert.NoError(got, "expected initial bond tx to be ok, got %v", got) - - // just send the same txunbond multiple times - holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded - - // XXX use decimals here - unbondShares, unbondSharesStr := int64(10), "10" - txUndelegate := newTxUnbond(unbondSharesStr, pks[0]) - nUnbonds := 5 - for i := 0; i < nUnbonds; i++ { - got := deliverer.unbond(txUndelegate) - assert.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the accounts and the bond account have the appropriate values - candidates := loadCandidates(deliverer.store) - 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() - gotHolder := accStore[string(holder.Address)] - gotSender := accStore[string(deliverer.sender.Address)] - - assert.Equal(expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) - assert.Equal(expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder) - assert.Equal(expectedSender, gotSender, "%v, %v", expectedSender, gotSender) - } - - // 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)) - txUndelegate := newTxUnbond(unbondShares, pks[0]) - got = deliverer.unbond(txUndelegate) - assert.Error(got, "expected unbond tx to fail") - } - - leftBonded := initBond - unbondShares*int64(nUnbonds) - - // should be unable to unbond one more than we have - txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) - got = deliverer.unbond(txUndelegate) - assert.Error(got, "expected unbond tx to fail") - - // should be able to unbond just what we have - txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0]) - got = deliverer.unbond(txUndelegate) - assert.NoError(got, "expected unbond tx to pass") -} - -func TestMultipleTxDeclareCandidacy(t *testing.T) { - assert := assert.New(t) - initSender := int64(1000) - senders, accStore := initAccounts(3, initSender) - pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]} - deliverer := newDeliver(senders[0], accStore) - - // bond them all - for i, sender := range senders { - txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i]) - deliverer.sender = sender - got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the account is bonded - candidates := loadCandidates(deliverer.store) - val := candidates[i] - balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10 - assert.Equal(i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) - assert.Equal(10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) - assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) - } - - // unbond them all - for i, sender := range senders { - candidatePre := loadCandidate(deliverer.store, pubKeys[i]) - txUndelegate := newTxUnbond("10", pubKeys[i]) - deliverer.sender = sender - got := deliverer.unbond(txUndelegate) - assert.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the account is unbonded - candidates := loadCandidates(deliverer.store) - assert.Equal(len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates)) - - candidatePost := loadCandidate(deliverer.store, pubKeys[i]) - balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender - assert.Nil(candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) - assert.Equal(balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) - } -} - -func TestMultipleTxDelegate(t *testing.T) { - assert, require := assert.New(t), require.New(t) - accounts, accStore := initAccounts(3, 1000) - sender, delegators := accounts[0], accounts[1:] - deliverer := newDeliver(sender, accStore) - - //first make a candidate - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) - got := deliverer.declareCandidacy(txDeclareCandidacy) - require.NoError(got, "expected tx to be ok, got %v", got) - - // delegate multiple parties - for i, delegator := range delegators { - txDelegate := newTxDelegate(10, pks[0]) - deliverer.sender = delegator - got := deliverer.delegate(txDelegate) - require.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the account is bonded - bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) - assert.NotNil(bond, "expected delegatee bond %d to exist", bond) - } - - // unbond them all - for i, delegator := range delegators { - txUndelegate := newTxUnbond("10", pks[0]) - deliverer.sender = delegator - got := deliverer.unbond(txUndelegate) - require.NoError(got, "expected tx %d to be ok, got %v", i, got) - - //Check that the account is unbonded - bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) - assert.Nil(bond, "expected delegatee bond %d to be nil", bond) - } -} - -func TestVoidCandidacy(t *testing.T) { - assert, require := assert.New(t), require.New(t) - accounts, accStore := initAccounts(2, 1000) // for accounts - sender, delegator := accounts[0], accounts[1] - deliverer := newDeliver(sender, accStore) - - // create the candidate - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) - got := deliverer.declareCandidacy(txDeclareCandidacy) - require.NoError(got, "expected no error on runTxDeclareCandidacy") - - // bond a delegator - txDelegate := newTxDelegate(10, pks[0]) - deliverer.sender = delegator - got = deliverer.delegate(txDelegate) - require.NoError(got, "expected ok, got %v", got) - - // unbond the candidates bond portion - txUndelegate := newTxUnbond("10", pks[0]) - deliverer.sender = sender - got = deliverer.unbond(txUndelegate) - require.NoError(got, "expected no error on runTxDeclareCandidacy") - - // test that this pubkey cannot yet be bonded too - deliverer.sender = delegator - got = deliverer.delegate(txDelegate) - assert.Error(got, "expected error, got %v", got) - - // test that the delegator can still withdraw their bonds - got = deliverer.unbond(txUndelegate) - require.NoError(got, "expected no error on runTxDeclareCandidacy") - - // verify that the pubkey can now be reused - got = deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(got, "expected ok, got %v", got) - -} diff --git a/x/stake/rest/query.go b/x/stake/rest/query.go deleted file mode 100644 index 48e54cfe81..0000000000 --- a/x/stake/rest/query.go +++ /dev/null @@ -1,188 +0,0 @@ -package rest - -import ( - "fmt" - "net/http" - - "github.com/gorilla/mux" - "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/commands" - "github.com/cosmos/cosmos-sdk/client/commands/query" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/stack" - - "github.com/cosmos/gaia/modules/stake" - scmds "github.com/cosmos/gaia/modules/stake/commands" - - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/common" -) - -// RegisterQueryCandidate is a mux.Router handler that exposes GET -// method access on route /query/stake/candidate/{pubkey} to query a candidate -func RegisterQueryCandidate(r *mux.Router) error { - r.HandleFunc("/query/stake/candidate/{pubkey}", queryCandidate).Methods("GET") - return nil -} - -// RegisterQueryCandidates is a mux.Router handler that exposes GET -// method access on route /query/stake/candidate to query the group of all candidates -func RegisterQueryCandidates(r *mux.Router) error { - r.HandleFunc("/query/stake/candidates", queryCandidates).Methods("GET") - return nil -} - -// RegisterQueryDelegatorBond is a mux.Router handler that exposes GET -// method access on route /query/stake/candidate/{pubkey} to query a candidate -func RegisterQueryDelegatorBond(r *mux.Router) error { - r.HandleFunc("/query/stake/delegator/{address}/{pubkey}", queryDelegatorBond).Methods("GET") - return nil -} - -// RegisterQueryDelegatorCandidates is a mux.Router handler that exposes GET -// method access on route /query/stake/candidate to query the group of all candidates -func RegisterQueryDelegatorCandidates(r *mux.Router) error { - r.HandleFunc("/query/stake/delegator_candidates/{address}", queryDelegatorCandidates).Methods("GET") - return nil -} - -//--------------------------------------------------------------------- - -// queryCandidate is the HTTP handlerfunc to query a candidate -// it expects a query string -func queryCandidate(w http.ResponseWriter, r *http.Request) { - - // get the arguments object - args := mux.Vars(r) - prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server - - // get the pubkey - pkArg := args["pubkey"] - pk, err := scmds.GetPubKey(pkArg) - if err != nil { - common.WriteError(w, err) - return - } - - // get the candidate - var candidate stake.Candidate - key := stack.PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) - height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) - if client.IsNoDataErr(err) { - err := fmt.Errorf("candidate bytes are empty for pubkey: %q", pkArg) - common.WriteError(w, err) - return - } else if err != nil { - common.WriteError(w, err) - return - } - - // write the output - err = query.FoutputProof(w, candidate, height) - if err != nil { - common.WriteError(w, err) - } -} - -// queryCandidates is the HTTP handlerfunc to query the group of all candidates -func queryCandidates(w http.ResponseWriter, r *http.Request) { - - var pks []crypto.PubKey - - prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server - key := stack.PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) - height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) - if err != nil { - common.WriteError(w, err) - return - } - - err = query.FoutputProof(w, pks, height) - if err != nil { - common.WriteError(w, err) - } -} - -// queryDelegatorBond is the HTTP handlerfunc to query a delegator bond it -// expects a query string -func queryDelegatorBond(w http.ResponseWriter, r *http.Request) { - - // get the arguments object - args := mux.Vars(r) - prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server - - // get the pubkey - pkArg := args["pubkey"] - pk, err := scmds.GetPubKey(pkArg) - if err != nil { - common.WriteError(w, err) - return - } - - // get the delegator actor - delegatorAddr := args["address"] - delegator, err := commands.ParseActor(delegatorAddr) - if err != nil { - common.WriteError(w, err) - return - } - delegator = coin.ChainAddr(delegator) - - // get the bond - var bond stake.DelegatorBond - key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) - height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) - if client.IsNoDataErr(err) { - err := fmt.Errorf("bond bytes are empty for pubkey: %q, address: %q", pkArg, delegatorAddr) - common.WriteError(w, err) - return - } else if err != nil { - common.WriteError(w, err) - return - } - - // write the output - err = query.FoutputProof(w, bond, height) - if err != nil { - common.WriteError(w, err) - } -} - -// queryDelegatorCandidates is the HTTP handlerfunc to query a delegator bond it -// expects a query string -func queryDelegatorCandidates(w http.ResponseWriter, r *http.Request) { - - // get the arguments object - args := mux.Vars(r) - prove := !viper.GetBool(commands.FlagTrustNode) // from viper because defined when starting server - - // get the delegator actor - delegatorAddr := args["address"] - delegator, err := commands.ParseActor(delegatorAddr) - if err != nil { - common.WriteError(w, err) - return - } - delegator = coin.ChainAddr(delegator) - - // get the bond - var bond stake.DelegatorBond - key := stack.PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) - height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) - if client.IsNoDataErr(err) { - err := fmt.Errorf("bond bytes are empty for address: %q", delegatorAddr) - common.WriteError(w, err) - return - } else if err != nil { - common.WriteError(w, err) - return - } - - // write the output - err = query.FoutputProof(w, bond, height) - if err != nil { - common.WriteError(w, err) - } -} diff --git a/x/stake/rest/tx.go b/x/stake/rest/tx.go deleted file mode 100644 index 090ea5abbc..0000000000 --- a/x/stake/rest/tx.go +++ /dev/null @@ -1,159 +0,0 @@ -package rest - -import ( - "net/http" - "strings" - - "github.com/gorilla/mux" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/common" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/client/commands" - "github.com/cosmos/cosmos-sdk/modules/auth" - "github.com/cosmos/cosmos-sdk/modules/base" - "github.com/cosmos/cosmos-sdk/modules/coin" - "github.com/cosmos/cosmos-sdk/modules/fee" - "github.com/cosmos/cosmos-sdk/modules/nonce" - "github.com/cosmos/gaia/modules/stake" -) - -const ( - //parameters used in urls - paramPubKey = "pubkey" - paramAmount = "amount" - paramShares = "shares" - - paramName = "name" - paramKeybase = "keybase" - paramWebsite = "website" - paramDetails = "details" -) - -type delegateInput struct { - Fees *coin.Coin `json:"fees"` - Sequence uint32 `json:"sequence"` - - Pubkey crypto.PubKey `json:"pub_key"` - From *sdk.Actor `json:"from"` - Amount coin.Coin `json:"amount"` -} - -type unbondInput struct { - Fees *coin.Coin `json:"fees"` - Sequence uint32 `json:"sequence"` - - Pubkey crypto.PubKey `json:"pub_key"` - From *sdk.Actor `json:"from"` - Shares string `json:"amount"` -} - -// RegisterDelegate is a mux.Router handler that exposes -// POST method access on route /tx/stake/delegate to create a -// transaction for delegate to a candidaate/validator -func RegisterDelegate(r *mux.Router) error { - r.HandleFunc("/build/stake/delegate", delegate).Methods("POST") - return nil -} - -// RegisterUnbond is a mux.Router handler that exposes -// POST method access on route /tx/stake/unbond to create a -// transaction for unbonding delegated coins -func RegisterUnbond(r *mux.Router) error { - r.HandleFunc("/build/stake/unbond", unbond).Methods("POST") - return nil -} - -func prepareDelegateTx(di *delegateInput) sdk.Tx { - tx := stake.NewTxDelegate(di.Amount, di.Pubkey) - // fees are optional - if di.Fees != nil && !di.Fees.IsZero() { - tx = fee.NewFee(tx, *di.Fees, *di.From) - } - // only add the actual signer to the nonce - signers := []sdk.Actor{*di.From} - tx = nonce.NewTx(di.Sequence, signers, tx) - tx = base.NewChainTx(commands.GetChainID(), 0, tx) - - tx = auth.NewSig(tx).Wrap() - return tx -} - -func delegate(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - di := new(delegateInput) - if err := common.ParseRequestAndValidateJSON(r, di); err != nil { - common.WriteError(w, err) - return - } - - var errsList []string - if di.From == nil { - errsList = append(errsList, `"from" cannot be nil`) - } - if di.Sequence <= 0 { - errsList = append(errsList, `"sequence" must be > 0`) - } - if di.Pubkey.Empty() { - errsList = append(errsList, `"pubkey" cannot be empty`) - } - if len(errsList) > 0 { - code := http.StatusBadRequest - err := &common.ErrorResponse{ - Err: strings.Join(errsList, ", "), - Code: code, - } - common.WriteCode(w, err, code) - return - } - - tx := prepareDelegateTx(di) - common.WriteSuccess(w, tx) -} - -func prepareUnbondTx(ui *unbondInput) sdk.Tx { - tx := stake.NewTxUnbond(ui.Shares, ui.Pubkey) - // fees are optional - if ui.Fees != nil && !ui.Fees.IsZero() { - tx = fee.NewFee(tx, *ui.Fees, *ui.From) - } - // only add the actual signer to the nonce - signers := []sdk.Actor{*ui.From} - tx = nonce.NewTx(ui.Sequence, signers, tx) - tx = base.NewChainTx(commands.GetChainID(), 0, tx) - - tx = auth.NewSig(tx).Wrap() - return tx -} - -func unbond(w http.ResponseWriter, r *http.Request) { - defer r.Body.Close() - ui := new(unbondInput) - if err := common.ParseRequestAndValidateJSON(r, ui); err != nil { - common.WriteError(w, err) - return - } - - var errsList []string - if ui.From == nil { - errsList = append(errsList, `"from" cannot be nil`) - } - if ui.Sequence <= 0 { - errsList = append(errsList, `"sequence" must be > 0`) - } - if ui.Pubkey.Empty() { - errsList = append(errsList, `"pubkey" cannot be empty`) - } - if len(errsList) > 0 { - code := http.StatusBadRequest - err := &common.ErrorResponse{ - Err: strings.Join(errsList, ", "), - Code: code, - } - common.WriteCode(w, err, code) - return - } - - tx := prepareUnbondTx(ui) - common.WriteSuccess(w, tx) -} diff --git a/x/stake/state.go b/x/stake/store.go similarity index 71% rename from x/stake/state.go rename to x/stake/store.go index e037d837b2..f62aa46e49 100644 --- a/x/stake/state.go +++ b/x/stake/store.go @@ -6,8 +6,7 @@ import ( crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/state" + "github.com/cosmos/cosmos-sdk/types" ) // nolint @@ -29,41 +28,52 @@ func GetCandidateKey(pubKey crypto.PubKey) []byte { } // GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegator sdk.Actor, candidate crypto.PubKey) []byte { +func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte { return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) } // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegator sdk.Actor) []byte { - return append(DelegatorBondKeyPrefix, wire.BinaryBytes(&delegator)...) +func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { + res, err := wire.MarshalBinary(&delegator) + if err != nil { + panic(err) + } + return append(DelegatorBondKeyPrefix, res...) } // GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegator sdk.Actor) []byte { - return append(DelegatorBondsKeyPrefix, wire.BinaryBytes(&delegator)...) +func GetDelegatorBondsKey(delegator crypto.Address) []byte { + res, err := wire.MarshalBinary(&delegator) + if err != nil { + panic(err) + } + return append(DelegatorBondsKeyPrefix, res...) } //--------------------------------------------------------------------- // Get the active list of all the candidate pubKeys and owners -func loadCandidatesPubKeys(store state.SimpleDB) (pubKeys []crypto.PubKey) { +func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) { bytes := store.Get(CandidatesPubKeysKey) if bytes == nil { return } - err := wire.ReadBinaryBytes(bytes, &pubKeys) + err := wire.UnmarshalBinary(bytes, &pubKeys) if err != nil { panic(err) } return } -func saveCandidatesPubKeys(store state.SimpleDB, pubKeys []crypto.PubKey) { - b := wire.BinaryBytes(pubKeys) +func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) { + b, err := wire.MarshalBinary(pubKeys) + if err != nil { + panic(err) + } store.Set(CandidatesPubKeysKey, b) } // loadCandidates - get the active list of all candidates TODO replace with multistore -func loadCandidates(store state.SimpleDB) (candidates Candidates) { +func loadCandidates(store types.KVStore) (candidates Candidates) { pks := loadCandidatesPubKeys(store) for _, pk := range pks { candidates = append(candidates, loadCandidate(store, pk)) @@ -74,10 +84,10 @@ func loadCandidates(store state.SimpleDB) (candidates Candidates) { //--------------------------------------------------------------------- // loadCandidate - loads the candidate object for the provided pubkey -func loadCandidate(store state.SimpleDB, pubKey crypto.PubKey) *Candidate { - if pubKey.Empty() { - return nil - } +func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { + //if pubKey.Empty() { + //return nil + //} b := store.Get(GetCandidateKey(pubKey)) if b == nil { return nil @@ -90,7 +100,7 @@ func loadCandidate(store state.SimpleDB, pubKey crypto.PubKey) *Candidate { return candidate } -func saveCandidate(store state.SimpleDB, candidate *Candidate) { +func saveCandidate(store types.KVStore, candidate *Candidate) { if !store.Has(GetCandidateKey(candidate.PubKey)) { // TODO to be replaced with iteration in the multistore? @@ -105,8 +115,8 @@ func saveCandidate(store state.SimpleDB, candidate *Candidate) { store.Set(GetCandidateKey(candidate.PubKey), b) } -func removeCandidate(store state.SimpleDB, pubKey crypto.PubKey) { - store.Remove(GetCandidateKey(pubKey)) +func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { + store.Delete(GetCandidateKey(pubKey)) // TODO to be replaced with iteration in the multistore? pks := loadCandidatesPubKeys(store) @@ -122,15 +132,15 @@ func removeCandidate(store state.SimpleDB, pubKey crypto.PubKey) { //--------------------------------------------------------------------- // load the pubkeys of all candidates a delegator is delegated too -func loadDelegatorCandidates(store state.SimpleDB, - delegator sdk.Actor) (candidates []crypto.PubKey) { +func loadDelegatorCandidates(store types.KVStore, + delegator crypto.Address) (candidates []crypto.PubKey) { candidateBytes := store.Get(GetDelegatorBondsKey(delegator)) if candidateBytes == nil { return nil } - err := wire.ReadBinaryBytes(candidateBytes, &candidates) + err := wire.UnmarshalBinary(candidateBytes, &candidates) if err != nil { panic(err) } @@ -139,8 +149,8 @@ func loadDelegatorCandidates(store state.SimpleDB, //--------------------------------------------------------------------- -func loadDelegatorBond(store state.SimpleDB, - delegator sdk.Actor, candidate crypto.PubKey) *DelegatorBond { +func loadDelegatorBond(store types.KVStore, + delegator crypto.Address, candidate crypto.PubKey) *DelegatorBond { delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate)) if delegatorBytes == nil { @@ -155,13 +165,16 @@ func loadDelegatorBond(store state.SimpleDB, return bond } -func saveDelegatorBond(store state.SimpleDB, delegator sdk.Actor, bond *DelegatorBond) { +func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *DelegatorBond) { // if a new bond add to the list of bonds if loadDelegatorBond(store, delegator, bond.PubKey) == nil { pks := loadDelegatorCandidates(store, delegator) pks = append(pks, (*bond).PubKey) - b := wire.BinaryBytes(pks) + b, err := wire.MarshalBinary(pks) + if err != nil { + panic(err) + } store.Set(GetDelegatorBondsKey(delegator), b) } @@ -174,7 +187,7 @@ func saveDelegatorBond(store state.SimpleDB, delegator sdk.Actor, bond *Delegato //updateDelegatorBonds(store, delegator) } -func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate crypto.PubKey) { +func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidate crypto.PubKey) { // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds @@ -184,16 +197,19 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr pks = append(pks[:i], pks[i+1:]...) } } - b := wire.BinaryBytes(pks) + b, err := wire.MarshalBinary(pks) + if err != nil { + panic(err) + } store.Set(GetDelegatorBondsKey(delegator), b) // now remove the actual bond - store.Remove(GetDelegatorBondKey(delegator, candidate)) + store.Delete(GetDelegatorBondKey(delegator, candidate)) //updateDelegatorBonds(store, delegator) } -//func updateDelegatorBonds(store state.SimpleDB, -//delegator sdk.Actor) { +//func updateDelegatorBonds(store types.KVStore, +//delegator crypto.Address) { //var bonds []*DelegatorBond @@ -209,7 +225,7 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr //} //bond := new(DelegatorBond) -//err := wire.ReadBinaryBytes(delegatorBytes, bond) +//err := wire.UnmarshalBinary(delegatorBytes, bond) //if err != nil { //panic(err) //} @@ -221,14 +237,14 @@ func removeDelegatorBond(store state.SimpleDB, delegator sdk.Actor, candidate cr //return //} -//b := wire.BinaryBytes(bonds) +//b := wire.MarshalBinary(bonds) //store.Set(GetDelegatorBondsKey(delegator), b) //} //_______________________________________________________________________ // load/save the global staking params -func loadParams(store state.SimpleDB) (params Params) { +func loadParams(store types.KVStore) (params Params) { b := store.Get(ParamKey) if b == nil { return defaultParams() @@ -241,7 +257,7 @@ func loadParams(store state.SimpleDB) (params Params) { return } -func saveParams(store state.SimpleDB, params Params) { +func saveParams(store types.KVStore, params Params) { b, err := json.Marshal(params) if err != nil { panic(err) @@ -252,7 +268,7 @@ func saveParams(store state.SimpleDB, params Params) { //_______________________________________________________________________ // load/save the global staking state -func loadGlobalState(store state.SimpleDB) (gs *GlobalState) { +func loadGlobalState(store types.KVStore) (gs *GlobalState) { b := store.Get(GlobalStateKey) if b == nil { return initialGlobalState() @@ -264,7 +280,7 @@ func loadGlobalState(store state.SimpleDB) (gs *GlobalState) { } return } -func saveGlobalState(store state.SimpleDB, gs *GlobalState) { +func saveGlobalState(store types.KVStore, gs *GlobalState) { b, err := json.Marshal(*gs) if err != nil { panic(err) diff --git a/x/stake/state_test.go b/x/stake/store_test.go similarity index 70% rename from x/stake/state_test.go rename to x/stake/store_test.go index 919dfc4c44..d2c6839cb3 100644 --- a/x/stake/state_test.go +++ b/x/stake/store_test.go @@ -1,31 +1,51 @@ package stake import ( + "bytes" + "encoding/hex" "testing" - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/auth" - "github.com/cosmos/cosmos-sdk/store" + sdkstore "github.com/cosmos/cosmos-sdk/store" + "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/rational" ) +func newPubKey(pk string) (res crypto.PubKey, err error) { + pkBytes, err := hex.DecodeString(pk) + if err != nil { + return + } + //res, err = crypto.PubKeyFromBytes(pkBytes) + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd, nil +} + func TestState(t *testing.T) { assert, require := assert.New(t), require.New(t) db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data") require.Nil(err) - mainLoader := store.NewIAVLStoreLoader(int64(100), 10000, numHistory) - var mainStoreKey = sdk.NewKVStoreKey("main") - multiStore := store.NewCommitMultiStore(db) - multiStore.SetSubstoreLoader(mainStoreKey, mainLoader) - var store = auth.NewAccountStore(mainStoreKey, bcm.AppAccountCodec{}) + cacheSize := 10000 + numHistory := int64(100) + stakeLoader := sdkstore.NewIAVLStoreLoader(db, cacheSize, numHistory) + var stakeStoreKey = types.NewKVStoreKey("stake") + multiStore := sdkstore.NewCommitMultiStore(db) + multiStore.SetSubstoreLoader(stakeStoreKey, stakeLoader) + multiStore.LoadLatestVersion() + store := multiStore.GetKVStore(stakeStoreKey) - delegator := sdk.Actor{"testChain", "testapp", []byte("addressdelegator")} - validator := sdk.Actor{"testChain", "testapp", []byte("addressvalidator")} + //delegator := crypto.Address{[]byte("addressdelegator")} + //validator := crypto.Address{[]byte("addressvalidator")} + delegator := []byte("addressdelegator") + validator := []byte("addressvalidator") - pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") + pk, err := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") + require.Nil(err) //---------------------------------------------------------------------- // Candidate checks @@ -42,7 +62,7 @@ func TestState(t *testing.T) { candidatesEqual := func(c1, c2 *Candidate) bool { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && - c1.Owner.Equals(c2.Owner) && + bytes.Equal(c1.Owner, c2.Owner) && c1.Assets.Equal(c2.Assets) && c1.Liabilities.Equal(c2.Liabilities) && c1.VotingPower.Equal(c2.VotingPower) && diff --git a/x/stake/test_common.go b/x/stake/test_common.go deleted file mode 100644 index 17d26d9230..0000000000 --- a/x/stake/test_common.go +++ /dev/null @@ -1,91 +0,0 @@ -package stake - -import ( - "encoding/hex" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" - - "github.com/cosmos/cosmos-sdk" -) - -func newActors(n int) (actors []sdk.Actor) { - for i := 0; i < n; i++ { - actors = append(actors, sdk.Actor{ - "testChain", "testapp", []byte(fmt.Sprintf("addr%d", i))}) - } - - return -} - -func newPubKey(pk string) crypto.PubKey { - pkBytes, _ := hex.DecodeString(pk) - var pkEd crypto.PubKeyEd25519 - copy(pkEd[:], pkBytes[:]) - return pkEd.Wrap() -} - -// dummy pubkeys used for testing -var pks = []crypto.PubKey{ - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB60"), -} - -// NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey -// instead this is just being set the address here for testing purposes -func candidatesFromActors(actors []sdk.Actor, amts []int64) (candidates Candidates) { - for i := 0; i < len(actors); i++ { - c := &Candidate{ - Status: Unbonded, - PubKey: pks[i], - Owner: actors[i], - Assets: rational.New(amts[i]), - Liabilities: rational.New(amts[i]), - VotingPower: rational.New(amts[i]), - } - candidates = append(candidates, c) - } - - return -} - -func candidatesFromActorsEmpty(actors []sdk.Actor) (candidates Candidates) { - for i := 0; i < len(actors); i++ { - c := &Candidate{ - Status: Unbonded, - PubKey: pks[i], - Owner: actors[i], - Assets: rational.Zero, - Liabilities: rational.Zero, - VotingPower: rational.Zero, - } - candidates = append(candidates, c) - } - return -} - -// helper function test if Candidate is changed asabci.Validator -func testChange(t *testing.T, val Validator, chg *abci.Validator) { - assert := assert.New(t) - assert.Equal(val.PubKey.Bytes(), chg.PubKey) - assert.Equal(val.VotingPower.Evaluate(), chg.Power) -} - -// helper function test if Candidate is removed as abci.Validator -func testRemove(t *testing.T, val Validator, chg *abci.Validator) { - assert := assert.New(t) - assert.Equal(val.PubKey.Bytes(), chg.PubKey) - assert.Equal(int64(0), chg.Power) -} diff --git a/x/stake/tick.go b/x/stake/tick.go deleted file mode 100644 index 678a01ccd4..0000000000 --- a/x/stake/tick.go +++ /dev/null @@ -1,75 +0,0 @@ -package stake - -import ( - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/state" - - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/rational" -) - -// Tick - called at the end of every block -func Tick(ctx sdk.Context, store state.SimpleDB) (change []*abci.Validator, err error) { - - // retrieve params - params := loadParams(store) - gs := loadGlobalState(store) - height := ctx.BlockHeight() - - // Process Validator Provisions - // XXX right now just process every 5 blocks, in new SDK make hourly - if gs.InflationLastTime+5 <= height { - gs.InflationLastTime = height - processProvisions(store, gs, params) - } - - return UpdateValidatorSet(store, gs, params) -} - -var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days - -// process provisions for an hour period -func processProvisions(store state.SimpleDB, gs *GlobalState, params Params) { - - gs.Inflation = nextInflation(gs, params).Round(1000000000) - - // Because the validators hold a relative bonded share (`GlobalStakeShare`), when - // more bonded tokens are added proportionally to all validators the only term - // which needs to be updated is the `BondedPool`. So for each previsions cycle: - - provisions := gs.Inflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() - gs.BondedPool += provisions - gs.TotalSupply += provisions - - // XXX XXX XXX XXX XXX XXX XXX XXX XXX - // XXX Mint them to the hold account - // XXX XXX XXX XXX XXX XXX XXX XXX XXX - - // save the params - saveGlobalState(store, gs) -} - -// get the next inflation rate for the hour -func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) { - - // The target annual inflation rate is recalculated for each previsions cycle. The - // inflation is also subject to a rate change (positive of negative) depending or - // the distance from the desired ratio (67%). The maximum rate change possible is - // defined to be 13% per year, however the annual inflation is capped as between - // 7% and 20%. - - // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := rational.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) - inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) - - // increase the new annual inflation for this next cycle - inflation = gs.Inflation.Add(inflationRateChange) - if inflation.GT(params.InflationMax) { - inflation = params.InflationMax - } - if inflation.LT(params.InflationMin) { - inflation = params.InflationMin - } - - return -} diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go deleted file mode 100644 index 3a80ae2a3b..0000000000 --- a/x/stake/tick_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/tendermint/tmlibs/rational" - - "github.com/cosmos/cosmos-sdk/state" -) - -func TestGetInflation(t *testing.T) { - assert := assert.New(t) - store := state.NewMemKVStore() - params := loadParams(store) - gs := loadGlobalState(store) - - // Governing Mechanism: - // bondedRatio = BondedPool / TotalSupply - // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange - - tests := []struct { - setBondedPool, setTotalSupply int64 - setInflation, expectedChange rational.Rat - }{ - // with 0% bonded atom supply the inflation should increase by InflationRateChange - {0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, - - // 100% bonded, starting at 20% inflation and being reduced - {1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, - - // 50% bonded, starting at 10% inflation and being increased - {1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, - - // test 7% minimum stop (testing with 100% bonded) - {1, 1, rational.New(7, 100), rational.Zero}, - {1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)}, - - // test 20% maximum stop (testing with 0% bonded) - {0, 0, rational.New(20, 100), rational.Zero}, - {0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)}, - - // perfect balance shouldn't change inflation - {67, 100, rational.New(15, 100), rational.Zero}, - } - for _, tc := range tests { - gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply - gs.Inflation = tc.setInflation - - inflation := nextInflation(gs, params) - diffInflation := inflation.Sub(tc.setInflation) - - assert.True(diffInflation.Equal(tc.expectedChange), - "%v, %v", diffInflation, tc.expectedChange) - } -} - -func TestProcessProvisions(t *testing.T) { - assert := assert.New(t) - store := state.NewMemKVStore() - params := loadParams(store) - gs := loadGlobalState(store) - - // create some candidates some bonded, some unbonded - n := 10 - actors := newActors(n) - candidates := candidatesFromActorsEmpty(actors) - for i, candidate := range candidates { - if i < 5 { - candidate.Status = Bonded - } - mintedTokens := int64((i + 1) * 10000000) - gs.TotalSupply += mintedTokens - candidate.addTokens(mintedTokens, gs) - saveCandidate(store, candidate) - } - var totalSupply int64 = 550000000 - var bondedShares int64 = 150000000 - var unbondedShares int64 = 400000000 - - // initial bonded ratio ~ 27% - assert.True(gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) - - // Supplies - assert.Equal(totalSupply, gs.TotalSupply) - assert.Equal(bondedShares, gs.BondedPool) - assert.Equal(unbondedShares, gs.UnbondedPool) - - // test the value of candidate shares - assert.True(gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate()) - - initialSupply := gs.TotalSupply - initialUnbonded := gs.TotalSupply - gs.BondedPool - - // process the provisions a year - for hr := 0; hr < 8766; hr++ { - expInflation := nextInflation(gs, params).Round(1000000000) - expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() - startBondedPool := gs.BondedPool - startTotalSupply := gs.TotalSupply - processProvisions(store, gs, params) - assert.Equal(startBondedPool+expProvisions, gs.BondedPool) - assert.Equal(startTotalSupply+expProvisions, gs.TotalSupply) - } - assert.NotEqual(initialSupply, gs.TotalSupply) - assert.Equal(initialUnbonded, gs.UnbondedPool) - //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) - - // initial bonded ratio ~ 35% ~ 30% increase for bonded holders - assert.True(gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio()) - - // global supply - assert.Equal(int64(611813022), gs.TotalSupply) - assert.Equal(int64(211813022), gs.BondedPool) - assert.Equal(unbondedShares, gs.UnbondedPool) - - // test the value of candidate shares - assert.True(gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate()) - -} diff --git a/x/stake/tx.go b/x/stake/tx.go deleted file mode 100644 index 951151dafd..0000000000 --- a/x/stake/tx.go +++ /dev/null @@ -1,147 +0,0 @@ -package stake - -import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/coin" - crypto "github.com/tendermint/go-crypto" -) - -// Tx -//-------------------------------------------------------------------------------- - -// register the tx type with its validation logic -// make sure to use the name of the handler as the prefix in the tx type, -// so it gets routed properly -const ( - ByteTxDeclareCandidacy = 0x55 - ByteTxEditCandidacy = 0x56 - ByteTxDelegate = 0x57 - ByteTxUnbond = 0x58 - TypeTxDeclareCandidacy = stakingModuleName + "/declareCandidacy" - TypeTxEditCandidacy = stakingModuleName + "/editCandidacy" - TypeTxDelegate = stakingModuleName + "/delegate" - TypeTxUnbond = stakingModuleName + "/unbond" -) - -func init() { - sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy) - sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy) - sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate) - sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond) -} - -//Verify interface at compile time -var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{} - -// BondUpdate - struct for bonding or unbonding transactions -type BondUpdate struct { - PubKey crypto.PubKey `json:"pub_key"` - Bond coin.Coin `json:"amount"` -} - -// ValidateBasic - Check for non-empty candidate, and valid coins -func (tx BondUpdate) ValidateBasic() error { - if tx.PubKey.Empty() { - return errCandidateEmpty - } - - coins := coin.Coins{tx.Bond} - if !coins.IsValid() { - return coin.ErrInvalidCoins() - } - if !coins.IsPositive() { - return fmt.Errorf("Amount must be > 0") - } - return nil -} - -// TxDeclareCandidacy - struct for unbonding transactions -type TxDeclareCandidacy struct { - BondUpdate - Description -} - -// NewTxDeclareCandidacy - new TxDeclareCandidacy -func NewTxDeclareCandidacy(bond coin.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { - return TxDeclareCandidacy{ - BondUpdate{ - PubKey: pubKey, - Bond: bond, - }, - description, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// TxEditCandidacy - struct for editing a candidate -type TxEditCandidacy struct { - PubKey crypto.PubKey `json:"pub_key"` - Description -} - -// NewTxEditCandidacy - new TxEditCandidacy -func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx { - return TxEditCandidacy{ - PubKey: pubKey, - Description: description, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// ValidateBasic - Check for non-empty candidate, -func (tx TxEditCandidacy) ValidateBasic() error { - if tx.PubKey.Empty() { - return errCandidateEmpty - } - - empty := Description{} - if tx.Description == empty { - return fmt.Errorf("Transaction must include some information to modify") - } - return nil -} - -// TxDelegate - struct for bonding transactions -type TxDelegate struct{ BondUpdate } - -// NewTxDelegate - new TxDelegate -func NewTxDelegate(bond coin.Coin, pubKey crypto.PubKey) sdk.Tx { - return TxDelegate{BondUpdate{ - PubKey: pubKey, - Bond: bond, - }}.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// TxUnbond - struct for unbonding transactions -type TxUnbond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares string `json:"amount"` -} - -// NewTxUnbond - new TxUnbond -func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx { - return TxUnbond{ - PubKey: pubKey, - Shares: shares, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// ValidateBasic - Check for non-empty candidate, positive shares -func (tx TxUnbond) ValidateBasic() error { - if tx.PubKey.Empty() { - return errCandidateEmpty - } - return nil -} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go deleted file mode 100644 index f6d814589d..0000000000 --- a/x/stake/tx_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package stake - -import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/coin" - - crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" -) - -var ( - validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")} - empty sdk.Actor - - coinPos = coin.Coin{"fermion", 1000} - coinZero = coin.Coin{"fermion", 0} - coinNeg = coin.Coin{"fermion", -10000} - coinPosNotAtoms = coin.Coin{"foo", 10000} - coinZeroNotAtoms = coin.Coin{"foo", 0} - coinNegNotAtoms = coin.Coin{"foo", -10000} -) - -func TestBondUpdateValidateBasic(t *testing.T) { - tests := []struct { - name string - PubKey crypto.PubKey - Bond coin.Coin - wantErr bool - }{ - {"basic good", pks[0], coinPos, false}, - {"empty delegator", crypto.PubKey{}, coinPos, true}, - {"zero coin", pks[0], coinZero, true}, - {"neg coin", pks[0], coinNeg, true}, - } - - for _, tc := range tests { - tx := TxDelegate{BondUpdate{ - PubKey: tc.PubKey, - Bond: tc.Bond, - }} - assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) - } -} - -func TestAllAreTx(t *testing.T) { - assert := assert.New(t) - - // make sure all types construct properly - pubKey := newPubKey("1234567890") - bondAmt := 1234321 - bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} - - // Note that Wrap is only defined on BondUpdate, so when you call it, - // you lose all info on the embedding type. Please add Wrap() - // method to all the parents - txDelegate := NewTxDelegate(bond, pubKey) - _, ok := txDelegate.Unwrap().(TxDelegate) - assert.True(ok, "%#v", txDelegate) - - txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey) - _, ok = txUnbond.Unwrap().(TxUnbond) - assert.True(ok, "%#v", txUnbond) - - txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{}) - _, ok = txDecl.Unwrap().(TxDeclareCandidacy) - assert.True(ok, "%#v", txDecl) - - txEditCan := NewTxEditCandidacy(pubKey, Description{}) - _, ok = txEditCan.Unwrap().(TxEditCandidacy) - assert.True(ok, "%#v", txEditCan) -} - -func TestSerializeTx(t *testing.T) { - assert := assert.New(t) - - // make sure all types construct properly - pubKey := newPubKey("1234567890") - bondAmt := 1234321 - bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} - - tests := []struct { - tx sdk.Tx - }{ - {NewTxUnbond(strconv.Itoa(bondAmt), pubKey)}, - {NewTxDeclareCandidacy(bond, pubKey, Description{})}, - {NewTxDeclareCandidacy(bond, pubKey, Description{})}, - // {NewTxRevokeCandidacy(pubKey)}, - } - - for i, tc := range tests { - var tx sdk.Tx - bs := wire.BinaryBytes(tc.tx) - err := wire.ReadBinaryBytes(bs, &tx) - if assert.NoError(err, "%d", i) { - assert.Equal(tc.tx, tx, "%d", i) - } - } -} diff --git a/x/stake/types.go b/x/stake/types.go index 0ca88ef11d..018840a017 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -4,8 +4,7 @@ import ( "bytes" "sort" - "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/state" + "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" @@ -15,8 +14,8 @@ import ( // Params defines the high level settings for staking type Params struct { - HoldBonded sdk.Actor `json:"hold_bonded"` // account where all bonded coins are held - HoldUnbonded sdk.Actor `json:"hold_unbonded"` // account where all delegated but unbonded coins are held + HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held + HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate @@ -35,8 +34,8 @@ type Params struct { func defaultParams() Params { return Params{ - HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), - HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), + HoldBonded: []byte("77777777777777777777777777777777"), + HoldUnbonded: []byte("88888888888888888888888888888888"), InflationRateChange: rational.New(13, 100), InflationMax: rational.New(20, 100), InflationMin: rational.New(7, 100), @@ -151,7 +150,7 @@ const ( type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner sdk.Actor `json:"owner"` // Sender of BondTx - UnbondTx returns here + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator @@ -167,7 +166,7 @@ type Description struct { } // NewCandidate - initialize a new candidate -func NewCandidate(pubKey crypto.PubKey, owner sdk.Actor, description Description) *Candidate { +func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { return &Candidate{ Status: Unbonded, PubKey: pubKey, @@ -234,8 +233,13 @@ type Validator Candidate // ABCIValidator - Get the validator from a bond value func (v Validator) ABCIValidator() *abci.Validator { + pk, err := wire.MarshalBinary(v.PubKey) + if err != nil { + panic(err) + } + return &abci.Validator{ - PubKey: wire.BinaryBytes(v.PubKey), + PubKey: pk, Power: v.VotingPower.Evaluate(), } } @@ -269,7 +273,7 @@ func (cs Candidates) Sort() { } // update the voting power and save -func (cs Candidates) updateVotingPower(store state.SimpleDB, gs *GlobalState, params Params) Candidates { +func (cs Candidates) updateVotingPower(store types.KVStore, gs *GlobalState, params Params) Candidates { // update voting power for _, c := range cs { @@ -391,7 +395,7 @@ func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validato // UpdateValidatorSet - Updates the voting power for the candidate set and // returns the subset of validators which have been updated for Tendermint -func UpdateValidatorSet(store state.SimpleDB, gs *GlobalState, params Params) (change []*abci.Validator, err error) { +func UpdateValidatorSet(store types.KVStore, gs *GlobalState, params Params) (change []*abci.Validator, err error) { // get the validators before update candidates := loadCandidates(store) diff --git a/x/stake/types_test.go b/x/stake/types_test.go deleted file mode 100644 index d78df60b00..0000000000 --- a/x/stake/types_test.go +++ /dev/null @@ -1,251 +0,0 @@ -package stake - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/tmlibs/rational" - - "github.com/cosmos/cosmos-sdk/state" -) - -func TestCandidatesSort(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - N := 5 - actors := newActors(N) - candidates := candidatesFromActors(actors, []int64{10, 300, 123, 4, 200}) - expectedOrder := []int{1, 4, 2, 0, 3} - - // test basic sort - candidates.Sort() - - vals := candidates.Validators() - require.Equal(N, len(vals)) - - for i, val := range vals { - expectedIdx := expectedOrder[i] - assert.Equal(val.PubKey, pks[expectedIdx]) - } -} - -func TestValidatorsSort(t *testing.T) { - assert := assert.New(t) - - v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(25)}).validator() - v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(1234)}).validator() - v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(122)}).validator() - v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(13)}).validator() - v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(1111)}).validator() - - // test from nothing to something - vs := Validators{v4, v2, v5, v1, v3} - - // test basic sort - vs.Sort() - - for i, v := range vs { - assert.True(v.PubKey.Equals(pks[i])) - } -} - -func TestUpdateVotingPower(t *testing.T) { - assert := assert.New(t) - store := state.NewMemKVStore() - params := loadParams(store) - gs := loadGlobalState(store) - - N := 5 - actors := newActors(N) - candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) - - // test a basic change in voting power - candidates[0].Assets = rational.New(500) - candidates.updateVotingPower(store, gs, params) - assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) - - // test a swap in voting power - candidates[1].Assets = rational.New(600) - candidates.updateVotingPower(store, gs, params) - assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) - assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) - - // test the max validators term - params.MaxVals = 4 - saveParams(store, params) - candidates.updateVotingPower(store, gs, params) - assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) -} - -func TestGetValidators(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - N := 5 - actors := newActors(N) - candidates := candidatesFromActors(actors, []int64{400, 200, 0, 0, 0}) - - validators := candidates.Validators() - require.Equal(2, len(validators)) - assert.Equal(candidates[0].PubKey, validators[0].PubKey) - assert.Equal(candidates[1].PubKey, validators[1].PubKey) -} - -func TestValidatorsChanged(t *testing.T) { - require := require.New(t) - - v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator() - v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator() - v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator() - v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator() - v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator() - - // test from nothing to something - vs1 := Validators{} - vs2 := Validators{v1, v2} - changed := vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testChange(t, vs2[0], changed[0]) - testChange(t, vs2[1], changed[1]) - - // test from something to nothing - vs1 = Validators{v1, v2} - vs2 = Validators{} - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testRemove(t, vs1[0], changed[0]) - testRemove(t, vs1[1], changed[1]) - - // test identical - vs1 = Validators{v1, v2, v4} - vs2 = Validators{v1, v2, v4} - changed = vs1.validatorsUpdated(vs2) - require.Zero(len(changed)) - - // test single value change - vs2[2].VotingPower = rational.One - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[2], changed[0]) - - // test multiple value change - vs2[0].VotingPower = rational.New(11) - vs2[2].VotingPower = rational.New(5) - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testChange(t, vs2[0], changed[0]) - testChange(t, vs2[2], changed[1]) - - // test validator added at the beginning - vs1 = Validators{v2, v4} - vs2 = Validators{v2, v4, v1} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[0], changed[0]) - - // test validator added in the middle - vs1 = Validators{v1, v2, v4} - vs2 = Validators{v3, v1, v4, v2} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[2], changed[0]) - - // test validator added at the end - vs2 = Validators{v1, v2, v4, v5} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[3], changed[0]) - - // test multiple validators added - vs2 = Validators{v1, v2, v3, v4, v5} - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testChange(t, vs2[2], changed[0]) - testChange(t, vs2[4], changed[1]) - - // test validator removed at the beginning - vs2 = Validators{v2, v4} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testRemove(t, vs1[0], changed[0]) - - // test validator removed in the middle - vs2 = Validators{v1, v4} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testRemove(t, vs1[1], changed[0]) - - // test validator removed at the end - vs2 = Validators{v1, v2} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testRemove(t, vs1[2], changed[0]) - - // test multiple validators removed - vs2 = Validators{v1} - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testRemove(t, vs1[1], changed[0]) - testRemove(t, vs1[2], changed[1]) - - // test many types of changes - vs2 = Validators{v1, v3, v4, v5} - vs2[2].VotingPower = rational.New(11) - changed = vs1.validatorsUpdated(vs2) - require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 - testRemove(t, vs1[1], changed[0]) - testChange(t, vs2[1], changed[1]) - testChange(t, vs2[2], changed[2]) - testChange(t, vs2[3], changed[3]) - -} - -func TestUpdateValidatorSet(t *testing.T) { - assert, require := assert.New(t), require.New(t) - store := state.NewMemKVStore() - params := loadParams(store) - gs := loadGlobalState(store) - - N := 5 - actors := newActors(N) - candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) - for _, c := range candidates { - saveCandidate(store, c) - } - - // they should all already be validators - change, err := UpdateValidatorSet(store, gs, params) - require.Nil(err) - require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 - - // test the max value and test again - params.MaxVals = 4 - saveParams(store, params) - change, err = UpdateValidatorSet(store, gs, params) - require.Nil(err) - require.Equal(1, len(change), "%v", change) - testRemove(t, candidates[4].validator(), change[0]) - candidates = loadCandidates(store) - assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) - - // mess with the power's of the candidates and test - candidates[0].Assets = rational.New(10) - candidates[1].Assets = rational.New(600) - candidates[2].Assets = rational.New(1000) - candidates[3].Assets = rational.One - candidates[4].Assets = rational.New(10) - for _, c := range candidates { - saveCandidate(store, c) - } - change, err = UpdateValidatorSet(store, gs, params) - require.Nil(err) - require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed - candidates = loadCandidates(store) - testChange(t, candidates[0].validator(), change[0]) - testChange(t, candidates[1].validator(), change[1]) - testChange(t, candidates[2].validator(), change[2]) - testRemove(t, candidates[3].validator(), change[3]) - testChange(t, candidates[4].validator(), change[4]) -} - -// XXX test global state functions, candidate exchange rate functions etc. From 7d4528e623f05c8078d0601cd2aa5d9c9a9e33be Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 18 Jan 2018 19:00:03 +0000 Subject: [PATCH 03/54] gaia store marshal binary broken working store updates --- x/stake/store.go | 139 ++++++- x/stake/store_test.go | 16 +- x/stake/types.go | 832 +++++++++++++++++++++--------------------- 3 files changed, 554 insertions(+), 433 deletions(-) diff --git a/x/stake/store.go b/x/stake/store.go index f62aa46e49..e834f47384 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -1,14 +1,133 @@ package stake import ( - "encoding/json" - crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/types" ) +/////////////////////////////////////////////////////////// temp types + +//nolint +type Params struct { + HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held + HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held + + InflationRateChange int64 `json:"inflation_rate_change"` // XXX maximum annual change in inflation rate + InflationMax int64 `json:"inflation_max"` // XXX maximum inflation rate + InflationMin int64 `json:"inflation_min"` // XXX minimum inflation rate + GoalBonded int64 `json:"goal_bonded"` // XXX Goal of percent bonded atoms + + MaxVals uint16 `json:"max_vals"` // maximum number of validators + AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` + GasEditCandidacy int64 `json:"gas_edit_candidacy"` + GasDelegate int64 `json:"gas_delegate"` + GasUnbond int64 `json:"gas_unbond"` +} + +func defaultParams() Params { + return Params{ + HoldBonded: []byte("77777777777777777777777777777777"), + HoldUnbonded: []byte("88888888888888888888888888888888"), + InflationRateChange: rational.New(13, 100), + InflationMax: rational.New(20, 100), + InflationMin: rational.New(7, 100), + GoalBonded: rational.New(67, 100), + MaxVals: 100, + AllowedBondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +// GlobalState - dynamic parameters of the current state +type GlobalState struct { + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares int64 `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares int64 `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation int64 `json:"inflation"` // current annual inflation rate +} + +// XXX define globalstate interface? + +func initialGlobalState() *GlobalState { + return &GlobalState{ + TotalSupply: 0, + BondedShares: rational.Zero, + UnbondedShares: rational.Zero, + BondedPool: 0, + UnbondedPool: 0, + InflationLastTime: 0, + Inflation: rational.New(7, 100), + } +} + +// CandidateStatus - status of a validator-candidate +type CandidateStatus byte + +const ( + // nolint + Bonded CandidateStatus = 0x00 + Unbonded CandidateStatus = 0x01 + Revoked CandidateStatus = 0x02 +) + +// Candidate defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// candidate, the candidate is credited with a DelegatorBond whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Candidate struct { + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets int64 `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities int64 `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower int64 `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate +} + +// Description - description fields for a candidate +type Description struct { + Moniker string `json:"moniker"` + Identity string `json:"identity"` + Website string `json:"website"` + Details string `json:"details"` +} + +// NewCandidate - initialize a new candidate +func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { + return &Candidate{ + Status: Unbonded, + PubKey: pubKey, + Owner: owner, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, + Description: description, + } +} + +//nolint +type DelegatorBond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares int64 `json:"shares"` +} + +///////////////////////////////////////////////////////////j + // nolint var ( // Keys for store prefixes @@ -93,7 +212,7 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { return nil } candidate := new(Candidate) - err := json.Unmarshal(b, candidate) + err := wire.UnmarshalBinary(b, candidate) if err != nil { panic(err) // This error should never occure big problem if does } @@ -108,7 +227,7 @@ func saveCandidate(store types.KVStore, candidate *Candidate) { saveCandidatesPubKeys(store, append(pks, candidate.PubKey)) } - b, err := json.Marshal(*candidate) + b, err := wire.MarshalBinary(*candidate) if err != nil { panic(err) } @@ -158,7 +277,7 @@ func loadDelegatorBond(store types.KVStore, } bond := new(DelegatorBond) - err := json.Unmarshal(delegatorBytes, bond) + err := wire.UnmarshalBinary(delegatorBytes, bond) if err != nil { panic(err) } @@ -179,7 +298,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele } // now actually save the bond - b, err := json.Marshal(*bond) + b, err := wire.MarshalBinary(*bond) if err != nil { panic(err) } @@ -250,7 +369,7 @@ func loadParams(store types.KVStore) (params Params) { return defaultParams() } - err := json.Unmarshal(b, ¶ms) + err := wire.UnmarshalBinary(b, ¶ms) if err != nil { panic(err) // This error should never occure big problem if does } @@ -258,7 +377,7 @@ func loadParams(store types.KVStore) (params Params) { return } func saveParams(store types.KVStore, params Params) { - b, err := json.Marshal(params) + b, err := wire.MarshalBinary(params) if err != nil { panic(err) } @@ -274,14 +393,14 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { return initialGlobalState() } gs = new(GlobalState) - err := json.Unmarshal(b, gs) + err := wire.UnmarshalBinary(b, gs) if err != nil { panic(err) // This error should never occure big problem if does } return } func saveGlobalState(store types.KVStore, gs *GlobalState) { - b, err := json.Marshal(*gs) + b, err := wire.MarshalBinary(*gs) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index d2c6839cb3..dd77aafa56 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/rational" ) func newPubKey(pk string) (res crypto.PubKey, err error) { @@ -38,6 +38,8 @@ func TestState(t *testing.T) { multiStore.SetSubstoreLoader(stakeStoreKey, stakeLoader) multiStore.LoadLatestVersion() store := multiStore.GetKVStore(stakeStoreKey) + wire.RegisterInterface((*crypto.PubKey)(nil), nil) + wire.RegisterConcrete(crypto.PubKeyEd25519{}, "crypto/PubKeyEd25519", nil) //delegator := crypto.Address{[]byte("addressdelegator")} //validator := crypto.Address{[]byte("addressvalidator")} @@ -54,9 +56,9 @@ func TestState(t *testing.T) { candidate := &Candidate{ Owner: validator, PubKey: pk, - Assets: rational.New(9), - Liabilities: rational.New(9), - VotingPower: rational.Zero, + Assets: 9, //rational.New(9), + Liabilities: 9, // rational.New(9), + VotingPower: 0, //rational.Zero, } candidatesEqual := func(c1, c2 *Candidate) bool { @@ -81,7 +83,7 @@ func TestState(t *testing.T) { assert.True(candidatesEqual(candidate, resCand)) // modify a records, save, and retrieve - candidate.Liabilities = rational.New(99) + candidate.Liabilities = 99 //rational.New(99) saveCandidate(store, candidate) resCand = loadCandidate(store, pk) assert.True(candidatesEqual(candidate, resCand)) @@ -96,7 +98,7 @@ func TestState(t *testing.T) { bond := &DelegatorBond{ PubKey: pk, - Shares: rational.New(9), + Shares: 9, // rational.New(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -114,7 +116,7 @@ func TestState(t *testing.T) { assert.True(bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = rational.New(99) + bond.Shares = 99 //rational.New(99) saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) assert.True(bondsEqual(bond, resBond)) diff --git a/x/stake/types.go b/x/stake/types.go index 018840a017..36caeb35bf 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -1,418 +1,418 @@ package stake -import ( - "bytes" - "sort" - - "github.com/cosmos/cosmos-sdk/types" - - abci "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" - "github.com/tendermint/tmlibs/rational" -) - -// Params defines the high level settings for staking -type Params struct { - HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held - HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - - InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate - InflationMin rational.Rat `json:"inflation_min"` // minimum inflation rate - GoalBonded rational.Rat `json:"goal_bonded"` // Goal of percent bonded atoms - - MaxVals uint16 `json:"max_vals"` // maximum number of validators - AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` - GasEditCandidacy int64 `json:"gas_edit_candidacy"` - GasDelegate int64 `json:"gas_delegate"` - GasUnbond int64 `json:"gas_unbond"` -} - -func defaultParams() Params { - return Params{ - HoldBonded: []byte("77777777777777777777777777777777"), - HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: rational.New(13, 100), - InflationMax: rational.New(20, 100), - InflationMin: rational.New(7, 100), - GoalBonded: rational.New(67, 100), - MaxVals: 100, - AllowedBondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, - } -} - -//_________________________________________________________________________ - -// GlobalState - dynamic parameters of the current state -type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares rational.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares rational.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation rational.Rat `json:"inflation"` // current annual inflation rate -} - -// XXX define globalstate interface? - -func initialGlobalState() *GlobalState { - return &GlobalState{ - TotalSupply: 0, - BondedShares: rational.Zero, - UnbondedShares: rational.Zero, - BondedPool: 0, - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: rational.New(7, 100), - } -} - -// get the bond ratio of the global state -func (gs *GlobalState) bondedRatio() rational.Rat { - if gs.TotalSupply > 0 { - return rational.New(gs.BondedPool, gs.TotalSupply) - } - return rational.Zero -} - -// get the exchange rate of bonded token per issued share -func (gs *GlobalState) bondedShareExRate() rational.Rat { - if gs.BondedShares.IsZero() { - return rational.One - } - return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) -} - -// get the exchange rate of unbonded tokens held in candidates per issued share -func (gs *GlobalState) unbondedShareExRate() rational.Rat { - if gs.UnbondedShares.IsZero() { - return rational.One - } - return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) -} - -func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rat) { - issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens - gs.BondedPool += amount - gs.BondedShares = gs.BondedShares.Add(issuedShares) - return -} - -func (gs *GlobalState) removeSharesBonded(shares rational.Rat) (removedTokens int64) { - removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.BondedShares = gs.BondedShares.Sub(shares) - gs.BondedPool -= removedTokens - return -} - -func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rat) { - issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - gs.UnbondedPool += amount - return -} - -func (gs *GlobalState) removeSharesUnbonded(shares rational.Rat) (removedTokens int64) { - removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.UnbondedShares = gs.UnbondedShares.Sub(shares) - gs.UnbondedPool -= removedTokens - return -} - -//_______________________________________________________________________________________________________ - -// CandidateStatus - status of a validator-candidate -type CandidateStatus byte - -const ( - // nolint - Bonded CandidateStatus = 0x00 - Unbonded CandidateStatus = 0x01 - Revoked CandidateStatus = 0x02 -) - -// Candidate defines the total amount of bond shares and their exchange rate to -// coins. Accumulation of interest is modelled as an in increase in the -// exchange rate, and slashing as a decrease. When coins are delegated to this -// candidate, the candidate is credited with a DelegatorBond whose number of -// bond shares is based on the amount of coins delegated divided by the current -// exchange rate. Voting power can be calculated as total bonds multiplied by -// exchange rate. -type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate -} - -// Description - description fields for a candidate -type Description struct { - Moniker string `json:"moniker"` - Identity string `json:"identity"` - Website string `json:"website"` - Details string `json:"details"` -} - -// NewCandidate - initialize a new candidate -func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { - return &Candidate{ - Status: Unbonded, - PubKey: pubKey, - Owner: owner, - Assets: rational.Zero, - Liabilities: rational.Zero, - VotingPower: rational.Zero, - Description: description, - } -} - -// XXX define candidate interface? - -// get the exchange rate of global pool shares over delegator shares -func (c *Candidate) delegatorShareExRate() rational.Rat { - if c.Liabilities.IsZero() { - return rational.One - } - return c.Assets.Quo(c.Liabilities) -} - -// add tokens to a candidate -func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rat) { - - exRate := c.delegatorShareExRate() - - var receivedGlobalShares rational.Rat - if c.Status == Bonded { - receivedGlobalShares = gs.addTokensBonded(amount) - } else { - receivedGlobalShares = gs.addTokensUnbonded(amount) - } - c.Assets = c.Assets.Add(receivedGlobalShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) - return -} - -// remove shares from a candidate -func (c *Candidate) removeShares(shares rational.Rat, gs *GlobalState) (removedTokens int64) { - - globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) - - if c.Status == Bonded { - removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) - } else { - removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) - } - c.Assets = c.Assets.Sub(globalPoolSharesToRemove) - - c.Liabilities = c.Liabilities.Sub(shares) - return -} - -// Validator returns a copy of the Candidate as a Validator. -// Should only be called when the Candidate qualifies as a validator. -func (c *Candidate) validator() Validator { - return Validator(*c) -} - -// Validator is one of the top Candidates -type Validator Candidate - -// ABCIValidator - Get the validator from a bond value -func (v Validator) ABCIValidator() *abci.Validator { - pk, err := wire.MarshalBinary(v.PubKey) - if err != nil { - panic(err) - } - - return &abci.Validator{ - PubKey: pk, - Power: v.VotingPower.Evaluate(), - } -} - -//_________________________________________________________________________ - -// TODO replace with sorted multistore functionality - -// Candidates - list of Candidates -type Candidates []*Candidate - -var _ sort.Interface = Candidates{} //enforce the sort interface at compile time - -// nolint - sort interface functions -func (cs Candidates) Len() int { return len(cs) } -func (cs Candidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } -func (cs Candidates) Less(i, j int) bool { - vp1, vp2 := cs[i].VotingPower, cs[j].VotingPower - pk1, pk2 := cs[i].PubKey.Bytes(), cs[j].PubKey.Bytes() - - //note that all ChainId and App must be the same for a group of candidates - if vp1 != vp2 { - return vp1.GT(vp2) - } - return bytes.Compare(pk1, pk2) == -1 -} - -// Sort - Sort the array of bonded values -func (cs Candidates) Sort() { - sort.Sort(cs) -} - -// update the voting power and save -func (cs Candidates) updateVotingPower(store types.KVStore, gs *GlobalState, params Params) Candidates { - - // update voting power - for _, c := range cs { - if !c.VotingPower.Equal(c.Assets) { - c.VotingPower = c.Assets - } - } - cs.Sort() - for i, c := range cs { - // truncate the power - if i >= int(params.MaxVals) { - c.VotingPower = rational.Zero - if c.Status == Bonded { - // XXX to replace this with handler.bondedToUnbondePool function - // XXX waiting for logic with new SDK to update account balance here - tokens := gs.removeSharesBonded(c.Assets) - c.Assets = gs.addTokensUnbonded(tokens) - c.Status = Unbonded - } - } else { - c.Status = Bonded - } - saveCandidate(store, c) - } - return cs -} - -// Validators - get the most recent updated validator set from the -// Candidates. These bonds are already sorted by VotingPower from -// the UpdateVotingPower function which is the only function which -// is to modify the VotingPower -func (cs Candidates) Validators() Validators { - - //test if empty - if len(cs) == 1 { - if cs[0].VotingPower.IsZero() { - return nil - } - } - - validators := make(Validators, len(cs)) - for i, c := range cs { - if c.VotingPower.IsZero() { //exit as soon as the first Voting power set to zero is found - return validators[:i] - } - validators[i] = c.validator() - } - - return validators -} - -//_________________________________________________________________________ - -// Validators - list of Validators -type Validators []Validator - -var _ sort.Interface = Validators{} //enforce the sort interface at compile time - -// nolint - sort interface functions -func (vs Validators) Len() int { return len(vs) } -func (vs Validators) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } -func (vs Validators) Less(i, j int) bool { - pk1, pk2 := vs[i].PubKey.Bytes(), vs[j].PubKey.Bytes() - return bytes.Compare(pk1, pk2) == -1 -} - -// Sort - Sort validators by pubkey -func (vs Validators) Sort() { - sort.Sort(vs) -} - -// determine all updated validators between two validator sets -func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validator) { - - //first sort the validator sets - vs.Sort() - vs2.Sort() - - max := len(vs) + len(vs2) - updated = make([]*abci.Validator, max) - i, j, n := 0, 0, 0 //counters for vs loop, vs2 loop, updated element - - for i < len(vs) && j < len(vs2) { - - if !vs[i].PubKey.Equals(vs2[j].PubKey) { - // pk1 > pk2, a new validator was introduced between these pubkeys - if bytes.Compare(vs[i].PubKey.Bytes(), vs2[j].PubKey.Bytes()) == 1 { - updated[n] = vs2[j].ABCIValidator() - n++ - j++ - continue - } // else, the old validator has been removed - updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} - n++ - i++ - continue - } - - if vs[i].VotingPower != vs2[j].VotingPower { - updated[n] = vs2[j].ABCIValidator() - n++ - } - j++ - i++ - } - - // add any excess validators in set 2 - for ; j < len(vs2); j, n = j+1, n+1 { - updated[n] = vs2[j].ABCIValidator() - } - - // remove any excess validators left in set 1 - for ; i < len(vs); i, n = i+1, n+1 { - updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} - } - - return updated[:n] -} - -// UpdateValidatorSet - Updates the voting power for the candidate set and -// returns the subset of validators which have been updated for Tendermint -func UpdateValidatorSet(store types.KVStore, gs *GlobalState, params Params) (change []*abci.Validator, err error) { - - // get the validators before update - candidates := loadCandidates(store) - - v1 := candidates.Validators() - v2 := candidates.updateVotingPower(store, gs, params).Validators() - - change = v1.validatorsUpdated(v2) - return -} - -//_________________________________________________________________________ - -// DelegatorBond represents the bond with tokens held by an account. It is -// owned by one delegator, and is associated with the voting power of one -// pubKey. -type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares rational.Rat `json:"shares"` -} +//import ( +//"bytes" +//"sort" + +//"github.com/cosmos/cosmos-sdk/types" + +//abci "github.com/tendermint/abci/types" +//crypto "github.com/tendermint/go-crypto" +//wire "github.com/tendermint/go-wire" +//"github.com/tendermint/tmlibs/rational" +//) + +//// Params defines the high level settings for staking +//type Params struct { +//HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held +//HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held + +//InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate +//InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate +//InflationMin rational.Rat `json:"inflation_min"` // minimum inflation rate +//GoalBonded rational.Rat `json:"goal_bonded"` // Goal of percent bonded atoms + +//MaxVals uint16 `json:"max_vals"` // maximum number of validators +//AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination + +//// gas costs for txs +//GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` +//GasEditCandidacy int64 `json:"gas_edit_candidacy"` +//GasDelegate int64 `json:"gas_delegate"` +//GasUnbond int64 `json:"gas_unbond"` +//} + +//func defaultParams() Params { +//return Params{ +//HoldBonded: []byte("77777777777777777777777777777777"), +//HoldUnbonded: []byte("88888888888888888888888888888888"), +//InflationRateChange: rational.New(13, 100), +//InflationMax: rational.New(20, 100), +//InflationMin: rational.New(7, 100), +//GoalBonded: rational.New(67, 100), +//MaxVals: 100, +//AllowedBondDenom: "fermion", +//GasDeclareCandidacy: 20, +//GasEditCandidacy: 20, +//GasDelegate: 20, +//GasUnbond: 20, +//} +//} + +////_________________________________________________________________________ + +//// GlobalState - dynamic parameters of the current state +//type GlobalState struct { +//TotalSupply int64 `json:"total_supply"` // total supply of all tokens +//BondedShares rational.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool +//UnbondedShares rational.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool +//BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens +//UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates +//InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time +//Inflation rational.Rat `json:"inflation"` // current annual inflation rate +//} + +//// XXX define globalstate interface? + +//func initialGlobalState() *GlobalState { +//return &GlobalState{ +//TotalSupply: 0, +//BondedShares: rational.Zero, +//UnbondedShares: rational.Zero, +//BondedPool: 0, +//UnbondedPool: 0, +//InflationLastTime: 0, +//Inflation: rational.New(7, 100), +//} +//} + +//// get the bond ratio of the global state +//func (gs *GlobalState) bondedRatio() rational.Rat { +//if gs.TotalSupply > 0 { +//return rational.New(gs.BondedPool, gs.TotalSupply) +//} +//return rational.Zero +//} + +//// get the exchange rate of bonded token per issued share +//func (gs *GlobalState) bondedShareExRate() rational.Rat { +//if gs.BondedShares.IsZero() { +//return rational.One +//} +//return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) +//} + +//// get the exchange rate of unbonded tokens held in candidates per issued share +//func (gs *GlobalState) unbondedShareExRate() rational.Rat { +//if gs.UnbondedShares.IsZero() { +//return rational.One +//} +//return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) +//} + +//func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rat) { +//issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens +//gs.BondedPool += amount +//gs.BondedShares = gs.BondedShares.Add(issuedShares) +//return +//} + +//func (gs *GlobalState) removeSharesBonded(shares rational.Rat) (removedTokens int64) { +//removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares +//gs.BondedShares = gs.BondedShares.Sub(shares) +//gs.BondedPool -= removedTokens +//return +//} + +//func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rat) { +//issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens +//gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) +//gs.UnbondedPool += amount +//return +//} + +//func (gs *GlobalState) removeSharesUnbonded(shares rational.Rat) (removedTokens int64) { +//removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares +//gs.UnbondedShares = gs.UnbondedShares.Sub(shares) +//gs.UnbondedPool -= removedTokens +//return +//} + +////_______________________________________________________________________________________________________ + +//// CandidateStatus - status of a validator-candidate +//type CandidateStatus byte + +//const ( +//// nolint +//Bonded CandidateStatus = 0x00 +//Unbonded CandidateStatus = 0x01 +//Revoked CandidateStatus = 0x02 +//) + +//// Candidate defines the total amount of bond shares and their exchange rate to +//// coins. Accumulation of interest is modelled as an in increase in the +//// exchange rate, and slashing as a decrease. When coins are delegated to this +//// candidate, the candidate is credited with a DelegatorBond whose number of +//// bond shares is based on the amount of coins delegated divided by the current +//// exchange rate. Voting power can be calculated as total bonds multiplied by +//// exchange rate. +//type Candidate struct { +//Status CandidateStatus `json:"status"` // Bonded status +//PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate +//Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here +//Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares +//Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares +//VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator +//Description Description `json:"description"` // Description terms for the candidate +//} + +//// Description - description fields for a candidate +//type Description struct { +//Moniker string `json:"moniker"` +//Identity string `json:"identity"` +//Website string `json:"website"` +//Details string `json:"details"` +//} + +//// NewCandidate - initialize a new candidate +//func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { +//return &Candidate{ +//Status: Unbonded, +//PubKey: pubKey, +//Owner: owner, +//Assets: rational.Zero, +//Liabilities: rational.Zero, +//VotingPower: rational.Zero, +//Description: description, +//} +//} + +//// XXX define candidate interface? + +//// get the exchange rate of global pool shares over delegator shares +//func (c *Candidate) delegatorShareExRate() rational.Rat { +//if c.Liabilities.IsZero() { +//return rational.One +//} +//return c.Assets.Quo(c.Liabilities) +//} + +//// add tokens to a candidate +//func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rat) { + +//exRate := c.delegatorShareExRate() + +//var receivedGlobalShares rational.Rat +//if c.Status == Bonded { +//receivedGlobalShares = gs.addTokensBonded(amount) +//} else { +//receivedGlobalShares = gs.addTokensUnbonded(amount) +//} +//c.Assets = c.Assets.Add(receivedGlobalShares) + +//issuedDelegatorShares = exRate.Mul(receivedGlobalShares) +//c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) +//return +//} + +//// remove shares from a candidate +//func (c *Candidate) removeShares(shares rational.Rat, gs *GlobalState) (removedTokens int64) { + +//globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) + +//if c.Status == Bonded { +//removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) +//} else { +//removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) +//} +//c.Assets = c.Assets.Sub(globalPoolSharesToRemove) + +//c.Liabilities = c.Liabilities.Sub(shares) +//return +//} + +//// Validator returns a copy of the Candidate as a Validator. +//// Should only be called when the Candidate qualifies as a validator. +//func (c *Candidate) validator() Validator { +//return Validator(*c) +//} + +//// Validator is one of the top Candidates +//type Validator Candidate + +//// ABCIValidator - Get the validator from a bond value +//func (v Validator) ABCIValidator() *abci.Validator { +//pk, err := wire.MarshalBinary(v.PubKey) +//if err != nil { +//panic(err) +//} + +//return &abci.Validator{ +//PubKey: pk, +//Power: v.VotingPower.Evaluate(), +//} +//} + +////_________________________________________________________________________ + +//// TODO replace with sorted multistore functionality + +//// Candidates - list of Candidates +//type Candidates []*Candidate + +//var _ sort.Interface = Candidates{} //enforce the sort interface at compile time + +//// nolint - sort interface functions +//func (cs Candidates) Len() int { return len(cs) } +//func (cs Candidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } +//func (cs Candidates) Less(i, j int) bool { +//vp1, vp2 := cs[i].VotingPower, cs[j].VotingPower +//pk1, pk2 := cs[i].PubKey.Bytes(), cs[j].PubKey.Bytes() + +////note that all ChainId and App must be the same for a group of candidates +//if vp1 != vp2 { +//return vp1.GT(vp2) +//} +//return bytes.Compare(pk1, pk2) == -1 +//} + +//// Sort - Sort the array of bonded values +//func (cs Candidates) Sort() { +//sort.Sort(cs) +//} + +//// update the voting power and save +//func (cs Candidates) updateVotingPower(store types.KVStore, gs *GlobalState, params Params) Candidates { + +//// update voting power +//for _, c := range cs { +//if !c.VotingPower.Equal(c.Assets) { +//c.VotingPower = c.Assets +//} +//} +//cs.Sort() +//for i, c := range cs { +//// truncate the power +//if i >= int(params.MaxVals) { +//c.VotingPower = rational.Zero +//if c.Status == Bonded { +//// XXX to replace this with handler.bondedToUnbondePool function +//// XXX waiting for logic with new SDK to update account balance here +//tokens := gs.removeSharesBonded(c.Assets) +//c.Assets = gs.addTokensUnbonded(tokens) +//c.Status = Unbonded +//} +//} else { +//c.Status = Bonded +//} +//saveCandidate(store, c) +//} +//return cs +//} + +//// Validators - get the most recent updated validator set from the +//// Candidates. These bonds are already sorted by VotingPower from +//// the UpdateVotingPower function which is the only function which +//// is to modify the VotingPower +//func (cs Candidates) Validators() Validators { + +////test if empty +//if len(cs) == 1 { +//if cs[0].VotingPower.IsZero() { +//return nil +//} +//} + +//validators := make(Validators, len(cs)) +//for i, c := range cs { +//if c.VotingPower.IsZero() { //exit as soon as the first Voting power set to zero is found +//return validators[:i] +//} +//validators[i] = c.validator() +//} + +//return validators +//} + +////_________________________________________________________________________ + +//// Validators - list of Validators +//type Validators []Validator + +//var _ sort.Interface = Validators{} //enforce the sort interface at compile time + +//// nolint - sort interface functions +//func (vs Validators) Len() int { return len(vs) } +//func (vs Validators) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } +//func (vs Validators) Less(i, j int) bool { +//pk1, pk2 := vs[i].PubKey.Bytes(), vs[j].PubKey.Bytes() +//return bytes.Compare(pk1, pk2) == -1 +//} + +//// Sort - Sort validators by pubkey +//func (vs Validators) Sort() { +//sort.Sort(vs) +//} + +//// determine all updated validators between two validator sets +//func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validator) { + +////first sort the validator sets +//vs.Sort() +//vs2.Sort() + +//max := len(vs) + len(vs2) +//updated = make([]*abci.Validator, max) +//i, j, n := 0, 0, 0 //counters for vs loop, vs2 loop, updated element + +//for i < len(vs) && j < len(vs2) { + +//if !vs[i].PubKey.Equals(vs2[j].PubKey) { +//// pk1 > pk2, a new validator was introduced between these pubkeys +//if bytes.Compare(vs[i].PubKey.Bytes(), vs2[j].PubKey.Bytes()) == 1 { +//updated[n] = vs2[j].ABCIValidator() +//n++ +//j++ +//continue +//} // else, the old validator has been removed +//updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} +//n++ +//i++ +//continue +//} + +//if vs[i].VotingPower != vs2[j].VotingPower { +//updated[n] = vs2[j].ABCIValidator() +//n++ +//} +//j++ +//i++ +//} + +//// add any excess validators in set 2 +//for ; j < len(vs2); j, n = j+1, n+1 { +//updated[n] = vs2[j].ABCIValidator() +//} + +//// remove any excess validators left in set 1 +//for ; i < len(vs); i, n = i+1, n+1 { +//updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} +//} + +//return updated[:n] +//} + +//// UpdateValidatorSet - Updates the voting power for the candidate set and +//// returns the subset of validators which have been updated for Tendermint +//func UpdateValidatorSet(store types.KVStore, gs *GlobalState, params Params) (change []*abci.Validator, err error) { + +//// get the validators before update +//candidates := loadCandidates(store) + +//v1 := candidates.Validators() +//v2 := candidates.updateVotingPower(store, gs, params).Validators() + +//change = v1.validatorsUpdated(v2) +//return +//} + +////_________________________________________________________________________ + +//// DelegatorBond represents the bond with tokens held by an account. It is +//// owned by one delegator, and is associated with the voting power of one +//// pubKey. +//type DelegatorBond struct { +//PubKey crypto.PubKey `json:"pub_key"` +//Shares rational.Rat `json:"shares"` +//} From eb597e17e5fc06ea9b9450de5d1b7e6c001aec2c Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 22 Jan 2018 18:09:05 +0000 Subject: [PATCH 04/54] staking orig tests passing with new store --- .gitignore | 1 + x/stake/store.go | 56 +++++++++++++++++++++++-------------------- x/stake/store_test.go | 13 +++++----- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index 707ded5508..93905e1f2d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ examples/basecoin/glide.lock examples/basecoin/app/data baseapp/data/* docs/_build +<<<<<<< HEAD .DS_Store coverage.txt profile.out diff --git a/x/stake/store.go b/x/stake/store.go index e834f47384..2e13e8906a 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -3,13 +3,14 @@ package stake import ( crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" - "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/types" ) /////////////////////////////////////////////////////////// temp types +var cdc = wire.NewCodec() + //nolint type Params struct { HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held @@ -34,10 +35,10 @@ func defaultParams() Params { return Params{ HoldBonded: []byte("77777777777777777777777777777777"), HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: rational.New(13, 100), - InflationMax: rational.New(20, 100), - InflationMin: rational.New(7, 100), - GoalBonded: rational.New(67, 100), + InflationRateChange: 13, //rational.New(13, 100), + InflationMax: 20, //rational.New(20, 100), + InflationMin: 7, //rational.New(7, 100), + GoalBonded: 67, //rational.New(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -63,12 +64,12 @@ type GlobalState struct { func initialGlobalState() *GlobalState { return &GlobalState{ TotalSupply: 0, - BondedShares: rational.Zero, - UnbondedShares: rational.Zero, + BondedShares: 0, //rational.Zero, + UnbondedShares: 0, //rational.Zero, BondedPool: 0, UnbondedPool: 0, InflationLastTime: 0, - Inflation: rational.New(7, 100), + Inflation: 0, //rational.New(7, 100), } } @@ -99,6 +100,9 @@ type Candidate struct { Description Description `json:"description"` // Description terms for the candidate } +//nolint +type Candidates []*Candidate + // Description - description fields for a candidate type Description struct { Moniker string `json:"moniker"` @@ -113,9 +117,9 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri Status: Unbonded, PubKey: pubKey, Owner: owner, - Assets: rational.Zero, - Liabilities: rational.Zero, - VotingPower: rational.Zero, + Assets: 0, // rational.Zero, + Liabilities: 0, // rational.Zero, + VotingPower: 0, //rational.Zero, Description: description, } } @@ -153,7 +157,7 @@ func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []by // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { - res, err := wire.MarshalBinary(&delegator) + res, err := cdc.MarshalBinary(&delegator) if err != nil { panic(err) } @@ -162,7 +166,7 @@ func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { // GetDelegatorBondsKey - get the key for list of all the delegator's bonds func GetDelegatorBondsKey(delegator crypto.Address) []byte { - res, err := wire.MarshalBinary(&delegator) + res, err := cdc.MarshalBinary(&delegator) if err != nil { panic(err) } @@ -177,14 +181,14 @@ func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) { if bytes == nil { return } - err := wire.UnmarshalBinary(bytes, &pubKeys) + err := cdc.UnmarshalBinary(bytes, &pubKeys) if err != nil { panic(err) } return } func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) { - b, err := wire.MarshalBinary(pubKeys) + b, err := cdc.MarshalBinary(pubKeys) if err != nil { panic(err) } @@ -212,7 +216,7 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { return nil } candidate := new(Candidate) - err := wire.UnmarshalBinary(b, candidate) + err := cdc.UnmarshalBinary(b, candidate) if err != nil { panic(err) // This error should never occure big problem if does } @@ -227,7 +231,7 @@ func saveCandidate(store types.KVStore, candidate *Candidate) { saveCandidatesPubKeys(store, append(pks, candidate.PubKey)) } - b, err := wire.MarshalBinary(*candidate) + b, err := cdc.MarshalBinary(*candidate) if err != nil { panic(err) } @@ -259,7 +263,7 @@ func loadDelegatorCandidates(store types.KVStore, return nil } - err := wire.UnmarshalBinary(candidateBytes, &candidates) + err := cdc.UnmarshalBinary(candidateBytes, &candidates) if err != nil { panic(err) } @@ -277,7 +281,7 @@ func loadDelegatorBond(store types.KVStore, } bond := new(DelegatorBond) - err := wire.UnmarshalBinary(delegatorBytes, bond) + err := cdc.UnmarshalBinary(delegatorBytes, bond) if err != nil { panic(err) } @@ -290,7 +294,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele if loadDelegatorBond(store, delegator, bond.PubKey) == nil { pks := loadDelegatorCandidates(store, delegator) pks = append(pks, (*bond).PubKey) - b, err := wire.MarshalBinary(pks) + b, err := cdc.MarshalBinary(pks) if err != nil { panic(err) } @@ -298,7 +302,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele } // now actually save the bond - b, err := wire.MarshalBinary(*bond) + b, err := cdc.MarshalBinary(*bond) if err != nil { panic(err) } @@ -316,7 +320,7 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat pks = append(pks[:i], pks[i+1:]...) } } - b, err := wire.MarshalBinary(pks) + b, err := cdc.MarshalBinary(pks) if err != nil { panic(err) } @@ -369,7 +373,7 @@ func loadParams(store types.KVStore) (params Params) { return defaultParams() } - err := wire.UnmarshalBinary(b, ¶ms) + err := cdc.UnmarshalBinary(b, ¶ms) if err != nil { panic(err) // This error should never occure big problem if does } @@ -377,7 +381,7 @@ func loadParams(store types.KVStore) (params Params) { return } func saveParams(store types.KVStore, params Params) { - b, err := wire.MarshalBinary(params) + b, err := cdc.MarshalBinary(params) if err != nil { panic(err) } @@ -393,14 +397,14 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { return initialGlobalState() } gs = new(GlobalState) - err := wire.UnmarshalBinary(b, gs) + err := cdc.UnmarshalBinary(b, gs) if err != nil { panic(err) // This error should never occure big problem if does } return } func saveGlobalState(store types.KVStore, gs *GlobalState) { - b, err := wire.MarshalBinary(*gs) + b, err := cdc.MarshalBinary(*gs) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index dd77aafa56..bf0287f39c 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" dbm "github.com/tendermint/tmlibs/db" ) @@ -38,8 +37,8 @@ func TestState(t *testing.T) { multiStore.SetSubstoreLoader(stakeStoreKey, stakeLoader) multiStore.LoadLatestVersion() store := multiStore.GetKVStore(stakeStoreKey) - wire.RegisterInterface((*crypto.PubKey)(nil), nil) - wire.RegisterConcrete(crypto.PubKeyEd25519{}, "crypto/PubKeyEd25519", nil) + cdc.RegisterInterface((*crypto.PubKey)(nil), nil) + cdc.RegisterConcrete(crypto.PubKeyEd25519{}, "crypto/PubKeyEd25519", nil) //delegator := crypto.Address{[]byte("addressdelegator")} //validator := crypto.Address{[]byte("addressvalidator")} @@ -65,9 +64,9 @@ func TestState(t *testing.T) { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && bytes.Equal(c1.Owner, c2.Owner) && - c1.Assets.Equal(c2.Assets) && - c1.Liabilities.Equal(c2.Liabilities) && - c1.VotingPower.Equal(c2.VotingPower) && + c1.Assets == c2.Assets && + c1.Liabilities == c2.Liabilities && + c1.VotingPower == c2.VotingPower && c1.Description == c2.Description } @@ -103,7 +102,7 @@ func TestState(t *testing.T) { bondsEqual := func(b1, b2 *DelegatorBond) bool { return b1.PubKey.Equals(b2.PubKey) && - b1.Shares.Equal(b2.Shares) + b1.Shares == b2.Shares } //check the empty store first From 2593f39de58e7f8ef1d005cc312084ae2b89b53e Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 23 Jan 2018 09:04:36 +0000 Subject: [PATCH 05/54] wip two store model gaia --- x/stake/store.go | 91 +++++---- x/stake/store_test.go | 13 ++ x/stake/types.go | 418 ------------------------------------------ 3 files changed, 73 insertions(+), 449 deletions(-) delete mode 100644 x/stake/types.go diff --git a/x/stake/store.go b/x/stake/store.go index 2e13e8906a..f0a2e38179 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -102,6 +102,8 @@ type Candidate struct { //nolint type Candidates []*Candidate +type Validator Candidate +type Validators []Validator // Description - description fields for a candidate type Description struct { @@ -175,37 +177,6 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { //--------------------------------------------------------------------- -// Get the active list of all the candidate pubKeys and owners -func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) { - bytes := store.Get(CandidatesPubKeysKey) - if bytes == nil { - return - } - err := cdc.UnmarshalBinary(bytes, &pubKeys) - if err != nil { - panic(err) - } - return -} -func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) { - b, err := cdc.MarshalBinary(pubKeys) - if err != nil { - panic(err) - } - store.Set(CandidatesPubKeysKey, b) -} - -// loadCandidates - get the active list of all candidates TODO replace with multistore -func loadCandidates(store types.KVStore) (candidates Candidates) { - pks := loadCandidatesPubKeys(store) - for _, pk := range pks { - candidates = append(candidates, loadCandidate(store, pk)) - } - return -} - -//--------------------------------------------------------------------- - // loadCandidate - loads the candidate object for the provided pubkey func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { //if pubKey.Empty() { @@ -254,6 +225,64 @@ func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { //--------------------------------------------------------------------- +// Get the active list of all the candidate pubKeys and owners +func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) { + bytes := store.Get(CandidatesPubKeysKey) + if bytes == nil { + return + } + err := cdc.UnmarshalBinary(bytes, &pubKeys) + if err != nil { + panic(err) + } + return +} +func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) { + b, err := cdc.MarshalBinary(pubKeys) + if err != nil { + panic(err) + } + store.Set(CandidatesPubKeysKey, b) +} + +// loadCandidates - get the active list of all candidates TODO replace with multistore +func loadCandidates(store types.KVStore) (candidates Candidates) { + pks := loadCandidatesPubKeys(store) + for _, pk := range pks { + candidates = append(candidates, loadCandidate(store, pk)) + } + return +} + +// Validators - get the most recent updated validator set from the +// Candidates. These bonds are already sorted by VotingPower from +// the UpdateVotingPower function which is the only function which +// is to modify the VotingPower +func getValidators(powerStore types.KVStore, maxVal int) Validators { + + iterator := powerStore.Iterator([]byte{}, []byte{nil}) //smallest to largest + + validators := make(Validators, maxVal) + for i := 0; ; i++ { + if !iterator.Valid() || i > maxVal { + iterator.Close() + break + } + pkBytes := iterator.Value() + var pk crypto.PubKey + err := cdc.UnmarshalBinary(pkBytes, &pk) + if err != nil { + panic(err) + } + validators[i] = pk + iterator.Next() + } + + return validators +} + +//--------------------------------------------------------------------- + // load the pubkeys of all candidates a delegator is delegated too func loadDelegatorCandidates(store types.KVStore, delegator crypto.Address) (candidates []crypto.PubKey) { diff --git a/x/stake/store_test.go b/x/stake/store_test.go index bf0287f39c..868dced6c6 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -135,3 +135,16 @@ func TestState(t *testing.T) { resParams = loadParams(store) assert.Equal(params, resParams) } + +//func TestGetValidators(t *testing.T) { +//assert, require := assert.New(t), require.New(t) + +//N := 5 +//actors := newActors(N) +//candidates := candidatesFromActors(actors, []int{400, 200, 0, 0, 0}) + +//validators := candidates.Validators() +//require.Equal(2, len(validators)) +//assert.Equal(candidates[0].PubKey, validators[0].PubKey) +//assert.Equal(candidates[1].PubKey, validators[1].PubKey) +//} diff --git a/x/stake/types.go b/x/stake/types.go deleted file mode 100644 index 36caeb35bf..0000000000 --- a/x/stake/types.go +++ /dev/null @@ -1,418 +0,0 @@ -package stake - -//import ( -//"bytes" -//"sort" - -//"github.com/cosmos/cosmos-sdk/types" - -//abci "github.com/tendermint/abci/types" -//crypto "github.com/tendermint/go-crypto" -//wire "github.com/tendermint/go-wire" -//"github.com/tendermint/tmlibs/rational" -//) - -//// Params defines the high level settings for staking -//type Params struct { -//HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held -//HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - -//InflationRateChange rational.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate -//InflationMax rational.Rat `json:"inflation_max"` // maximum inflation rate -//InflationMin rational.Rat `json:"inflation_min"` // minimum inflation rate -//GoalBonded rational.Rat `json:"goal_bonded"` // Goal of percent bonded atoms - -//MaxVals uint16 `json:"max_vals"` // maximum number of validators -//AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination - -//// gas costs for txs -//GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` -//GasEditCandidacy int64 `json:"gas_edit_candidacy"` -//GasDelegate int64 `json:"gas_delegate"` -//GasUnbond int64 `json:"gas_unbond"` -//} - -//func defaultParams() Params { -//return Params{ -//HoldBonded: []byte("77777777777777777777777777777777"), -//HoldUnbonded: []byte("88888888888888888888888888888888"), -//InflationRateChange: rational.New(13, 100), -//InflationMax: rational.New(20, 100), -//InflationMin: rational.New(7, 100), -//GoalBonded: rational.New(67, 100), -//MaxVals: 100, -//AllowedBondDenom: "fermion", -//GasDeclareCandidacy: 20, -//GasEditCandidacy: 20, -//GasDelegate: 20, -//GasUnbond: 20, -//} -//} - -////_________________________________________________________________________ - -//// GlobalState - dynamic parameters of the current state -//type GlobalState struct { -//TotalSupply int64 `json:"total_supply"` // total supply of all tokens -//BondedShares rational.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool -//UnbondedShares rational.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool -//BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens -//UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates -//InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time -//Inflation rational.Rat `json:"inflation"` // current annual inflation rate -//} - -//// XXX define globalstate interface? - -//func initialGlobalState() *GlobalState { -//return &GlobalState{ -//TotalSupply: 0, -//BondedShares: rational.Zero, -//UnbondedShares: rational.Zero, -//BondedPool: 0, -//UnbondedPool: 0, -//InflationLastTime: 0, -//Inflation: rational.New(7, 100), -//} -//} - -//// get the bond ratio of the global state -//func (gs *GlobalState) bondedRatio() rational.Rat { -//if gs.TotalSupply > 0 { -//return rational.New(gs.BondedPool, gs.TotalSupply) -//} -//return rational.Zero -//} - -//// get the exchange rate of bonded token per issued share -//func (gs *GlobalState) bondedShareExRate() rational.Rat { -//if gs.BondedShares.IsZero() { -//return rational.One -//} -//return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) -//} - -//// get the exchange rate of unbonded tokens held in candidates per issued share -//func (gs *GlobalState) unbondedShareExRate() rational.Rat { -//if gs.UnbondedShares.IsZero() { -//return rational.One -//} -//return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) -//} - -//func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rat) { -//issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens -//gs.BondedPool += amount -//gs.BondedShares = gs.BondedShares.Add(issuedShares) -//return -//} - -//func (gs *GlobalState) removeSharesBonded(shares rational.Rat) (removedTokens int64) { -//removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares -//gs.BondedShares = gs.BondedShares.Sub(shares) -//gs.BondedPool -= removedTokens -//return -//} - -//func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rat) { -//issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens -//gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) -//gs.UnbondedPool += amount -//return -//} - -//func (gs *GlobalState) removeSharesUnbonded(shares rational.Rat) (removedTokens int64) { -//removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares -//gs.UnbondedShares = gs.UnbondedShares.Sub(shares) -//gs.UnbondedPool -= removedTokens -//return -//} - -////_______________________________________________________________________________________________________ - -//// CandidateStatus - status of a validator-candidate -//type CandidateStatus byte - -//const ( -//// nolint -//Bonded CandidateStatus = 0x00 -//Unbonded CandidateStatus = 0x01 -//Revoked CandidateStatus = 0x02 -//) - -//// Candidate defines the total amount of bond shares and their exchange rate to -//// coins. Accumulation of interest is modelled as an in increase in the -//// exchange rate, and slashing as a decrease. When coins are delegated to this -//// candidate, the candidate is credited with a DelegatorBond whose number of -//// bond shares is based on the amount of coins delegated divided by the current -//// exchange rate. Voting power can be calculated as total bonds multiplied by -//// exchange rate. -//type Candidate struct { -//Status CandidateStatus `json:"status"` // Bonded status -//PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate -//Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here -//Assets rational.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares -//Liabilities rational.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares -//VotingPower rational.Rat `json:"voting_power"` // Voting power if considered a validator -//Description Description `json:"description"` // Description terms for the candidate -//} - -//// Description - description fields for a candidate -//type Description struct { -//Moniker string `json:"moniker"` -//Identity string `json:"identity"` -//Website string `json:"website"` -//Details string `json:"details"` -//} - -//// NewCandidate - initialize a new candidate -//func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { -//return &Candidate{ -//Status: Unbonded, -//PubKey: pubKey, -//Owner: owner, -//Assets: rational.Zero, -//Liabilities: rational.Zero, -//VotingPower: rational.Zero, -//Description: description, -//} -//} - -//// XXX define candidate interface? - -//// get the exchange rate of global pool shares over delegator shares -//func (c *Candidate) delegatorShareExRate() rational.Rat { -//if c.Liabilities.IsZero() { -//return rational.One -//} -//return c.Assets.Quo(c.Liabilities) -//} - -//// add tokens to a candidate -//func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rat) { - -//exRate := c.delegatorShareExRate() - -//var receivedGlobalShares rational.Rat -//if c.Status == Bonded { -//receivedGlobalShares = gs.addTokensBonded(amount) -//} else { -//receivedGlobalShares = gs.addTokensUnbonded(amount) -//} -//c.Assets = c.Assets.Add(receivedGlobalShares) - -//issuedDelegatorShares = exRate.Mul(receivedGlobalShares) -//c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) -//return -//} - -//// remove shares from a candidate -//func (c *Candidate) removeShares(shares rational.Rat, gs *GlobalState) (removedTokens int64) { - -//globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) - -//if c.Status == Bonded { -//removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) -//} else { -//removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) -//} -//c.Assets = c.Assets.Sub(globalPoolSharesToRemove) - -//c.Liabilities = c.Liabilities.Sub(shares) -//return -//} - -//// Validator returns a copy of the Candidate as a Validator. -//// Should only be called when the Candidate qualifies as a validator. -//func (c *Candidate) validator() Validator { -//return Validator(*c) -//} - -//// Validator is one of the top Candidates -//type Validator Candidate - -//// ABCIValidator - Get the validator from a bond value -//func (v Validator) ABCIValidator() *abci.Validator { -//pk, err := wire.MarshalBinary(v.PubKey) -//if err != nil { -//panic(err) -//} - -//return &abci.Validator{ -//PubKey: pk, -//Power: v.VotingPower.Evaluate(), -//} -//} - -////_________________________________________________________________________ - -//// TODO replace with sorted multistore functionality - -//// Candidates - list of Candidates -//type Candidates []*Candidate - -//var _ sort.Interface = Candidates{} //enforce the sort interface at compile time - -//// nolint - sort interface functions -//func (cs Candidates) Len() int { return len(cs) } -//func (cs Candidates) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } -//func (cs Candidates) Less(i, j int) bool { -//vp1, vp2 := cs[i].VotingPower, cs[j].VotingPower -//pk1, pk2 := cs[i].PubKey.Bytes(), cs[j].PubKey.Bytes() - -////note that all ChainId and App must be the same for a group of candidates -//if vp1 != vp2 { -//return vp1.GT(vp2) -//} -//return bytes.Compare(pk1, pk2) == -1 -//} - -//// Sort - Sort the array of bonded values -//func (cs Candidates) Sort() { -//sort.Sort(cs) -//} - -//// update the voting power and save -//func (cs Candidates) updateVotingPower(store types.KVStore, gs *GlobalState, params Params) Candidates { - -//// update voting power -//for _, c := range cs { -//if !c.VotingPower.Equal(c.Assets) { -//c.VotingPower = c.Assets -//} -//} -//cs.Sort() -//for i, c := range cs { -//// truncate the power -//if i >= int(params.MaxVals) { -//c.VotingPower = rational.Zero -//if c.Status == Bonded { -//// XXX to replace this with handler.bondedToUnbondePool function -//// XXX waiting for logic with new SDK to update account balance here -//tokens := gs.removeSharesBonded(c.Assets) -//c.Assets = gs.addTokensUnbonded(tokens) -//c.Status = Unbonded -//} -//} else { -//c.Status = Bonded -//} -//saveCandidate(store, c) -//} -//return cs -//} - -//// Validators - get the most recent updated validator set from the -//// Candidates. These bonds are already sorted by VotingPower from -//// the UpdateVotingPower function which is the only function which -//// is to modify the VotingPower -//func (cs Candidates) Validators() Validators { - -////test if empty -//if len(cs) == 1 { -//if cs[0].VotingPower.IsZero() { -//return nil -//} -//} - -//validators := make(Validators, len(cs)) -//for i, c := range cs { -//if c.VotingPower.IsZero() { //exit as soon as the first Voting power set to zero is found -//return validators[:i] -//} -//validators[i] = c.validator() -//} - -//return validators -//} - -////_________________________________________________________________________ - -//// Validators - list of Validators -//type Validators []Validator - -//var _ sort.Interface = Validators{} //enforce the sort interface at compile time - -//// nolint - sort interface functions -//func (vs Validators) Len() int { return len(vs) } -//func (vs Validators) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] } -//func (vs Validators) Less(i, j int) bool { -//pk1, pk2 := vs[i].PubKey.Bytes(), vs[j].PubKey.Bytes() -//return bytes.Compare(pk1, pk2) == -1 -//} - -//// Sort - Sort validators by pubkey -//func (vs Validators) Sort() { -//sort.Sort(vs) -//} - -//// determine all updated validators between two validator sets -//func (vs Validators) validatorsUpdated(vs2 Validators) (updated []*abci.Validator) { - -////first sort the validator sets -//vs.Sort() -//vs2.Sort() - -//max := len(vs) + len(vs2) -//updated = make([]*abci.Validator, max) -//i, j, n := 0, 0, 0 //counters for vs loop, vs2 loop, updated element - -//for i < len(vs) && j < len(vs2) { - -//if !vs[i].PubKey.Equals(vs2[j].PubKey) { -//// pk1 > pk2, a new validator was introduced between these pubkeys -//if bytes.Compare(vs[i].PubKey.Bytes(), vs2[j].PubKey.Bytes()) == 1 { -//updated[n] = vs2[j].ABCIValidator() -//n++ -//j++ -//continue -//} // else, the old validator has been removed -//updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} -//n++ -//i++ -//continue -//} - -//if vs[i].VotingPower != vs2[j].VotingPower { -//updated[n] = vs2[j].ABCIValidator() -//n++ -//} -//j++ -//i++ -//} - -//// add any excess validators in set 2 -//for ; j < len(vs2); j, n = j+1, n+1 { -//updated[n] = vs2[j].ABCIValidator() -//} - -//// remove any excess validators left in set 1 -//for ; i < len(vs); i, n = i+1, n+1 { -//updated[n] = &abci.Validator{vs[i].PubKey.Bytes(), 0} -//} - -//return updated[:n] -//} - -//// UpdateValidatorSet - Updates the voting power for the candidate set and -//// returns the subset of validators which have been updated for Tendermint -//func UpdateValidatorSet(store types.KVStore, gs *GlobalState, params Params) (change []*abci.Validator, err error) { - -//// get the validators before update -//candidates := loadCandidates(store) - -//v1 := candidates.Validators() -//v2 := candidates.updateVotingPower(store, gs, params).Validators() - -//change = v1.validatorsUpdated(v2) -//return -//} - -////_________________________________________________________________________ - -//// DelegatorBond represents the bond with tokens held by an account. It is -//// owned by one delegator, and is associated with the voting power of one -//// pubKey. -//type DelegatorBond struct { -//PubKey crypto.PubKey `json:"pub_key"` -//Shares rational.Rat `json:"shares"` -//} From 9a1a89247bc96aa5c77d048009a41fdf83563a82 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 25 Jan 2018 09:06:25 +0000 Subject: [PATCH 06/54] porting over gaia store --- x/stake/store.go | 129 ++++++++++++++++++++++++++---------------- x/stake/store_test.go | 34 +++++++---- 2 files changed, 103 insertions(+), 60 deletions(-) diff --git a/x/stake/store.go b/x/stake/store.go index f0a2e38179..ded46418dc 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -1,6 +1,8 @@ package stake import ( + "encoding/binary" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" @@ -102,7 +104,10 @@ type Candidate struct { //nolint type Candidates []*Candidate -type Validator Candidate +type Validator struct { + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + VotingPower int64 `json:"voting_power"` // Voting power if considered a validator +} type Validators []Validator // Description - description fields for a candidate @@ -143,8 +148,9 @@ var ( // Key prefixes CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x06} // prefix for each key to a delegator's bond + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x06} // prefix for each key to a delegator's bond + DelegatorBondsKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond ) // GetCandidateKey - get the key for the candidate with pubKey @@ -152,6 +158,13 @@ func GetCandidateKey(pubKey crypto.PubKey) []byte { return append(CandidateKeyPrefix, pubKey.Bytes()...) } +// GetValidatorKey - get the key for the validator used in the power-store +func GetValidatorKey(pubKey crypto.PubKey, power int64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, uint64(power)) + return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store +} + // GetDelegatorBondKey - get the key for delegator bond with candidate func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte { return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) @@ -177,7 +190,6 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { //--------------------------------------------------------------------- -// loadCandidate - loads the candidate object for the provided pubkey func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { //if pubKey.Empty() { //return nil @@ -189,18 +201,16 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { candidate := new(Candidate) err := cdc.UnmarshalBinary(b, candidate) if err != nil { - panic(err) // This error should never occure big problem if does + panic(err) // This error should never occur big problem if does } return candidate } func saveCandidate(store types.KVStore, candidate *Candidate) { - if !store.Has(GetCandidateKey(candidate.PubKey)) { - // TODO to be replaced with iteration in the multistore? - pks := loadCandidatesPubKeys(store) - saveCandidatesPubKeys(store, append(pks, candidate.PubKey)) - } + removeValidatorFromKey(store, candidate.PubKey) + validator := &Validator{candidate.PubKey, candidate.VotingPower} + saveValidator(store, validator) b, err := cdc.MarshalBinary(*candidate) if err != nil { @@ -210,57 +220,52 @@ func saveCandidate(store types.KVStore, candidate *Candidate) { } func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { + removeValidatorFromKey(store, pubKey) store.Delete(GetCandidateKey(pubKey)) - - // TODO to be replaced with iteration in the multistore? - pks := loadCandidatesPubKeys(store) - for i := range pks { - if pks[i].Equals(pubKey) { - saveCandidatesPubKeys(store, - append(pks[:i], pks[i+1:]...)) - break - } - } } //--------------------------------------------------------------------- -// Get the active list of all the candidate pubKeys and owners -func loadCandidatesPubKeys(store types.KVStore) (pubKeys []crypto.PubKey) { - bytes := store.Get(CandidatesPubKeysKey) - if bytes == nil { - return +func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) *Validator { + b := store.Get(GetValidatorKey(pubKey, votingPower)) + if b == nil { + return nil } - err := cdc.UnmarshalBinary(bytes, &pubKeys) + validator := new(Validator) + err := cdc.UnmarshalBinary(b, validator) if err != nil { - panic(err) + panic(err) // This error should never occur big problem if does } - return -} -func saveCandidatesPubKeys(store types.KVStore, pubKeys []crypto.PubKey) { - b, err := cdc.MarshalBinary(pubKeys) - if err != nil { - panic(err) - } - store.Set(CandidatesPubKeysKey, b) + return validator } -// loadCandidates - get the active list of all candidates TODO replace with multistore -func loadCandidates(store types.KVStore) (candidates Candidates) { - pks := loadCandidatesPubKeys(store) - for _, pk := range pks { - candidates = append(candidates, loadCandidate(store, pk)) +func saveValidator(store types.KVStore, validator *Validator) { + b, err := cdc.MarshalBinary(*validator) + if err != nil { + panic(err) + } + store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) +} + +func removeValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) { + store.Delete(GetValidatorKey(pubKey, votingPower)) +} + +func removeValidatorFromKey(store types.KVStore, pubKey crypto.PubKey) { + // remove validator if already there, then add new validator + candidate := loadCandidate(store, pubKey) + if candidate != nil { + removeValidator(store, pubKey, candidate.VotingPower) } - return } // Validators - get the most recent updated validator set from the // Candidates. These bonds are already sorted by VotingPower from // the UpdateVotingPower function which is the only function which // is to modify the VotingPower -func getValidators(powerStore types.KVStore, maxVal int) Validators { +func getValidators(store types.KVStore, maxVal int) Validators { - iterator := powerStore.Iterator([]byte{}, []byte{nil}) //smallest to largest + iterator := store.Iterator(ValidatorKeyPrefix, ValidatorKeyPrefix) //smallest to largest validators := make(Validators, maxVal) for i := 0; ; i++ { @@ -268,13 +273,13 @@ func getValidators(powerStore types.KVStore, maxVal int) Validators { iterator.Close() break } - pkBytes := iterator.Value() - var pk crypto.PubKey - err := cdc.UnmarshalBinary(pkBytes, &pk) + valBytes := iterator.Value() + var val Validator + err := cdc.UnmarshalBinary(valBytes, &val) if err != nil { panic(err) } - validators[i] = pk + validators[i] = val iterator.Next() } @@ -283,6 +288,31 @@ func getValidators(powerStore types.KVStore, maxVal int) Validators { //--------------------------------------------------------------------- +// loadCandidates - get the active list of all candidates TODO replace with multistore +func loadCandidates(store types.KVStore) (candidates Candidates) { + + iterator := store.Iterator(CandidateKeyPrefix, CandidateKeyPrefix) //smallest to largest + + for i := 0; ; i++ { + if !iterator.Valid() { + iterator.Close() + break + } + candidateBytes := iterator.Value() + var candidate Candidate + err := cdc.UnmarshalBinary(candidateBytes, &candidate) + if err != nil { + panic(err) + } + candidates[i] = &candidate + iterator.Next() + } + + return candidates +} + +//--------------------------------------------------------------------- + // load the pubkeys of all candidates a delegator is delegated too func loadDelegatorCandidates(store types.KVStore, delegator crypto.Address) (candidates []crypto.PubKey) { @@ -340,7 +370,6 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele } func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidate crypto.PubKey) { - // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds pks := loadDelegatorCandidates(store, delegator) @@ -404,7 +433,7 @@ func loadParams(store types.KVStore) (params Params) { err := cdc.UnmarshalBinary(b, ¶ms) if err != nil { - panic(err) // This error should never occure big problem if does + panic(err) // This error should never occur big problem if does } return @@ -428,7 +457,7 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { gs = new(GlobalState) err := cdc.UnmarshalBinary(b, gs) if err != nil { - panic(err) // This error should never occure big problem if does + panic(err) // This error should never occur big problem if does } return } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index 868dced6c6..e82154a6c2 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -136,15 +136,29 @@ func TestState(t *testing.T) { assert.Equal(params, resParams) } -//func TestGetValidators(t *testing.T) { -//assert, require := assert.New(t), require.New(t) +func candidatesFromActors(actors []sdk.Actor, amts []int) (candidates Candidates) { + for i := 0; i < len(actors); i++ { + c := &Candidate{ + PubKey: pks[i], + Owner: actors[i], + Shares: int64(amts[i]), + VotingPower: int64(amts[i]), + } + candidates = append(candidates, c) + } -//N := 5 -//actors := newActors(N) -//candidates := candidatesFromActors(actors, []int{400, 200, 0, 0, 0}) + return +} -//validators := candidates.Validators() -//require.Equal(2, len(validators)) -//assert.Equal(candidates[0].PubKey, validators[0].PubKey) -//assert.Equal(candidates[1].PubKey, validators[1].PubKey) -//} +func TestGetValidators(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + N := 5 + actors := newActors(N) + candidates := candidatesFromActors(actors, []int{400, 200, 0, 0, 0}) + + validators := candidates.Validators() + require.Equal(2, len(validators)) + assert.Equal(candidates[0].PubKey, validators[0].PubKey) + assert.Equal(candidates[1].PubKey, validators[1].PubKey) +} From 6b9d836f406981a4965bc395a8b99e7bd027f18b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 25 Jan 2018 20:11:58 +0000 Subject: [PATCH 07/54] gaia store compile errors resolved --- .gitignore | 1 + x/stake/store_test.go | 71 ++++++++++++---------------------- x/stake/test_common.go | 87 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 x/stake/test_common.go diff --git a/.gitignore b/.gitignore index 93905e1f2d..ed8f93c683 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ examples/basecoin/app/data baseapp/data/* docs/_build <<<<<<< HEAD +<<<<<<< HEAD .DS_Store coverage.txt profile.out diff --git a/x/stake/store_test.go b/x/stake/store_test.go index e82154a6c2..a1955862d9 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -2,41 +2,32 @@ package stake import ( "bytes" - "encoding/hex" "testing" - sdkstore "github.com/cosmos/cosmos-sdk/store" - "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + dbm "github.com/tendermint/tmlibs/db" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/tmlibs/db" ) -func newPubKey(pk string) (res crypto.PubKey, err error) { - pkBytes, err := hex.DecodeString(pk) - if err != nil { - return - } - //res, err = crypto.PubKeyFromBytes(pkBytes) - var pkEd crypto.PubKeyEd25519 - copy(pkEd[:], pkBytes[:]) - return pkEd, nil +func initTestStore(t *testing.T) sdk.KVStore { + // Capabilities key to access the main KVStore. + db, err := dbm.NewGoLevelDB("stake", "data") + require.Nil(t, err) + stakeStoreKey := sdk.NewKVStoreKey("stake") + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + return ms.GetKVStore(stakeStoreKey) } func TestState(t *testing.T) { assert, require := assert.New(t), require.New(t) - db, err := dbm.NewGoLevelDB("basecoin", "basecoin-data") - require.Nil(err) - cacheSize := 10000 - numHistory := int64(100) - stakeLoader := sdkstore.NewIAVLStoreLoader(db, cacheSize, numHistory) - var stakeStoreKey = types.NewKVStoreKey("stake") - multiStore := sdkstore.NewCommitMultiStore(db) - multiStore.SetSubstoreLoader(stakeStoreKey, stakeLoader) - multiStore.LoadLatestVersion() - store := multiStore.GetKVStore(stakeStoreKey) + store := initTestStore(t) cdc.RegisterInterface((*crypto.PubKey)(nil), nil) cdc.RegisterConcrete(crypto.PubKeyEd25519{}, "crypto/PubKeyEd25519", nil) @@ -45,8 +36,7 @@ func TestState(t *testing.T) { delegator := []byte("addressdelegator") validator := []byte("addressvalidator") - pk, err := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") - require.Nil(err) + pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") //---------------------------------------------------------------------- // Candidate checks @@ -73,7 +63,7 @@ func TestState(t *testing.T) { // check the empty store first resCand := loadCandidate(store, pk) assert.Nil(resCand) - resPks := loadCandidatesPubKeys(store) + resPks := loadCandidates(store) assert.Zero(len(resPks)) // set and retrieve a record @@ -88,9 +78,9 @@ func TestState(t *testing.T) { assert.True(candidatesEqual(candidate, resCand)) // also test that the pubkey has been added to pubkey list - resPks = loadCandidatesPubKeys(store) + resPks = loadCandidates(store) require.Equal(1, len(resPks)) - assert.Equal(pk, resPks[0]) + assert.Equal(pk, resPks[0].PubKey) //---------------------------------------------------------------------- // Bond checks @@ -136,29 +126,16 @@ func TestState(t *testing.T) { assert.Equal(params, resParams) } -func candidatesFromActors(actors []sdk.Actor, amts []int) (candidates Candidates) { - for i := 0; i < len(actors); i++ { - c := &Candidate{ - PubKey: pks[i], - Owner: actors[i], - Shares: int64(amts[i]), - VotingPower: int64(amts[i]), - } - candidates = append(candidates, c) - } - - return -} - func TestGetValidators(t *testing.T) { assert, require := assert.New(t), require.New(t) + store := initTestStore(t) N := 5 - actors := newActors(N) - candidates := candidatesFromActors(actors, []int{400, 200, 0, 0, 0}) + addrs := newAddrs(N) + candidatesFromActors(store, addrs, []int{400, 200, 0, 0, 0}) - validators := candidates.Validators() + validators := getValidators(store, 5) require.Equal(2, len(validators)) - assert.Equal(candidates[0].PubKey, validators[0].PubKey) - assert.Equal(candidates[1].PubKey, validators[1].PubKey) + assert.Equal(pks[0], validators[0].PubKey) + assert.Equal(pks[1], validators[1].PubKey) } diff --git a/x/stake/test_common.go b/x/stake/test_common.go new file mode 100644 index 0000000000..57ab56434b --- /dev/null +++ b/x/stake/test_common.go @@ -0,0 +1,87 @@ +package stake + +import ( + "encoding/hex" + "fmt" + + crypto "github.com/tendermint/go-crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func newAddrs(n int) (addrs []crypto.Address) { + for i := 0; i < n; i++ { + addrs = append(addrs, []byte(fmt.Sprintf("addr%d", i))) + } + return +} + +func newPubKey(pk string) (res crypto.PubKey) { + pkBytes, err := hex.DecodeString(pk) + if err != nil { + panic(err) + } + //res, err = crypto.PubKeyFromBytes(pkBytes) + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + return pkEd +} + +// dummy pubkeys used for testing +var pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB60"), +} + +// NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey +// instead this is just being set the address here for testing purposes +func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int) { + for i := 0; i < len(addrs); i++ { + c := &Candidate{ + Status: Unbonded, + PubKey: pks[i], + Owner: addrs[i], + Assets: int64(amts[i]), //rational.New(amts[i]), + Liabilities: int64(amts[i]), //rational.New(amts[i]), + VotingPower: int64(amts[i]), //rational.New(amts[i]), + } + saveCandidate(store, c) + } +} + +func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { + for i := 0; i < len(addrs); i++ { + c := &Candidate{ + Status: Unbonded, + PubKey: pks[i], + Owner: addrs[i], + Assets: 0, //rational.Zero, + Liabilities: 0, //rational.Zero, + VotingPower: 0, //rational.Zero, + } + candidates = append(candidates, c) + } + return +} + +//// helper function test if Candidate is changed asabci.Validator +//func testChange(t *testing.T, val Validator, chg *abci.Validator) { +//assert := assert.New(t) +//assert.Equal(val.PubKey.Bytes(), chg.PubKey) +//assert.Equal(val.VotingPower.Evaluate(), chg.Power) +//} + +//// helper function test if Candidate is removed as abci.Validator +//func testRemove(t *testing.T, val Validator, chg *abci.Validator) { +//assert := assert.New(t) +//assert.Equal(val.PubKey.Bytes(), chg.PubKey) +//assert.Equal(int64(0), chg.Power) +//} From e00f285379ebd935fa45172f81eb41b39cab10e5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 25 Jan 2018 21:11:40 +0000 Subject: [PATCH 08/54] store subspace --- x/stake/store.go | 4 ++-- x/stake/test_common.go | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/x/stake/store.go b/x/stake/store.go index ded46418dc..8f38bc9145 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -265,7 +265,7 @@ func removeValidatorFromKey(store types.KVStore, pubKey crypto.PubKey) { // is to modify the VotingPower func getValidators(store types.KVStore, maxVal int) Validators { - iterator := store.Iterator(ValidatorKeyPrefix, ValidatorKeyPrefix) //smallest to largest + iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators := make(Validators, maxVal) for i := 0; ; i++ { @@ -291,7 +291,7 @@ func getValidators(store types.KVStore, maxVal int) Validators { // loadCandidates - get the active list of all candidates TODO replace with multistore func loadCandidates(store types.KVStore) (candidates Candidates) { - iterator := store.Iterator(CandidateKeyPrefix, CandidateKeyPrefix) //smallest to largest + iterator := store.Iterator(subspace(CandidateKeyPrefix)) //smallest to largest for i := 0; ; i++ { if !iterator.Valid() { diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 57ab56434b..c7e1be3368 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -9,6 +9,12 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +func subspace(prefix []byte) (start, end []byte) { + start, end = prefix, prefix + end[len(end)-1]++ + return +} + func newAddrs(n int) (addrs []crypto.Address) { for i := 0; i < n; i++ { addrs = append(addrs, []byte(fmt.Sprintf("addr%d", i))) From 73eb98ca7f538163a2063666c5f48062a48e2596 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 26 Jan 2018 03:31:07 +0000 Subject: [PATCH 09/54] gaia store debugging --- x/stake/store.go | 136 ++---------------------------------------- x/stake/store_test.go | 1 + x/stake/types.go | 132 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 132 deletions(-) create mode 100644 x/stake/types.go diff --git a/x/stake/store.go b/x/stake/store.go index 8f38bc9145..ec1bb8a20c 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -4,141 +4,10 @@ import ( "encoding/binary" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" "github.com/cosmos/cosmos-sdk/types" ) -/////////////////////////////////////////////////////////// temp types - -var cdc = wire.NewCodec() - -//nolint -type Params struct { - HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held - HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - - InflationRateChange int64 `json:"inflation_rate_change"` // XXX maximum annual change in inflation rate - InflationMax int64 `json:"inflation_max"` // XXX maximum inflation rate - InflationMin int64 `json:"inflation_min"` // XXX minimum inflation rate - GoalBonded int64 `json:"goal_bonded"` // XXX Goal of percent bonded atoms - - MaxVals uint16 `json:"max_vals"` // maximum number of validators - AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` - GasEditCandidacy int64 `json:"gas_edit_candidacy"` - GasDelegate int64 `json:"gas_delegate"` - GasUnbond int64 `json:"gas_unbond"` -} - -func defaultParams() Params { - return Params{ - HoldBonded: []byte("77777777777777777777777777777777"), - HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: 13, //rational.New(13, 100), - InflationMax: 20, //rational.New(20, 100), - InflationMin: 7, //rational.New(7, 100), - GoalBonded: 67, //rational.New(67, 100), - MaxVals: 100, - AllowedBondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, - } -} - -// GlobalState - dynamic parameters of the current state -type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares int64 `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares int64 `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation int64 `json:"inflation"` // current annual inflation rate -} - -// XXX define globalstate interface? - -func initialGlobalState() *GlobalState { - return &GlobalState{ - TotalSupply: 0, - BondedShares: 0, //rational.Zero, - UnbondedShares: 0, //rational.Zero, - BondedPool: 0, - UnbondedPool: 0, - InflationLastTime: 0, - Inflation: 0, //rational.New(7, 100), - } -} - -// CandidateStatus - status of a validator-candidate -type CandidateStatus byte - -const ( - // nolint - Bonded CandidateStatus = 0x00 - Unbonded CandidateStatus = 0x01 - Revoked CandidateStatus = 0x02 -) - -// Candidate defines the total amount of bond shares and their exchange rate to -// coins. Accumulation of interest is modelled as an in increase in the -// exchange rate, and slashing as a decrease. When coins are delegated to this -// candidate, the candidate is credited with a DelegatorBond whose number of -// bond shares is based on the amount of coins delegated divided by the current -// exchange rate. Voting power can be calculated as total bonds multiplied by -// exchange rate. -type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets int64 `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities int64 `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate -} - -//nolint -type Candidates []*Candidate -type Validator struct { - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator -} -type Validators []Validator - -// Description - description fields for a candidate -type Description struct { - Moniker string `json:"moniker"` - Identity string `json:"identity"` - Website string `json:"website"` - Details string `json:"details"` -} - -// NewCandidate - initialize a new candidate -func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { - return &Candidate{ - Status: Unbonded, - PubKey: pubKey, - Owner: owner, - Assets: 0, // rational.Zero, - Liabilities: 0, // rational.Zero, - VotingPower: 0, //rational.Zero, - Description: description, - } -} - -//nolint -type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares int64 `json:"shares"` -} - -///////////////////////////////////////////////////////////j - // nolint var ( // Keys for store prefixes @@ -291,10 +160,13 @@ func getValidators(store types.KVStore, maxVal int) Validators { // loadCandidates - get the active list of all candidates TODO replace with multistore func loadCandidates(store types.KVStore) (candidates Candidates) { - iterator := store.Iterator(subspace(CandidateKeyPrefix)) //smallest to largest + //iterator := store.Iterator(subspace(CandidateKeyPrefix)) //smallest to largest + //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) //smallest to largest + iterator := store.Iterator([]byte{}, []byte(nil)) //smallest to largest for i := 0; ; i++ { if !iterator.Valid() { + //panic(fmt.Sprintf("debug i: %v\n", i)) iterator.Close() break } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index a1955862d9..1554501027 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -77,6 +77,7 @@ func TestState(t *testing.T) { resCand = loadCandidate(store, pk) assert.True(candidatesEqual(candidate, resCand)) + store.Write() // also test that the pubkey has been added to pubkey list resPks = loadCandidates(store) require.Equal(1, len(resPks)) diff --git a/x/stake/types.go b/x/stake/types.go new file mode 100644 index 0000000000..4ecd907e31 --- /dev/null +++ b/x/stake/types.go @@ -0,0 +1,132 @@ +package stake + +import ( + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +var cdc = wire.NewCodec() + +//nolint +type Params struct { + HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held + HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held + + InflationRateChange int64 `json:"inflation_rate_change"` // XXX maximum annual change in inflation rate + InflationMax int64 `json:"inflation_max"` // XXX maximum inflation rate + InflationMin int64 `json:"inflation_min"` // XXX minimum inflation rate + GoalBonded int64 `json:"goal_bonded"` // XXX Goal of percent bonded atoms + + MaxVals uint16 `json:"max_vals"` // maximum number of validators + AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` + GasEditCandidacy int64 `json:"gas_edit_candidacy"` + GasDelegate int64 `json:"gas_delegate"` + GasUnbond int64 `json:"gas_unbond"` +} + +func defaultParams() Params { + return Params{ + HoldBonded: []byte("77777777777777777777777777777777"), + HoldUnbonded: []byte("88888888888888888888888888888888"), + InflationRateChange: 13, //rational.New(13, 100), + InflationMax: 20, //rational.New(20, 100), + InflationMin: 7, //rational.New(7, 100), + GoalBonded: 67, //rational.New(67, 100), + MaxVals: 100, + AllowedBondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +// GlobalState - dynamic parameters of the current state +type GlobalState struct { + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares int64 `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares int64 `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation int64 `json:"inflation"` // current annual inflation rate +} + +// XXX define globalstate interface? + +func initialGlobalState() *GlobalState { + return &GlobalState{ + TotalSupply: 0, + BondedShares: 0, //rational.Zero, + UnbondedShares: 0, //rational.Zero, + BondedPool: 0, + UnbondedPool: 0, + InflationLastTime: 0, + Inflation: 0, //rational.New(7, 100), + } +} + +// CandidateStatus - status of a validator-candidate +type CandidateStatus byte + +const ( + // nolint + Bonded CandidateStatus = 0x00 + Unbonded CandidateStatus = 0x01 + Revoked CandidateStatus = 0x02 +) + +// Candidate defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// candidate, the candidate is credited with a DelegatorBond whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Candidate struct { + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets int64 `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities int64 `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower int64 `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate +} + +//nolint +type Candidates []*Candidate +type Validator struct { + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + VotingPower int64 `json:"voting_power"` // Voting power if considered a validator +} +type Validators []Validator + +// Description - description fields for a candidate +type Description struct { + Moniker string `json:"moniker"` + Identity string `json:"identity"` + Website string `json:"website"` + Details string `json:"details"` +} + +// NewCandidate - initialize a new candidate +func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { + return &Candidate{ + Status: Unbonded, + PubKey: pubKey, + Owner: owner, + Assets: 0, // rational.Zero, + Liabilities: 0, // rational.Zero, + VotingPower: 0, //rational.Zero, + Description: description, + } +} + +//nolint +type DelegatorBond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares int64 `json:"shares"` +} From a1c7722a8070da61dc40e4a23188a52234dd790a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 31 Jan 2018 21:56:46 -0500 Subject: [PATCH 10/54] Integrate in types and rational staking --- glide.yaml | 55 ++++++++++ x/stake/store.go | 221 ++++++++++++++++++++++------------------- x/stake/store_test.go | 217 ++++++++++++++++++++++++++++++++++++---- x/stake/test_common.go | 31 ++++-- x/stake/types.go | 215 +++++++++++++++++++++++++++++++-------- x/stake/types_test.go | 3 + 6 files changed, 568 insertions(+), 174 deletions(-) create mode 100644 glide.yaml create mode 100644 x/stake/types_test.go diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000000..34cdb13d8f --- /dev/null +++ b/glide.yaml @@ -0,0 +1,55 @@ +package: github.com/cosmos/cosmos-sdk +import: +- package: github.com/golang/protobuf + version: ^1.0.0 + subpackages: + - proto +- package: github.com/bgentry/speakeasy + version: ^0.1.0 +- package: github.com/mattn/go-isatty + version: ~0.0.3 +- package: github.com/pkg/errors + version: ^0.8.0 +- package: github.com/rigelrozanski/common +- package: github.com/tendermint/abci + version: develop + subpackages: + - server + - types +- package: github.com/tendermint/go-crypto + version: develop +- package: github.com/tendermint/go-wire + version: develop +- package: github.com/tendermint/iavl + version: develop +- package: github.com/tendermint/tmlibs + version: develop + subpackages: + - common + - db + - log + - logger + - merkle +- package: github.com/tendermint/tendermint + version: breaking/wire-sdk2 + subpackages: + - cmd/tendermint/commands + - config + - lite + - rpc/client + - types +- package: golang.org/x/crypto + subpackages: + - ripemd160 +- package: github.com/spf13/pflag + version: v1.0.0 +- package: github.com/spf13/cobra + version: v0.0.1 +- package: github.com/spf13/viper + version: ^1.0.0 +testImport: +- package: github.com/stretchr/testify + version: ^1.2.1 + subpackages: + - assert + - require diff --git a/x/stake/store.go b/x/stake/store.go index ec1bb8a20c..e738b220c2 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -1,39 +1,54 @@ package stake import ( - "encoding/binary" - crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/types" ) // nolint var ( + + // internal wire codec + cdc *wire.Codec + // Keys for store prefixes CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys ParamKey = []byte{0x02} // key for global parameters relating to staking GlobalStateKey = []byte{0x03} // key for global parameters relating to staking // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x06} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond ) +func init() { + cdc = wire.NewCodec() + cdc.RegisterInterface((*rational.Rational)(nil), nil) + cdc.RegisterConcrete(rational.Rat{}, "rat", nil) +} + // GetCandidateKey - get the key for the candidate with pubKey func GetCandidateKey(pubKey crypto.PubKey) []byte { return append(CandidateKeyPrefix, pubKey.Bytes()...) } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(pubKey crypto.PubKey, power int64) []byte { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b, uint64(power)) +func GetValidatorKey(pubKey crypto.PubKey, power rational.Rational) []byte { + b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store } +// GetValidatorUpdatesKey - get the key for the validator used in the power-store +func GetValidatorUpdatesKey(pubKey crypto.PubKey) []byte { + return append(ValidatorUpdatesKeyPrefix, pubKey.Bytes()...) // TODO does this need prefix if its in its own store +} + // GetDelegatorBondKey - get the key for delegator bond with candidate func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte { return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) @@ -41,7 +56,7 @@ func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []by // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { - res, err := cdc.MarshalBinary(&delegator) + res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) } @@ -50,7 +65,7 @@ func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { // GetDelegatorBondsKey - get the key for list of all the delegator's bonds func GetDelegatorBondsKey(delegator crypto.Address) []byte { - res, err := cdc.MarshalBinary(&delegator) + res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) } @@ -60,15 +75,12 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { //--------------------------------------------------------------------- func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { - //if pubKey.Empty() { - //return nil - //} b := store.Get(GetCandidateKey(pubKey)) if b == nil { return nil } candidate := new(Candidate) - err := cdc.UnmarshalBinary(b, candidate) + err := cdc.UnmarshalJSON(b, candidate) if err != nil { panic(err) // This error should never occur big problem if does } @@ -77,11 +89,12 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { func saveCandidate(store types.KVStore, candidate *Candidate) { - removeValidatorFromKey(store, candidate.PubKey) + // XXX should only remove validator if we know candidate is a validator + removeValidator(store, candidate.PubKey) validator := &Validator{candidate.PubKey, candidate.VotingPower} - saveValidator(store, validator) + updateValidator(store, validator) - b, err := cdc.MarshalBinary(*candidate) + b, err := cdc.MarshalJSON(*candidate) if err != nil { panic(err) } @@ -89,54 +102,67 @@ func saveCandidate(store types.KVStore, candidate *Candidate) { } func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { - removeValidatorFromKey(store, pubKey) + + // XXX should only remove validator if we know candidate is a validator + removeValidator(store, pubKey) store.Delete(GetCandidateKey(pubKey)) } //--------------------------------------------------------------------- -func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) *Validator { - b := store.Get(GetValidatorKey(pubKey, votingPower)) - if b == nil { - return nil - } - validator := new(Validator) - err := cdc.UnmarshalBinary(b, validator) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return validator -} +//func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower rational.Rational) *Validator { +//b := store.Get(GetValidatorKey(pubKey, votingPower)) +//if b == nil { +//return nil +//} +//validator := new(Validator) +//err := cdc.UnmarshalJSON(b, validator) +//if err != nil { +//panic(err) // This error should never occur big problem if does +//} +//return validator +//} -func saveValidator(store types.KVStore, validator *Validator) { - b, err := cdc.MarshalBinary(*validator) +// updateValidator - update a validator and create accumulate any changes +// in the changed validator substore +func updateValidator(store types.KVStore, validator *Validator) { + + b, err := cdc.MarshalJSON(*validator) if err != nil { panic(err) } + + // add to the validators to update list if necessary + store.Set(GetValidatorUpdatesKey(validator.PubKey), b) + + // update the list ordered by voting power store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) } -func removeValidator(store types.KVStore, pubKey crypto.PubKey, votingPower int64) { - store.Delete(GetValidatorKey(pubKey, votingPower)) -} +func removeValidator(store types.KVStore, pubKey crypto.PubKey) { -func removeValidatorFromKey(store types.KVStore, pubKey crypto.PubKey) { - // remove validator if already there, then add new validator + //add validator with zero power to the validator updates + b, err := cdc.MarshalJSON(Validator{pubKey, rational.Zero}) + if err != nil { + panic(err) + } + store.Set(GetValidatorUpdatesKey(pubKey), b) + + // now actually delete from the validator set candidate := loadCandidate(store, pubKey) if candidate != nil { - removeValidator(store, pubKey, candidate.VotingPower) + store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) } } -// Validators - get the most recent updated validator set from the -// Candidates. These bonds are already sorted by VotingPower from -// the UpdateVotingPower function which is the only function which -// is to modify the VotingPower -func getValidators(store types.KVStore, maxVal int) Validators { +// get the most recent updated validator set from the Candidates. These bonds +// are already sorted by VotingPower from the UpdateVotingPower function which +// is the only function which is to modify the VotingPower +func getValidators(store types.KVStore, maxVal int) (validators []Validator) { iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest - validators := make(Validators, maxVal) + validators = make([]Validator, maxVal) for i := 0; ; i++ { if !iterator.Valid() || i > maxVal { iterator.Close() @@ -144,7 +170,7 @@ func getValidators(store types.KVStore, maxVal int) Validators { } valBytes := iterator.Value() var val Validator - err := cdc.UnmarshalBinary(valBytes, &val) + err := cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -152,7 +178,37 @@ func getValidators(store types.KVStore, maxVal int) Validators { iterator.Next() } - return validators + return +} + +//--------------------------------------------------------------------- + +// get the most updated validators +func getValidatorUpdates(store types.KVStore) (updates []Validator) { + + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest + + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val Validator + err := cdc.UnmarshalJSON(valBytes, &val) + if err != nil { + panic(err) + } + updates = append(updates, val) + } + + iterator.Close() + return +} + +// remove all validator update entries +func clearValidatorUpdates(store types.KVStore, maxVal int) { + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + } + iterator.Close() } //--------------------------------------------------------------------- @@ -160,26 +216,20 @@ func getValidators(store types.KVStore, maxVal int) Validators { // loadCandidates - get the active list of all candidates TODO replace with multistore func loadCandidates(store types.KVStore) (candidates Candidates) { - //iterator := store.Iterator(subspace(CandidateKeyPrefix)) //smallest to largest - //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) //smallest to largest - iterator := store.Iterator([]byte{}, []byte(nil)) //smallest to largest + iterator := store.Iterator(subspace(CandidateKeyPrefix)) + //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) + //iterator := store.Iterator([]byte{}, []byte(nil)) - for i := 0; ; i++ { - if !iterator.Valid() { - //panic(fmt.Sprintf("debug i: %v\n", i)) - iterator.Close() - break - } + for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() var candidate Candidate - err := cdc.UnmarshalBinary(candidateBytes, &candidate) + err := cdc.UnmarshalJSON(candidateBytes, &candidate) if err != nil { panic(err) } - candidates[i] = &candidate - iterator.Next() + candidates = append(candidates, &candidate) } - + iterator.Close() return candidates } @@ -194,7 +244,7 @@ func loadDelegatorCandidates(store types.KVStore, return nil } - err := cdc.UnmarshalBinary(candidateBytes, &candidates) + err := cdc.UnmarshalJSON(candidateBytes, &candidates) if err != nil { panic(err) } @@ -212,7 +262,7 @@ func loadDelegatorBond(store types.KVStore, } bond := new(DelegatorBond) - err := cdc.UnmarshalBinary(delegatorBytes, bond) + err := cdc.UnmarshalJSON(delegatorBytes, bond) if err != nil { panic(err) } @@ -225,7 +275,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele if loadDelegatorBond(store, delegator, bond.PubKey) == nil { pks := loadDelegatorCandidates(store, delegator) pks = append(pks, (*bond).PubKey) - b, err := cdc.MarshalBinary(pks) + b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } @@ -233,7 +283,7 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele } // now actually save the bond - b, err := cdc.MarshalBinary(*bond) + b, err := cdc.MarshalJSON(*bond) if err != nil { panic(err) } @@ -250,7 +300,7 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat pks = append(pks[:i], pks[i+1:]...) } } - b, err := cdc.MarshalBinary(pks) + b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } @@ -261,39 +311,6 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat //updateDelegatorBonds(store, delegator) } -//func updateDelegatorBonds(store types.KVStore, -//delegator crypto.Address) { - -//var bonds []*DelegatorBond - -//prefix := GetDelegatorBondKeyPrefix(delegator) -//l := len(prefix) -//delegatorsBytes := store.List(prefix, -//append(prefix[:l-1], (prefix[l-1]+1)), loadParams(store).MaxVals) - -//for _, delegatorBytesModel := range delegatorsBytes { -//delegatorBytes := delegatorBytesModel.Value -//if delegatorBytes == nil { -//continue -//} - -//bond := new(DelegatorBond) -//err := wire.UnmarshalBinary(delegatorBytes, bond) -//if err != nil { -//panic(err) -//} -//bonds = append(bonds, bond) -//} - -//if len(bonds) == 0 { -//store.Remove(GetDelegatorBondsKey(delegator)) -//return -//} - -//b := wire.MarshalBinary(bonds) -//store.Set(GetDelegatorBondsKey(delegator), b) -//} - //_______________________________________________________________________ // load/save the global staking params @@ -303,7 +320,7 @@ func loadParams(store types.KVStore) (params Params) { return defaultParams() } - err := cdc.UnmarshalBinary(b, ¶ms) + err := cdc.UnmarshalJSON(b, ¶ms) if err != nil { panic(err) // This error should never occur big problem if does } @@ -311,7 +328,7 @@ func loadParams(store types.KVStore) (params Params) { return } func saveParams(store types.KVStore, params Params) { - b, err := cdc.MarshalBinary(params) + b, err := cdc.MarshalJSON(params) if err != nil { panic(err) } @@ -327,14 +344,14 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { return initialGlobalState() } gs = new(GlobalState) - err := cdc.UnmarshalBinary(b, gs) + err := cdc.UnmarshalJSON(b, gs) if err != nil { panic(err) // This error should never occur big problem if does } return } func saveGlobalState(store types.KVStore, gs *GlobalState) { - b, err := cdc.MarshalBinary(*gs) + b, err := cdc.MarshalJSON(*gs) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index 1554501027..b366015ec0 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -4,25 +4,201 @@ import ( "bytes" "testing" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/rational" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" ) -func initTestStore(t *testing.T) sdk.KVStore { - // Capabilities key to access the main KVStore. - db, err := dbm.NewGoLevelDB("stake", "data") - require.Nil(t, err) - stakeStoreKey := sdk.NewKVStoreKey("stake") - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() - return ms.GetKVStore(stakeStoreKey) -} +// XXX XXX XXX +// XXX revive these tests but for the store update proceedure +// XXX XXX XXX + +//func TestUpdateVotingPower(t *testing.T) { +//assert := assert.New(t) +//store := initTestStore(t) +//params := loadParams(store) +//gs := loadGlobalState(store) + +//N := 5 +//actors := newAddrs(N) +//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + +//// test a basic change in voting power +//candidates[0].Assets = rational.New(500) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + +//// test a swap in voting power +//candidates[1].Assets = rational.New(600) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) +//assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) + +//// test the max validators term +//params.MaxVals = 4 +//saveParams(store, params) +//candidates.updateVotingPower(store, gs, params) +//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) +//} + +//func TestValidatorsChanged(t *testing.T) { +//require := require.New(t) + +//v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator() +//v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator() +//v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator() +//v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator() +//v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator() + +//// test from nothing to something +//vs1 := []Validator{} +//vs2 := []Validator{v1, v2} +//changed := vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[0], changed[0]) +//testChange(t, vs2[1], changed[1]) + +//// test from something to nothing +//vs1 = []Validator{v1, v2} +//vs2 = []Validator{} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testRemove(t, vs1[0], changed[0]) +//testRemove(t, vs1[1], changed[1]) + +//// test identical +//vs1 = []Validator{v1, v2, v4} +//vs2 = []Validator{v1, v2, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Zero(len(changed)) + +//// test single value change +//vs2[2].VotingPower = rational.One +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[2], changed[0]) + +//// test multiple value change +//vs2[0].VotingPower = rational.New(11) +//vs2[2].VotingPower = rational.New(5) +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[0], changed[0]) +//testChange(t, vs2[2], changed[1]) + +//// test validator added at the beginning +//vs1 = []Validator{v2, v4} +//vs2 = []Validator{v2, v4, v1} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[0], changed[0]) + +//// test validator added in the middle +//vs1 = []Validator{v1, v2, v4} +//vs2 = []Validator{v3, v1, v4, v2} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[2], changed[0]) + +//// test validator added at the end +//vs2 = []Validator{v1, v2, v4, v5} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testChange(t, vs2[3], changed[0]) + +//// test multiple validators added +//vs2 = []Validator{v1, v2, v3, v4, v5} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testChange(t, vs2[2], changed[0]) +//testChange(t, vs2[4], changed[1]) + +//// test validator removed at the beginning +//vs2 = []Validator{v2, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[0], changed[0]) + +//// test validator removed in the middle +//vs2 = []Validator{v1, v4} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[1], changed[0]) + +//// test validator removed at the end +//vs2 = []Validator{v1, v2} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(1, len(changed)) +//testRemove(t, vs1[2], changed[0]) + +//// test multiple validators removed +//vs2 = []Validator{v1} +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(2, len(changed)) +//testRemove(t, vs1[1], changed[0]) +//testRemove(t, vs1[2], changed[1]) + +//// test many types of changes +//vs2 = []Validator{v1, v3, v4, v5} +//vs2[2].VotingPower = rational.New(11) +//changed = vs1.validatorsUpdated(vs2) +//require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 +//testRemove(t, vs1[1], changed[0]) +//testChange(t, vs2[1], changed[1]) +//testChange(t, vs2[2], changed[2]) +//testChange(t, vs2[3], changed[3]) + +//} + +//func TestUpdateValidatorSet(t *testing.T) { +//assert, require := assert.New(t), require.New(t) +//store := initTestStore(t) +//params := loadParams(store) +//gs := loadGlobalState(store) + +//N := 5 +//actors := newAddrs(N) +//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) +//for _, c := range candidates { +//saveCandidate(store, c) +//} + +//// they should all already be validators +//change, err := UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 + +//// test the max value and test again +//params.MaxVals = 4 +//saveParams(store, params) +//change, err = UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(1, len(change), "%v", change) +//testRemove(t, candidates[4].validator(), change[0]) +//candidates = loadCandidates(store) +//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) + +//// mess with the power's of the candidates and test +//candidates[0].Assets = rational.New(10) +//candidates[1].Assets = rational.New(600) +//candidates[2].Assets = rational.New(1000) +//candidates[3].Assets = rational.One +//candidates[4].Assets = rational.New(10) +//for _, c := range candidates { +//saveCandidate(store, c) +//} +//change, err = UpdateValidatorSet(store, gs, params) +//require.Nil(err) +//require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed +//candidates = loadCandidates(store) +//testChange(t, candidates[0].validator(), change[0]) +//testChange(t, candidates[1].validator(), change[1]) +//testChange(t, candidates[2].validator(), change[2]) +//testRemove(t, candidates[3].validator(), change[3]) +//testChange(t, candidates[4].validator(), change[4]) +//} func TestState(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -45,9 +221,9 @@ func TestState(t *testing.T) { candidate := &Candidate{ Owner: validator, PubKey: pk, - Assets: 9, //rational.New(9), - Liabilities: 9, // rational.New(9), - VotingPower: 0, //rational.Zero, + Assets: rational.New(9), + Liabilities: rational.New(9), + VotingPower: rational.Zero, } candidatesEqual := func(c1, c2 *Candidate) bool { @@ -72,12 +248,11 @@ func TestState(t *testing.T) { assert.True(candidatesEqual(candidate, resCand)) // modify a records, save, and retrieve - candidate.Liabilities = 99 //rational.New(99) + candidate.Liabilities = rational.New(99) saveCandidate(store, candidate) resCand = loadCandidate(store, pk) assert.True(candidatesEqual(candidate, resCand)) - store.Write() // also test that the pubkey has been added to pubkey list resPks = loadCandidates(store) require.Equal(1, len(resPks)) @@ -88,7 +263,7 @@ func TestState(t *testing.T) { bond := &DelegatorBond{ PubKey: pk, - Shares: 9, // rational.New(9), + Shares: rational.New(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -106,7 +281,7 @@ func TestState(t *testing.T) { assert.True(bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = 99 //rational.New(99) + bond.Shares = rational.New(99) saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) assert.True(bondsEqual(bond, resBond)) @@ -133,7 +308,7 @@ func TestGetValidators(t *testing.T) { store := initTestStore(t) N := 5 addrs := newAddrs(N) - candidatesFromActors(store, addrs, []int{400, 200, 0, 0, 0}) + candidatesFromActors(store, addrs, []int64{400, 200, 0, 0, 0}) validators := getValidators(store, 5) require.Equal(2, len(validators)) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index c7e1be3368..cf59ca48ab 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -3,9 +3,15 @@ package stake import ( "encoding/hex" "fmt" + "testing" + + "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/rational" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,6 +21,17 @@ func subspace(prefix []byte) (start, end []byte) { return } +func initTestStore(t *testing.T) sdk.KVStore { + // Capabilities key to access the main KVStore. + db, err := dbm.NewGoLevelDB("stake", "data") + require.Nil(t, err) + stakeStoreKey := sdk.NewKVStoreKey("stake") + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + return ms.GetKVStore(stakeStoreKey) +} + func newAddrs(n int) (addrs []crypto.Address) { for i := 0; i < n; i++ { addrs = append(addrs, []byte(fmt.Sprintf("addr%d", i))) @@ -49,15 +66,15 @@ var pks = []crypto.PubKey{ // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey // instead this is just being set the address here for testing purposes -func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int) { +func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int64) { for i := 0; i < len(addrs); i++ { c := &Candidate{ Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: int64(amts[i]), //rational.New(amts[i]), - Liabilities: int64(amts[i]), //rational.New(amts[i]), - VotingPower: int64(amts[i]), //rational.New(amts[i]), + Assets: rational.New(amts[i]), + Liabilities: rational.New(amts[i]), + VotingPower: rational.New(amts[i]), } saveCandidate(store, c) } @@ -69,9 +86,9 @@ func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: 0, //rational.Zero, - Liabilities: 0, //rational.Zero, - VotingPower: 0, //rational.Zero, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, } candidates = append(candidates, c) } diff --git a/x/stake/types.go b/x/stake/types.go index 4ecd907e31..f615f188c0 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -1,21 +1,20 @@ package stake import ( + abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" + "github.com/tendermint/tmlibs/rational" ) -var cdc = wire.NewCodec() - -//nolint +// Params defines the high level settings for staking type Params struct { HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - InflationRateChange int64 `json:"inflation_rate_change"` // XXX maximum annual change in inflation rate - InflationMax int64 `json:"inflation_max"` // XXX maximum inflation rate - InflationMin int64 `json:"inflation_min"` // XXX minimum inflation rate - GoalBonded int64 `json:"goal_bonded"` // XXX Goal of percent bonded atoms + InflationRateChange rational.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax rational.Rational `json:"inflation_max"` // maximum inflation rate + InflationMin rational.Rational `json:"inflation_min"` // minimum inflation rate + GoalBonded rational.Rational `json:"goal_bonded"` // Goal of percent bonded atoms MaxVals uint16 `json:"max_vals"` // maximum number of validators AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination @@ -31,10 +30,10 @@ func defaultParams() Params { return Params{ HoldBonded: []byte("77777777777777777777777777777777"), HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: 13, //rational.New(13, 100), - InflationMax: 20, //rational.New(20, 100), - InflationMin: 7, //rational.New(7, 100), - GoalBonded: 67, //rational.New(67, 100), + InflationRateChange: rational.New(13, 100), + InflationMax: rational.New(20, 100), + InflationMin: rational.New(7, 100), + GoalBonded: rational.New(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -44,15 +43,17 @@ func defaultParams() Params { } } +//_________________________________________________________________________ + // GlobalState - dynamic parameters of the current state type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares int64 `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares int64 `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation int64 `json:"inflation"` // current annual inflation rate + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares rational.Rational `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares rational.Rational `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation rational.Rational `json:"inflation"` // current annual inflation rate } // XXX define globalstate interface? @@ -60,15 +61,69 @@ type GlobalState struct { func initialGlobalState() *GlobalState { return &GlobalState{ TotalSupply: 0, - BondedShares: 0, //rational.Zero, - UnbondedShares: 0, //rational.Zero, + BondedShares: rational.Zero, + UnbondedShares: rational.Zero, BondedPool: 0, UnbondedPool: 0, InflationLastTime: 0, - Inflation: 0, //rational.New(7, 100), + Inflation: rational.New(7, 100), } } +// get the bond ratio of the global state +func (gs *GlobalState) bondedRatio() rational.Rational { + if gs.TotalSupply > 0 { + return rational.New(gs.BondedPool, gs.TotalSupply) + } + return rational.Zero +} + +// get the exchange rate of bonded token per issued share +func (gs *GlobalState) bondedShareExRate() rational.Rational { + if gs.BondedShares.IsZero() { + return rational.One + } + return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) +} + +// get the exchange rate of unbonded tokens held in candidates per issued share +func (gs *GlobalState) unbondedShareExRate() rational.Rational { + if gs.UnbondedShares.IsZero() { + return rational.One + } + return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) +} + +func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rational) { + issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.BondedPool += amount + gs.BondedShares = gs.BondedShares.Add(issuedShares) + return +} + +func (gs *GlobalState) removeSharesBonded(shares rational.Rational) (removedTokens int64) { + removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.BondedShares = gs.BondedShares.Sub(shares) + gs.BondedPool -= removedTokens + return +} + +func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rational) { + issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + gs.UnbondedPool += amount + return +} + +func (gs *GlobalState) removeSharesUnbonded(shares rational.Rational) (removedTokens int64) { + removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.UnbondedShares = gs.UnbondedShares.Sub(shares) + gs.UnbondedPool -= removedTokens + return +} + +//_______________________________________________________________________________________________________ + // CandidateStatus - status of a validator-candidate type CandidateStatus byte @@ -87,23 +142,15 @@ const ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets int64 `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities int64 `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets rational.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities rational.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate } -//nolint -type Candidates []*Candidate -type Validator struct { - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - VotingPower int64 `json:"voting_power"` // Voting power if considered a validator -} -type Validators []Validator - // Description - description fields for a candidate type Description struct { Moniker string `json:"moniker"` @@ -118,15 +165,95 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri Status: Unbonded, PubKey: pubKey, Owner: owner, - Assets: 0, // rational.Zero, - Liabilities: 0, // rational.Zero, - VotingPower: 0, //rational.Zero, + Assets: rational.Zero, + Liabilities: rational.Zero, + VotingPower: rational.Zero, Description: description, } } -//nolint -type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares int64 `json:"shares"` +// XXX define candidate interface? + +// get the exchange rate of global pool shares over delegator shares +func (c *Candidate) delegatorShareExRate() rational.Rational { + if c.Liabilities.IsZero() { + return rational.One + } + return c.Assets.Quo(c.Liabilities) +} + +// add tokens to a candidate +func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rational) { + + exRate := c.delegatorShareExRate() + + var receivedGlobalShares rational.Rational + if c.Status == Bonded { + receivedGlobalShares = gs.addTokensBonded(amount) + } else { + receivedGlobalShares = gs.addTokensUnbonded(amount) + } + c.Assets = c.Assets.Add(receivedGlobalShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) + return +} + +// remove shares from a candidate +func (c *Candidate) removeShares(shares rational.Rational, gs *GlobalState) (removedTokens int64) { + + globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) + + if c.Status == Bonded { + removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) + } else { + removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) + } + c.Assets = c.Assets.Sub(globalPoolSharesToRemove) + + c.Liabilities = c.Liabilities.Sub(shares) + return +} + +// Validator returns a copy of the Candidate as a Validator. +// Should only be called when the Candidate qualifies as a validator. +func (c *Candidate) validator() Validator { + return Validator{ + PubKey: c.PubKey, + VotingPower: c.VotingPower, + } +} + +// Validator is one of the top Candidates +type Validator struct { + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator +} + +// ABCIValidator - Get the validator from a bond value +func (v Validator) ABCIValidator() (*abci.Validator, error) { + pkBytes, err := cdc.MarshalBinary(v.PubKey) + if err != nil { + return nil, err + } + return &abci.Validator{ + PubKey: pkBytes, + Power: v.VotingPower.Evaluate(), + }, nil +} + +//_________________________________________________________________________ + +// Candidates - list of Candidates +type Candidates []*Candidate + +//_________________________________________________________________________ + +// DelegatorBond represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// pubKey. +type DelegatorBond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares rational.Rational `json:"shares"` } diff --git a/x/stake/types_test.go b/x/stake/types_test.go new file mode 100644 index 0000000000..ec16f32d96 --- /dev/null +++ b/x/stake/types_test.go @@ -0,0 +1,3 @@ +package stake + +// XXX test global state functions, candidate exchange rate functions etc. From 3afade80b2caab33fc4b6bc16eb35798906e2e00 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 2 Feb 2018 11:38:40 -0500 Subject: [PATCH 11/54] code cleanup --- x/stake/store.go | 3 ++- x/stake/store_test.go | 61 +++++++----------------------------------- x/stake/test_common.go | 7 ++--- 3 files changed, 16 insertions(+), 55 deletions(-) diff --git a/x/stake/store.go b/x/stake/store.go index e738b220c2..68bfe58cf3 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -29,8 +29,9 @@ var ( func init() { cdc = wire.NewCodec() - cdc.RegisterInterface((*rational.Rational)(nil), nil) + cdc.RegisterInterface((*rational.Rational)(nil), nil) // XXX make like crypto.RegisterWire() cdc.RegisterConcrete(rational.Rat{}, "rat", nil) + crypto.RegisterWire(cdc) } // GetCandidateKey - get the key for the candidate with pubKey diff --git a/x/stake/store_test.go b/x/stake/store_test.go index b366015ec0..f3f470e226 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -4,11 +4,11 @@ import ( "bytes" "testing" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/tmlibs/rational" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" ) // XXX XXX XXX @@ -105,29 +105,7 @@ import ( //// test validator added at the end //vs2 = []Validator{v1, v2, v4, v5} //changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testChange(t, vs2[3], changed[0]) - -//// test multiple validators added -//vs2 = []Validator{v1, v2, v3, v4, v5} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(2, len(changed)) -//testChange(t, vs2[2], changed[0]) -//testChange(t, vs2[4], changed[1]) - -//// test validator removed at the beginning -//vs2 = []Validator{v2, v4} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testRemove(t, vs1[0], changed[0]) - -//// test validator removed in the middle -//vs2 = []Validator{v1, v4} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testRemove(t, vs1[1], changed[0]) - -//// test validator removed at the end +//require.Equal(1, len(changed)) //testChange(t, vs2[3], changed[0]) //// test multiple validators added //vs2 = []Validator{v1, v2, v3, v4, v5} //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[2], changed[0]) //testChange(t, vs2[4], changed[1]) //// test validator removed at the beginning //vs2 = []Validator{v2, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[0], changed[0]) //// test validator removed in the middle //vs2 = []Validator{v1, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[1], changed[0]) //// test validator removed at the end //vs2 = []Validator{v1, v2} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) @@ -140,25 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many types of changes -//vs2 = []Validator{v1, v3, v4, v5} -//vs2[2].VotingPower = rational.New(11) -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 -//testRemove(t, vs1[1], changed[0]) -//testChange(t, vs2[1], changed[1]) -//testChange(t, vs2[2], changed[2]) -//testChange(t, vs2[3], changed[3]) - -//} - -//func TestUpdateValidatorSet(t *testing.T) { -//assert, require := assert.New(t), require.New(t) -//store := initTestStore(t) -//params := loadParams(store) -//gs := loadGlobalState(store) - -//N := 5 +//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = rational.New(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -204,15 +164,13 @@ func TestState(t *testing.T) { assert, require := assert.New(t), require.New(t) store := initTestStore(t) - cdc.RegisterInterface((*crypto.PubKey)(nil), nil) - cdc.RegisterConcrete(crypto.PubKeyEd25519{}, "crypto/PubKeyEd25519", nil) //delegator := crypto.Address{[]byte("addressdelegator")} //validator := crypto.Address{[]byte("addressvalidator")} delegator := []byte("addressdelegator") validator := []byte("addressvalidator") - - pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") + //pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") + pk := crypto.GenPrivKeyEd25519().PubKey() //---------------------------------------------------------------------- // Candidate checks @@ -230,9 +188,9 @@ func TestState(t *testing.T) { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && bytes.Equal(c1.Owner, c2.Owner) && - c1.Assets == c2.Assets && - c1.Liabilities == c2.Liabilities && - c1.VotingPower == c2.VotingPower && + c1.Assets.Equal(c2.Assets) && + c1.Liabilities.Equal(c2.Liabilities) && + c1.VotingPower.Equal(c2.VotingPower) && c1.Description == c2.Description } @@ -245,7 +203,8 @@ func TestState(t *testing.T) { // set and retrieve a record saveCandidate(store, candidate) resCand = loadCandidate(store, pk) - assert.True(candidatesEqual(candidate, resCand)) + assert.Equal(candidate, resCand) + assert.True(candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve candidate.Liabilities = rational.New(99) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index cf59ca48ab..a38ab3f3a1 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -23,12 +23,13 @@ func subspace(prefix []byte) (start, end []byte) { func initTestStore(t *testing.T) sdk.KVStore { // Capabilities key to access the main KVStore. - db, err := dbm.NewGoLevelDB("stake", "data") - require.Nil(t, err) + //db, err := dbm.NewGoLevelDB("stake", "data") + db := dbm.NewMemDB() stakeStoreKey := sdk.NewKVStoreKey("stake") ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) - ms.LoadLatestVersion() + err := ms.LoadLatestVersion() + require.Nil(t, err) return ms.GetKVStore(stakeStoreKey) } From d34333b3e982301b481209462b1bd71c139b8768 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 2 Feb 2018 11:51:08 -0500 Subject: [PATCH 12/54] gaia iterator store fix --- x/stake/store_test.go | 2 +- x/stake/test_common.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x/stake/store_test.go b/x/stake/store_test.go index f3f470e226..7f99c8171b 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -203,7 +203,7 @@ func TestState(t *testing.T) { // set and retrieve a record saveCandidate(store, candidate) resCand = loadCandidate(store, pk) - assert.Equal(candidate, resCand) + //assert.Equal(candidate, resCand) assert.True(candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve diff --git a/x/stake/test_common.go b/x/stake/test_common.go index a38ab3f3a1..51f60c69c5 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -16,9 +16,10 @@ import ( ) func subspace(prefix []byte) (start, end []byte) { - start, end = prefix, prefix + end = make([]byte, len(prefix)) + copy(end, prefix) end[len(end)-1]++ - return + return prefix, end } func initTestStore(t *testing.T) sdk.KVStore { From 1ed7206efe4eab7557d667ee0aa41068a5faf9c4 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Feb 2018 11:31:37 +0000 Subject: [PATCH 13/54] added rational to types --- types/rational.go | 199 +++++++++++++++++++++++++++ types/rational_test.go | 296 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 495 insertions(+) create mode 100644 types/rational.go create mode 100644 types/rational_test.go diff --git a/types/rational.go b/types/rational.go new file mode 100644 index 0000000000..65020d510c --- /dev/null +++ b/types/rational.go @@ -0,0 +1,199 @@ +package types + +import ( + "errors" + "fmt" + "math/big" + "strconv" + "strings" + + wire "github.com/tendermint/go-wire" +) + +var cdc *wire.Codec + +func init() { + cdc = wire.NewCodec() + cdc.RegisterInterface((*Rational)(nil), nil) + cdc.RegisterConcrete(Rat{}, "rat", nil) +} + +// Rat - extend big.Rat +type Rat struct { + *big.Rat `json:"rat"` +} + +// Rational - big Rat with additional functionality +type Rational interface { + GetRat() *big.Rat + Num() int64 + Denom() int64 + GT(Rational) bool + LT(Rational) bool + Equal(Rational) bool + IsZero() bool + Inv() Rational + Mul(Rational) Rational + Quo(Rational) Rational + Add(Rational) Rational + Sub(Rational) Rational + Round(int64) Rational + Evaluate() int64 +} + +var _ Rational = Rat{} // enforce at compile time + +// nolint - common values +var ( + Zero = Rat{big.NewRat(0, 1)} + One = Rat{big.NewRat(1, 1)} +) + +// New - create a new Rat from integers +func New(Numerator int64, Denominator ...int64) Rat { + switch len(Denominator) { + case 0: + return Rat{big.NewRat(Numerator, 1)} + case 1: + return Rat{big.NewRat(Numerator, Denominator[0])} + default: + panic("improper use of New, can only have one denominator") + } +} + +//NewFromDecimal - create a rational from decimal string or integer string +func NewFromDecimal(decimalStr string) (f Rat, err error) { + + // first extract any negative symbol + neg := false + if string(decimalStr[0]) == "-" { + neg = true + decimalStr = decimalStr[1:] + } + + str := strings.Split(decimalStr, ".") + + var numStr string + var denom int64 = 1 + switch len(str) { + case 1: + if len(str[0]) == 0 { + return f, errors.New("not a decimal string") + } + numStr = str[0] + case 2: + if len(str[0]) == 0 || len(str[1]) == 0 { + return f, errors.New("not a decimal string") + } + numStr = str[0] + str[1] + len := int64(len(str[1])) + denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64() + default: + return f, errors.New("not a decimal string") + } + + num, err := strconv.Atoi(numStr) + if err != nil { + return f, err + } + + if neg { + num *= -1 + } + + return Rat{big.NewRat(int64(num), denom)}, nil +} + +//nolint +func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat +func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator +func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator +func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero +func (r Rat) Equal(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal +func (r Rat) GT(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than +func (r Rat) LT(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than +func (r Rat) Inv() Rational { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse +func (r Rat) Mul(r2 Rational) Rational { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication +func (r Rat) Quo(r2 Rational) Rational { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient +func (r Rat) Add(r2 Rational) Rational { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition +func (r Rat) Sub(r2 Rational) Rational { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction + +var zero = big.NewInt(0) +var one = big.NewInt(1) +var two = big.NewInt(2) +var five = big.NewInt(5) +var nFive = big.NewInt(-5) +var ten = big.NewInt(10) + +// EvaluateBig - evaluate the rational using bankers rounding +func (r Rat) EvaluateBig() *big.Int { + + num := r.Rat.Num() + denom := r.Rat.Denom() + + d, rem := new(big.Int), new(big.Int) + d.QuoRem(num, denom, rem) + if rem.Cmp(zero) == 0 { // is the remainder zero + return d + } + + // evaluate the remainder using bankers rounding + tenNum := new(big.Int).Mul(num, ten) + tenD := new(big.Int).Mul(d, ten) + remainderDigit := new(big.Int).Sub(new(big.Int).Quo(tenNum, denom), tenD) // get the first remainder digit + isFinalDigit := (new(big.Int).Rem(tenNum, denom).Cmp(zero) == 0) // is this the final digit in the remainder? + + switch { + case isFinalDigit && (remainderDigit.Cmp(five) == 0 || remainderDigit.Cmp(nFive) == 0): + dRem2 := new(big.Int).Rem(d, two) + return new(big.Int).Add(d, dRem2) // always rounds to the even number + case remainderDigit.Cmp(five) != -1: //remainderDigit >= 5: + d.Add(d, one) + case remainderDigit.Cmp(nFive) != 1: //remainderDigit <= -5: + d.Sub(d, one) + } + return d +} + +// Evaluate - evaluate the rational using bankers rounding +func (r Rat) Evaluate() int64 { + return r.EvaluateBig().Int64() +} + +// Round - round Rat with the provided precisionFactor +func (r Rat) Round(precisionFactor int64) Rational { + rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))} + return Rat{big.NewRat(rTen.Evaluate(), precisionFactor)} +} + +//___________________________________________________________________________________ + +//TODO there has got to be a better way using native MarshalText and UnmarshalText + +// RatMarshal - Marshable Rat Struct +type RatMarshal struct { + Numerator int64 `json:"numerator"` + Denominator int64 `json:"denominator"` +} + +// MarshalJSON - custom implementation of JSON Marshal +func (r Rat) MarshalJSON() ([]byte, error) { + return cdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) +} + +// UnmarshalJSON - custom implementation of JSON Unmarshal +func (r *Rat) UnmarshalJSON(data []byte) (err error) { + defer func() { + if rcv := recover(); rcv != nil { + err = fmt.Errorf("Panic during UnmarshalJSON: %v", rcv) + } + }() + + ratMar := new(RatMarshal) + if err := cdc.UnmarshalJSON(data, ratMar); err != nil { + return err + } + r.Rat = big.NewRat(ratMar.Numerator, ratMar.Denominator) + + return nil +} diff --git a/types/rational_test.go b/types/rational_test.go new file mode 100644 index 0000000000..874036c275 --- /dev/null +++ b/types/rational_test.go @@ -0,0 +1,296 @@ +package types + +import ( + "encoding/json" + "math/big" + "testing" + + asrt "github.com/stretchr/testify/assert" + rqr "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + assert := asrt.New(t) + + assert.Equal(New(1), New(1, 1)) + assert.Equal(New(100), New(100, 1)) + assert.Equal(New(-1), New(-1, 1)) + assert.Equal(New(-100), New(-100, 1)) + assert.Equal(New(0), New(0, 1)) + + // do not allow for more than 2 variables + assert.Panics(func() { New(1, 1, 1) }) +} + +func TestNewFromDecimal(t *testing.T) { + assert := asrt.New(t) + + tests := []struct { + decimalStr string + expErr bool + exp Rat + }{ + {"0", false, New(0)}, + {"1", false, New(1)}, + {"1.1", false, New(11, 10)}, + {"0.75", false, New(3, 4)}, + {"0.8", false, New(4, 5)}, + {"0.11111", false, New(11111, 100000)}, + {".", true, Rat{}}, + {".0", true, Rat{}}, + {"1.", true, Rat{}}, + {"foobar", true, Rat{}}, + {"0.foobar", true, Rat{}}, + {"0.foobar.", true, Rat{}}, + } + + for _, tc := range tests { + + res, err := NewFromDecimal(tc.decimalStr) + if tc.expErr { + assert.NotNil(err, tc.decimalStr) + } else { + assert.Nil(err) + assert.True(res.Equal(tc.exp)) + } + + // negative tc + res, err = NewFromDecimal("-" + tc.decimalStr) + if tc.expErr { + assert.NotNil(err, tc.decimalStr) + } else { + assert.Nil(err) + assert.True(res.Equal(tc.exp.Mul(New(-1)))) + } + } +} + +func TestEqualities(t *testing.T) { + assert := asrt.New(t) + + tests := []struct { + r1, r2 Rat + gt, lt, eq bool + }{ + {New(0), New(0), false, false, true}, + {New(0, 100), New(0, 10000), false, false, true}, + {New(100), New(100), false, false, true}, + {New(-100), New(-100), false, false, true}, + {New(-100, -1), New(100), false, false, true}, + {New(-1, 1), New(1, -1), false, false, true}, + {New(1, -1), New(-1, 1), false, false, true}, + {New(3, 7), New(3, 7), false, false, true}, + + {New(0), New(3, 7), false, true, false}, + {New(0), New(100), false, true, false}, + {New(-1), New(3, 7), false, true, false}, + {New(-1), New(100), false, true, false}, + {New(1, 7), New(100), false, true, false}, + {New(1, 7), New(3, 7), false, true, false}, + {New(-3, 7), New(-1, 7), false, true, false}, + + {New(3, 7), New(0), true, false, false}, + {New(100), New(0), true, false, false}, + {New(3, 7), New(-1), true, false, false}, + {New(100), New(-1), true, false, false}, + {New(100), New(1, 7), true, false, false}, + {New(3, 7), New(1, 7), true, false, false}, + {New(-1, 7), New(-3, 7), true, false, false}, + } + + for _, tc := range tests { + assert.Equal(tc.gt, tc.r1.GT(tc.r2)) + assert.Equal(tc.lt, tc.r1.LT(tc.r2)) + assert.Equal(tc.eq, tc.r1.Equal(tc.r2)) + } + +} + +func TestArithmatic(t *testing.T) { + assert := asrt.New(t) + + tests := []struct { + r1, r2 Rat + resMul, resDiv, resAdd, resSub Rat + }{ + // r1 r2 MUL DIV ADD SUB + {New(0), New(0), New(0), New(0), New(0), New(0)}, + {New(1), New(0), New(0), New(0), New(1), New(1)}, + {New(0), New(1), New(0), New(0), New(1), New(-1)}, + {New(0), New(-1), New(0), New(0), New(-1), New(1)}, + {New(-1), New(0), New(0), New(0), New(-1), New(-1)}, + + {New(1), New(1), New(1), New(1), New(2), New(0)}, + {New(-1), New(-1), New(1), New(1), New(-2), New(0)}, + {New(1), New(-1), New(-1), New(-1), New(0), New(2)}, + {New(-1), New(1), New(-1), New(-1), New(0), New(-2)}, + + {New(3), New(7), New(21), New(3, 7), New(10), New(-4)}, + {New(2), New(4), New(8), New(1, 2), New(6), New(-2)}, + {New(100), New(100), New(10000), New(1), New(200), New(0)}, + + {New(3, 2), New(3, 2), New(9, 4), New(1), New(3), New(0)}, + {New(3, 7), New(7, 3), New(1), New(9, 49), New(58, 21), New(-40, 21)}, + {New(1, 21), New(11, 5), New(11, 105), New(5, 231), New(236, 105), New(-226, 105)}, + {New(-21), New(3, 7), New(-9), New(-49), New(-144, 7), New(-150, 7)}, + {New(100), New(1, 7), New(100, 7), New(700), New(701, 7), New(699, 7)}, + } + + for _, tc := range tests { + assert.True(tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + + if tc.r2.Num() == 0 { // panic for divide by zero + assert.Panics(func() { tc.r1.Quo(tc.r2) }) + } else { + assert.True(tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + } + } +} + +func TestEvaluate(t *testing.T) { + assert := asrt.New(t) + + tests := []struct { + r1 Rat + res int64 + }{ + {New(0), 0}, + {New(1), 1}, + {New(1, 4), 0}, + {New(1, 2), 0}, + {New(3, 4), 1}, + {New(5, 6), 1}, + {New(3, 2), 2}, + {New(5, 2), 2}, + {New(6, 11), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even + {New(17, 11), 2}, // 1.545 + {New(5, 11), 0}, + {New(16, 11), 1}, + {New(113, 12), 9}, + } + + for _, tc := range tests { + assert.Equal(tc.res, tc.r1.Evaluate(), "%v", tc.r1) + assert.Equal(tc.res*-1, tc.r1.Mul(New(-1)).Evaluate(), "%v", tc.r1.Mul(New(-1))) + } +} + +func TestRound(t *testing.T) { + assert, require := asrt.New(t), rqr.New(t) + + many3 := "333333333333333333333333333333333333333333333" + many7 := "777777777777777777777777777777777777777777777" + big3, worked := new(big.Int).SetString(many3, 10) + require.True(worked) + big7, worked := new(big.Int).SetString(many7, 10) + require.True(worked) + + tests := []struct { + r1, res Rat + precFactor int64 + }{ + {New(333, 777), New(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, New(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, New(4285714286, 10000000000), 10000000000}, + {New(1, 2), New(1, 2), 1000}, + } + + for _, tc := range tests { + assert.Equal(tc.res, tc.r1.Round(tc.precFactor), "%v", tc.r1) + negR1, negRes := tc.r1.Mul(New(-1)), tc.res.Mul(New(-1)) + assert.Equal(negRes, negR1.Round(tc.precFactor), "%v", negR1) + } +} + +func TestZeroSerializationJSON(t *testing.T) { + assert := asrt.New(t) + + var r Rat + err := json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":1}"), &r) + assert.Nil(err) + err = json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":0}"), &r) + assert.NotNil(err) + err = json.Unmarshal([]byte("{\"numerator\":1,\"denominator\":0}"), &r) + assert.NotNil(err) + err = json.Unmarshal([]byte("{}"), &r) + assert.NotNil(err) +} + +func TestSerializationJSON(t *testing.T) { + assert, require := asrt.New(t), rqr.New(t) + + r := New(1, 3) + + rMarshal, err := json.Marshal(r) + require.Nil(err) + + var rUnmarshal Rat + err = json.Unmarshal(rMarshal, &rUnmarshal) + require.Nil(err) + + assert.True(r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) +} + +func TestSerializationGoWire(t *testing.T) { + assert, require := asrt.New(t), rqr.New(t) + + r := New(1, 3) + + rMarshal, err := cdc.MarshalJSON(r) + require.Nil(err) + + var rUnmarshal Rat + err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(err) + + assert.True(r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) +} + +type testEmbedStruct struct { + Field1 string `json:"f1"` + Field2 int `json:"f2"` + Field3 Rat `json:"f3"` +} + +func TestEmbeddedStructSerializationGoWire(t *testing.T) { + assert, require := asrt.New(t), rqr.New(t) + + r := testEmbedStruct{"foo", 10, New(1, 3)} + + rMarshal, err := cdc.MarshalJSON(r) + require.Nil(err) + + var rUnmarshal testEmbedStruct + err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(err) + + assert.Equal(r.Field1, rUnmarshal.Field1) + assert.Equal(r.Field2, rUnmarshal.Field2) + assert.True(r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) + +} + +type testEmbedInterface struct { + Field1 string `json:"f1"` + Field2 int `json:"f2"` + Field3 Rational `json:"f3"` +} + +func TestEmbeddedInterfaceSerializationGoWire(t *testing.T) { + assert, require := asrt.New(t), rqr.New(t) + + r := testEmbedInterface{"foo", 10, New(1, 3)} + + rMarshal, err := cdc.MarshalJSON(r) + require.Nil(err) + + var rUnmarshal testEmbedInterface + err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(err) + + assert.Equal(r.Field1, rUnmarshal.Field1) + assert.Equal(r.Field2, rUnmarshal.Field2) + assert.True(r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) +} From 514470b4d6b2cb8dbcb690fc406b15eeffbc63c9 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Feb 2018 15:55:53 +0000 Subject: [PATCH 14/54] working porting rational --- types/rational.go | 21 ++++---- x/stake/store.go | 11 ++--- x/stake/store_test.go | 46 +++++++++--------- x/stake/test_common.go | 13 +++-- x/stake/types.go | 106 ++++++++++++++++++++--------------------- 5 files changed, 100 insertions(+), 97 deletions(-) diff --git a/types/rational.go b/types/rational.go index 65020d510c..c85fcdef75 100644 --- a/types/rational.go +++ b/types/rational.go @@ -10,14 +10,19 @@ import ( wire "github.com/tendermint/go-wire" ) -var cdc *wire.Codec - -func init() { - cdc = wire.NewCodec() +// add rational codec elements to provided codec +func RationalCodec(cdc *wire.Codec) *wire.Codec { cdc.RegisterInterface((*Rational)(nil), nil) cdc.RegisterConcrete(Rat{}, "rat", nil) + return cdc } +// "that's one big rat!" +// ______ +// / / /\ \____oo +// __ /___...._____ _\o +// __| |_ |_ + // Rat - extend big.Rat type Rat struct { *big.Rat `json:"rat"` @@ -45,12 +50,12 @@ var _ Rational = Rat{} // enforce at compile time // nolint - common values var ( - Zero = Rat{big.NewRat(0, 1)} - One = Rat{big.NewRat(1, 1)} + ZeroRat = Rat{big.NewRat(0, 1)} + OneRat = Rat{big.NewRat(1, 1)} ) // New - create a new Rat from integers -func New(Numerator int64, Denominator ...int64) Rat { +func NewRational(Numerator int64, Denominator ...int64) Rat { switch len(Denominator) { case 0: return Rat{big.NewRat(Numerator, 1)} @@ -62,7 +67,7 @@ func New(Numerator int64, Denominator ...int64) Rat { } //NewFromDecimal - create a rational from decimal string or integer string -func NewFromDecimal(decimalStr string) (f Rat, err error) { +func NewRationlFromDecimal(decimalStr string) (f Rat, err error) { // first extract any negative symbol neg := false diff --git a/x/stake/store.go b/x/stake/store.go index 68bfe58cf3..54073875cd 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -3,7 +3,6 @@ package stake import ( crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" - "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/types" ) @@ -29,8 +28,8 @@ var ( func init() { cdc = wire.NewCodec() - cdc.RegisterInterface((*rational.Rational)(nil), nil) // XXX make like crypto.RegisterWire() - cdc.RegisterConcrete(rational.Rat{}, "rat", nil) + cdc.RegisterInterface((*types.Rational)(nil), nil) // XXX make like crypto.RegisterWire() + cdc.RegisterConcrete(types.Rat{}, "rat", nil) crypto.RegisterWire(cdc) } @@ -40,7 +39,7 @@ func GetCandidateKey(pubKey crypto.PubKey) []byte { } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(pubKey crypto.PubKey, power rational.Rational) []byte { +func GetValidatorKey(pubKey crypto.PubKey, power types.Rational) []byte { b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store } @@ -111,7 +110,7 @@ func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { //--------------------------------------------------------------------- -//func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower rational.Rational) *Validator { +//func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower types.Rational) *Validator { //b := store.Get(GetValidatorKey(pubKey, votingPower)) //if b == nil { //return nil @@ -143,7 +142,7 @@ func updateValidator(store types.KVStore, validator *Validator) { func removeValidator(store types.KVStore, pubKey crypto.PubKey) { //add validator with zero power to the validator updates - b, err := cdc.MarshalJSON(Validator{pubKey, rational.Zero}) + b, err := cdc.MarshalJSON(Validator{pubKey, types.Zero}) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index 7f99c8171b..3956b2379b 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -4,8 +4,8 @@ import ( "bytes" "testing" + "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,12 +26,12 @@ import ( //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //// test a basic change in voting power -//candidates[0].Assets = rational.New(500) +//candidates[0].Assets = types.New(500) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //// test a swap in voting power -//candidates[1].Assets = rational.New(600) +//candidates[1].Assets = types.New(600) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) @@ -46,11 +46,11 @@ import ( //func TestValidatorsChanged(t *testing.T) { //require := require.New(t) -//v1 := (&Candidate{PubKey: pks[0], VotingPower: rational.New(10)}).validator() -//v2 := (&Candidate{PubKey: pks[1], VotingPower: rational.New(10)}).validator() -//v3 := (&Candidate{PubKey: pks[2], VotingPower: rational.New(10)}).validator() -//v4 := (&Candidate{PubKey: pks[3], VotingPower: rational.New(10)}).validator() -//v5 := (&Candidate{PubKey: pks[4], VotingPower: rational.New(10)}).validator() +//v1 := (&Candidate{PubKey: pks[0], VotingPower: types.New(10)}).validator() +//v2 := (&Candidate{PubKey: pks[1], VotingPower: types.New(10)}).validator() +//v3 := (&Candidate{PubKey: pks[2], VotingPower: types.New(10)}).validator() +//v4 := (&Candidate{PubKey: pks[3], VotingPower: types.New(10)}).validator() +//v5 := (&Candidate{PubKey: pks[4], VotingPower: types.New(10)}).validator() //// test from nothing to something //vs1 := []Validator{} @@ -75,14 +75,14 @@ import ( //require.Zero(len(changed)) //// test single value change -//vs2[2].VotingPower = rational.One +//vs2[2].VotingPower = types.One //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testChange(t, vs2[2], changed[0]) //// test multiple value change -//vs2[0].VotingPower = rational.New(11) -//vs2[2].VotingPower = rational.New(5) +//vs2[0].VotingPower = types.New(11) +//vs2[2].VotingPower = types.New(5) //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[0], changed[0]) @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = rational.New(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 +//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = types.New(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -141,11 +141,11 @@ import ( //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) //// mess with the power's of the candidates and test -//candidates[0].Assets = rational.New(10) -//candidates[1].Assets = rational.New(600) -//candidates[2].Assets = rational.New(1000) -//candidates[3].Assets = rational.One -//candidates[4].Assets = rational.New(10) +//candidates[0].Assets = types.New(10) +//candidates[1].Assets = types.New(600) +//candidates[2].Assets = types.New(1000) +//candidates[3].Assets = types.One +//candidates[4].Assets = types.New(10) //for _, c := range candidates { //saveCandidate(store, c) //} @@ -179,9 +179,9 @@ func TestState(t *testing.T) { candidate := &Candidate{ Owner: validator, PubKey: pk, - Assets: rational.New(9), - Liabilities: rational.New(9), - VotingPower: rational.Zero, + Assets: types.New(9), + Liabilities: types.New(9), + VotingPower: types.Zero, } candidatesEqual := func(c1, c2 *Candidate) bool { @@ -207,7 +207,7 @@ func TestState(t *testing.T) { assert.True(candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve - candidate.Liabilities = rational.New(99) + candidate.Liabilities = types.New(99) saveCandidate(store, candidate) resCand = loadCandidate(store, pk) assert.True(candidatesEqual(candidate, resCand)) @@ -222,7 +222,7 @@ func TestState(t *testing.T) { bond := &DelegatorBond{ PubKey: pk, - Shares: rational.New(9), + Shares: types.New(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -240,7 +240,7 @@ func TestState(t *testing.T) { assert.True(bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = rational.New(99) + bond.Shares = types.New(99) saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) assert.True(bondsEqual(bond, resBond)) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 51f60c69c5..7628fbd1bd 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -9,7 +9,6 @@ import ( crypto "github.com/tendermint/go-crypto" dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/rational" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -74,9 +73,9 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6 Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: rational.New(amts[i]), - Liabilities: rational.New(amts[i]), - VotingPower: rational.New(amts[i]), + Assets: sdk.New(amts[i]), + Liabilities: sdk.New(amts[i]), + VotingPower: sdk.New(amts[i]), } saveCandidate(store, c) } @@ -88,9 +87,9 @@ func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: rational.Zero, - Liabilities: rational.Zero, - VotingPower: rational.Zero, + Assets: sdk.Zero, + Liabilities: sdk.Zero, + VotingPower: sdk.Zero, } candidates = append(candidates, c) } diff --git a/x/stake/types.go b/x/stake/types.go index f615f188c0..2d2632674a 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -1,9 +1,9 @@ package stake import ( + sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" ) // Params defines the high level settings for staking @@ -11,10 +11,10 @@ type Params struct { HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - InflationRateChange rational.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax rational.Rational `json:"inflation_max"` // maximum inflation rate - InflationMin rational.Rational `json:"inflation_min"` // minimum inflation rate - GoalBonded rational.Rational `json:"goal_bonded"` // Goal of percent bonded atoms + InflationRateChange sdk.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Rational `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Rational `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Rational `json:"goal_bonded"` // Goal of percent bonded atoms MaxVals uint16 `json:"max_vals"` // maximum number of validators AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination @@ -30,10 +30,10 @@ func defaultParams() Params { return Params{ HoldBonded: []byte("77777777777777777777777777777777"), HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: rational.New(13, 100), - InflationMax: rational.New(20, 100), - InflationMin: rational.New(7, 100), - GoalBonded: rational.New(67, 100), + InflationRateChange: sdk.New(13, 100), + InflationMax: sdk.New(20, 100), + InflationMin: sdk.New(7, 100), + GoalBonded: sdk.New(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -47,13 +47,13 @@ func defaultParams() Params { // GlobalState - dynamic parameters of the current state type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares rational.Rational `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares rational.Rational `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation rational.Rational `json:"inflation"` // current annual inflation rate + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares sdk.Rational `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares sdk.Rational `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation sdk.Rational `json:"inflation"` // current annual inflation rate } // XXX define globalstate interface? @@ -61,61 +61,61 @@ type GlobalState struct { func initialGlobalState() *GlobalState { return &GlobalState{ TotalSupply: 0, - BondedShares: rational.Zero, - UnbondedShares: rational.Zero, + BondedShares: sdk.Zero, + UnbondedShares: sdk.Zero, BondedPool: 0, UnbondedPool: 0, InflationLastTime: 0, - Inflation: rational.New(7, 100), + Inflation: sdk.New(7, 100), } } // get the bond ratio of the global state -func (gs *GlobalState) bondedRatio() rational.Rational { +func (gs *GlobalState) bondedRatio() sdk.Rational { if gs.TotalSupply > 0 { - return rational.New(gs.BondedPool, gs.TotalSupply) + return sdk.New(gs.BondedPool, gs.TotalSupply) } - return rational.Zero + return sdk.Zero } // get the exchange rate of bonded token per issued share -func (gs *GlobalState) bondedShareExRate() rational.Rational { +func (gs *GlobalState) bondedShareExRate() sdk.Rational { if gs.BondedShares.IsZero() { - return rational.One + return sdk.One } - return gs.BondedShares.Inv().Mul(rational.New(gs.BondedPool)) + return gs.BondedShares.Inv().Mul(sdk.New(gs.BondedPool)) } // get the exchange rate of unbonded tokens held in candidates per issued share -func (gs *GlobalState) unbondedShareExRate() rational.Rational { +func (gs *GlobalState) unbondedShareExRate() sdk.Rational { if gs.UnbondedShares.IsZero() { - return rational.One + return sdk.One } - return gs.UnbondedShares.Inv().Mul(rational.New(gs.UnbondedPool)) + return gs.UnbondedShares.Inv().Mul(sdk.New(gs.UnbondedPool)) } -func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares rational.Rational) { - issuedShares = gs.bondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens +func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) { + issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.New(amount)) // (tokens/shares)^-1 * tokens gs.BondedPool += amount gs.BondedShares = gs.BondedShares.Add(issuedShares) return } -func (gs *GlobalState) removeSharesBonded(shares rational.Rational) (removedTokens int64) { +func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens int64) { removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.BondedShares = gs.BondedShares.Sub(shares) gs.BondedPool -= removedTokens return } -func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares rational.Rational) { - issuedShares = gs.unbondedShareExRate().Inv().Mul(rational.New(amount)) // (tokens/shares)^-1 * tokens +func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rational) { + issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.New(amount)) // (tokens/shares)^-1 * tokens gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) gs.UnbondedPool += amount return } -func (gs *GlobalState) removeSharesUnbonded(shares rational.Rational) (removedTokens int64) { +func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rational) (removedTokens int64) { removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.UnbondedShares = gs.UnbondedShares.Sub(shares) gs.UnbondedPool -= removedTokens @@ -142,13 +142,13 @@ const ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets rational.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities rational.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate + Status CandidateStatus `json:"status"` // Bonded status + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Assets sdk.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities sdk.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator + Description Description `json:"description"` // Description terms for the candidate } // Description - description fields for a candidate @@ -165,9 +165,9 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri Status: Unbonded, PubKey: pubKey, Owner: owner, - Assets: rational.Zero, - Liabilities: rational.Zero, - VotingPower: rational.Zero, + Assets: sdk.Zero, + Liabilities: sdk.Zero, + VotingPower: sdk.Zero, Description: description, } } @@ -175,19 +175,19 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri // XXX define candidate interface? // get the exchange rate of global pool shares over delegator shares -func (c *Candidate) delegatorShareExRate() rational.Rational { +func (c *Candidate) delegatorShareExRate() sdk.Rational { if c.Liabilities.IsZero() { - return rational.One + return sdk.One } return c.Assets.Quo(c.Liabilities) } // add tokens to a candidate -func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares rational.Rational) { +func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares sdk.Rational) { exRate := c.delegatorShareExRate() - var receivedGlobalShares rational.Rational + var receivedGlobalShares sdk.Rational if c.Status == Bonded { receivedGlobalShares = gs.addTokensBonded(amount) } else { @@ -201,7 +201,7 @@ func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorSha } // remove shares from a candidate -func (c *Candidate) removeShares(shares rational.Rational, gs *GlobalState) (removedTokens int64) { +func (c *Candidate) removeShares(shares sdk.Rational, gs *GlobalState) (removedTokens int64) { globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) @@ -227,8 +227,8 @@ func (c *Candidate) validator() Validator { // Validator is one of the top Candidates type Validator struct { - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - VotingPower rational.Rational `json:"voting_power"` // Voting power if considered a validator + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator } // ABCIValidator - Get the validator from a bond value @@ -254,6 +254,6 @@ type Candidates []*Candidate // owned by one delegator, and is associated with the voting power of one // pubKey. type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares rational.Rational `json:"shares"` + PubKey crypto.PubKey `json:"pub_key"` + Shares sdk.Rational `json:"shares"` } From c8c85dfbc8b9172d20331b938f33403cac9b8edb Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 23 Feb 2018 13:13:55 +0000 Subject: [PATCH 15/54] ported over rational --- types/rational.go | 12 +- types/rational_test.go | 270 +++++++++++++++++++---------------------- x/stake/store.go | 2 +- x/stake/store_test.go | 78 ++++++------ x/stake/test_common.go | 12 +- x/stake/types.go | 38 +++--- 6 files changed, 194 insertions(+), 218 deletions(-) diff --git a/types/rational.go b/types/rational.go index c85fcdef75..556fd0eef4 100644 --- a/types/rational.go +++ b/types/rational.go @@ -10,8 +10,10 @@ import ( wire "github.com/tendermint/go-wire" ) +var ratCdc = RegisterWire(wire.NewCodec()) + // add rational codec elements to provided codec -func RationalCodec(cdc *wire.Codec) *wire.Codec { +func RegisterWire(cdc *wire.Codec) *wire.Codec { cdc.RegisterInterface((*Rational)(nil), nil) cdc.RegisterConcrete(Rat{}, "rat", nil) return cdc @@ -55,7 +57,7 @@ var ( ) // New - create a new Rat from integers -func NewRational(Numerator int64, Denominator ...int64) Rat { +func NewRat(Numerator int64, Denominator ...int64) Rat { switch len(Denominator) { case 0: return Rat{big.NewRat(Numerator, 1)} @@ -67,7 +69,7 @@ func NewRational(Numerator int64, Denominator ...int64) Rat { } //NewFromDecimal - create a rational from decimal string or integer string -func NewRationlFromDecimal(decimalStr string) (f Rat, err error) { +func NewRatFromDecimal(decimalStr string) (f Rat, err error) { // first extract any negative symbol neg := false @@ -183,7 +185,7 @@ type RatMarshal struct { // MarshalJSON - custom implementation of JSON Marshal func (r Rat) MarshalJSON() ([]byte, error) { - return cdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) + return ratCdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) } // UnmarshalJSON - custom implementation of JSON Unmarshal @@ -195,7 +197,7 @@ func (r *Rat) UnmarshalJSON(data []byte) (err error) { }() ratMar := new(RatMarshal) - if err := cdc.UnmarshalJSON(data, ratMar); err != nil { + if err := ratCdc.UnmarshalJSON(data, ratMar); err != nil { return err } r.Rat = big.NewRat(ratMar.Numerator, ratMar.Denominator) diff --git a/types/rational_test.go b/types/rational_test.go index 874036c275..9376a8e463 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -5,37 +5,33 @@ import ( "math/big" "testing" - asrt "github.com/stretchr/testify/assert" - rqr "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNew(t *testing.T) { - assert := asrt.New(t) - - assert.Equal(New(1), New(1, 1)) - assert.Equal(New(100), New(100, 1)) - assert.Equal(New(-1), New(-1, 1)) - assert.Equal(New(-100), New(-100, 1)) - assert.Equal(New(0), New(0, 1)) + assert.Equal(t, NewRat(1), NewRat(1, 1)) + assert.Equal(t, NewRat(100), NewRat(100, 1)) + assert.Equal(t, NewRat(-1), NewRat(-1, 1)) + assert.Equal(t, NewRat(-100), NewRat(-100, 1)) + assert.Equal(t, NewRat(0), NewRat(0, 1)) // do not allow for more than 2 variables - assert.Panics(func() { New(1, 1, 1) }) + assert.Panics(t, func() { NewRat(1, 1, 1) }) } func TestNewFromDecimal(t *testing.T) { - assert := asrt.New(t) - tests := []struct { decimalStr string expErr bool exp Rat }{ - {"0", false, New(0)}, - {"1", false, New(1)}, - {"1.1", false, New(11, 10)}, - {"0.75", false, New(3, 4)}, - {"0.8", false, New(4, 5)}, - {"0.11111", false, New(11111, 100000)}, + {"0", false, NewRat(0)}, + {"1", false, NewRat(1)}, + {"1.1", false, NewRat(11, 10)}, + {"0.75", false, NewRat(3, 4)}, + {"0.8", false, NewRat(4, 5)}, + {"0.11111", false, NewRat(11111, 100000)}, {".", true, Rat{}}, {".0", true, Rat{}}, {"1.", true, Rat{}}, @@ -46,206 +42,192 @@ func TestNewFromDecimal(t *testing.T) { for _, tc := range tests { - res, err := NewFromDecimal(tc.decimalStr) + res, err := NewRatFromDecimal(tc.decimalStr) if tc.expErr { - assert.NotNil(err, tc.decimalStr) + assert.NotNil(t, err, tc.decimalStr) } else { - assert.Nil(err) - assert.True(res.Equal(tc.exp)) + assert.Nil(t, err) + assert.True(t, res.Equal(tc.exp)) } // negative tc - res, err = NewFromDecimal("-" + tc.decimalStr) + res, err = NewRatFromDecimal("-" + tc.decimalStr) if tc.expErr { - assert.NotNil(err, tc.decimalStr) + assert.NotNil(t, err, tc.decimalStr) } else { - assert.Nil(err) - assert.True(res.Equal(tc.exp.Mul(New(-1)))) + assert.Nil(t, err) + assert.True(t, res.Equal(tc.exp.Mul(NewRat(-1)))) } } } func TestEqualities(t *testing.T) { - assert := asrt.New(t) - tests := []struct { r1, r2 Rat gt, lt, eq bool }{ - {New(0), New(0), false, false, true}, - {New(0, 100), New(0, 10000), false, false, true}, - {New(100), New(100), false, false, true}, - {New(-100), New(-100), false, false, true}, - {New(-100, -1), New(100), false, false, true}, - {New(-1, 1), New(1, -1), false, false, true}, - {New(1, -1), New(-1, 1), false, false, true}, - {New(3, 7), New(3, 7), false, false, true}, + {NewRat(0), NewRat(0), false, false, true}, + {NewRat(0, 100), NewRat(0, 10000), false, false, true}, + {NewRat(100), NewRat(100), false, false, true}, + {NewRat(-100), NewRat(-100), false, false, true}, + {NewRat(-100, -1), NewRat(100), false, false, true}, + {NewRat(-1, 1), NewRat(1, -1), false, false, true}, + {NewRat(1, -1), NewRat(-1, 1), false, false, true}, + {NewRat(3, 7), NewRat(3, 7), false, false, true}, - {New(0), New(3, 7), false, true, false}, - {New(0), New(100), false, true, false}, - {New(-1), New(3, 7), false, true, false}, - {New(-1), New(100), false, true, false}, - {New(1, 7), New(100), false, true, false}, - {New(1, 7), New(3, 7), false, true, false}, - {New(-3, 7), New(-1, 7), false, true, false}, + {NewRat(0), NewRat(3, 7), false, true, false}, + {NewRat(0), NewRat(100), false, true, false}, + {NewRat(-1), NewRat(3, 7), false, true, false}, + {NewRat(-1), NewRat(100), false, true, false}, + {NewRat(1, 7), NewRat(100), false, true, false}, + {NewRat(1, 7), NewRat(3, 7), false, true, false}, + {NewRat(-3, 7), NewRat(-1, 7), false, true, false}, - {New(3, 7), New(0), true, false, false}, - {New(100), New(0), true, false, false}, - {New(3, 7), New(-1), true, false, false}, - {New(100), New(-1), true, false, false}, - {New(100), New(1, 7), true, false, false}, - {New(3, 7), New(1, 7), true, false, false}, - {New(-1, 7), New(-3, 7), true, false, false}, + {NewRat(3, 7), NewRat(0), true, false, false}, + {NewRat(100), NewRat(0), true, false, false}, + {NewRat(3, 7), NewRat(-1), true, false, false}, + {NewRat(100), NewRat(-1), true, false, false}, + {NewRat(100), NewRat(1, 7), true, false, false}, + {NewRat(3, 7), NewRat(1, 7), true, false, false}, + {NewRat(-1, 7), NewRat(-3, 7), true, false, false}, } for _, tc := range tests { - assert.Equal(tc.gt, tc.r1.GT(tc.r2)) - assert.Equal(tc.lt, tc.r1.LT(tc.r2)) - assert.Equal(tc.eq, tc.r1.Equal(tc.r2)) + assert.Equal(t, tc.gt, tc.r1.GT(tc.r2)) + assert.Equal(t, tc.lt, tc.r1.LT(tc.r2)) + assert.Equal(t, tc.eq, tc.r1.Equal(tc.r2)) } } func TestArithmatic(t *testing.T) { - assert := asrt.New(t) - tests := []struct { r1, r2 Rat resMul, resDiv, resAdd, resSub Rat }{ - // r1 r2 MUL DIV ADD SUB - {New(0), New(0), New(0), New(0), New(0), New(0)}, - {New(1), New(0), New(0), New(0), New(1), New(1)}, - {New(0), New(1), New(0), New(0), New(1), New(-1)}, - {New(0), New(-1), New(0), New(0), New(-1), New(1)}, - {New(-1), New(0), New(0), New(0), New(-1), New(-1)}, + // r1 r2 MUL DIV ADD SUB + {NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0)}, + {NewRat(1), NewRat(0), NewRat(0), NewRat(0), NewRat(1), NewRat(1)}, + {NewRat(0), NewRat(1), NewRat(0), NewRat(0), NewRat(1), NewRat(-1)}, + {NewRat(0), NewRat(-1), NewRat(0), NewRat(0), NewRat(-1), NewRat(1)}, + {NewRat(-1), NewRat(0), NewRat(0), NewRat(0), NewRat(-1), NewRat(-1)}, - {New(1), New(1), New(1), New(1), New(2), New(0)}, - {New(-1), New(-1), New(1), New(1), New(-2), New(0)}, - {New(1), New(-1), New(-1), New(-1), New(0), New(2)}, - {New(-1), New(1), New(-1), New(-1), New(0), New(-2)}, + {NewRat(1), NewRat(1), NewRat(1), NewRat(1), NewRat(2), NewRat(0)}, + {NewRat(-1), NewRat(-1), NewRat(1), NewRat(1), NewRat(-2), NewRat(0)}, + {NewRat(1), NewRat(-1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(2)}, + {NewRat(-1), NewRat(1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(-2)}, - {New(3), New(7), New(21), New(3, 7), New(10), New(-4)}, - {New(2), New(4), New(8), New(1, 2), New(6), New(-2)}, - {New(100), New(100), New(10000), New(1), New(200), New(0)}, + {NewRat(3), NewRat(7), NewRat(21), NewRat(3, 7), NewRat(10), NewRat(-4)}, + {NewRat(2), NewRat(4), NewRat(8), NewRat(1, 2), NewRat(6), NewRat(-2)}, + {NewRat(100), NewRat(100), NewRat(10000), NewRat(1), NewRat(200), NewRat(0)}, - {New(3, 2), New(3, 2), New(9, 4), New(1), New(3), New(0)}, - {New(3, 7), New(7, 3), New(1), New(9, 49), New(58, 21), New(-40, 21)}, - {New(1, 21), New(11, 5), New(11, 105), New(5, 231), New(236, 105), New(-226, 105)}, - {New(-21), New(3, 7), New(-9), New(-49), New(-144, 7), New(-150, 7)}, - {New(100), New(1, 7), New(100, 7), New(700), New(701, 7), New(699, 7)}, + {NewRat(3, 2), NewRat(3, 2), NewRat(9, 4), NewRat(1), NewRat(3), NewRat(0)}, + {NewRat(3, 7), NewRat(7, 3), NewRat(1), NewRat(9, 49), NewRat(58, 21), NewRat(-40, 21)}, + {NewRat(1, 21), NewRat(11, 5), NewRat(11, 105), NewRat(5, 231), NewRat(236, 105), NewRat(-226, 105)}, + {NewRat(-21), NewRat(3, 7), NewRat(-9), NewRat(-49), NewRat(-144, 7), NewRat(-150, 7)}, + {NewRat(100), NewRat(1, 7), NewRat(100, 7), NewRat(700), NewRat(701, 7), NewRat(699, 7)}, } for _, tc := range tests { - assert.True(tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) - assert.True(tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) - assert.True(tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(t, tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) if tc.r2.Num() == 0 { // panic for divide by zero - assert.Panics(func() { tc.r1.Quo(tc.r2) }) + assert.Panics(t, func() { tc.r1.Quo(tc.r2) }) } else { - assert.True(tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) + assert.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) } } } func TestEvaluate(t *testing.T) { - assert := asrt.New(t) - tests := []struct { r1 Rat res int64 }{ - {New(0), 0}, - {New(1), 1}, - {New(1, 4), 0}, - {New(1, 2), 0}, - {New(3, 4), 1}, - {New(5, 6), 1}, - {New(3, 2), 2}, - {New(5, 2), 2}, - {New(6, 11), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even - {New(17, 11), 2}, // 1.545 - {New(5, 11), 0}, - {New(16, 11), 1}, - {New(113, 12), 9}, + {NewRat(0), 0}, + {NewRat(1), 1}, + {NewRat(1, 4), 0}, + {NewRat(1, 2), 0}, + {NewRat(3, 4), 1}, + {NewRat(5, 6), 1}, + {NewRat(3, 2), 2}, + {NewRat(5, 2), 2}, + {NewRat(6, 11), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even + {NewRat(17, 11), 2}, // 1.545 + {NewRat(5, 11), 0}, + {NewRat(16, 11), 1}, + {NewRat(113, 12), 9}, } for _, tc := range tests { - assert.Equal(tc.res, tc.r1.Evaluate(), "%v", tc.r1) - assert.Equal(tc.res*-1, tc.r1.Mul(New(-1)).Evaluate(), "%v", tc.r1.Mul(New(-1))) + assert.Equal(t, tc.res, tc.r1.Evaluate(), "%v", tc.r1) + assert.Equal(t, tc.res*-1, tc.r1.Mul(NewRat(-1)).Evaluate(), "%v", tc.r1.Mul(NewRat(-1))) } } func TestRound(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - many3 := "333333333333333333333333333333333333333333333" many7 := "777777777777777777777777777777777777777777777" big3, worked := new(big.Int).SetString(many3, 10) - require.True(worked) + require.True(t, worked) big7, worked := new(big.Int).SetString(many7, 10) - require.True(worked) + require.True(t, worked) tests := []struct { r1, res Rat precFactor int64 }{ - {New(333, 777), New(429, 1000), 1000}, - {Rat{new(big.Rat).SetFrac(big3, big7)}, New(429, 1000), 1000}, - {Rat{new(big.Rat).SetFrac(big3, big7)}, New(4285714286, 10000000000), 10000000000}, - {New(1, 2), New(1, 2), 1000}, + {NewRat(333, 777), NewRat(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, + {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(4285714286, 10000000000), 10000000000}, + {NewRat(1, 2), NewRat(1, 2), 1000}, } for _, tc := range tests { - assert.Equal(tc.res, tc.r1.Round(tc.precFactor), "%v", tc.r1) - negR1, negRes := tc.r1.Mul(New(-1)), tc.res.Mul(New(-1)) - assert.Equal(negRes, negR1.Round(tc.precFactor), "%v", negR1) + assert.Equal(t, tc.res, tc.r1.Round(tc.precFactor), "%v", tc.r1) + negR1, negRes := tc.r1.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1)) + assert.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1) } } func TestZeroSerializationJSON(t *testing.T) { - assert := asrt.New(t) - var r Rat err := json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":1}"), &r) - assert.Nil(err) + assert.Nil(t, err) err = json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":0}"), &r) - assert.NotNil(err) + assert.NotNil(t, err) err = json.Unmarshal([]byte("{\"numerator\":1,\"denominator\":0}"), &r) - assert.NotNil(err) + assert.NotNil(t, err) err = json.Unmarshal([]byte("{}"), &r) - assert.NotNil(err) + assert.NotNil(t, err) } func TestSerializationJSON(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) - - r := New(1, 3) + r := NewRat(1, 3) rMarshal, err := json.Marshal(r) - require.Nil(err) + require.Nil(t, err) var rUnmarshal Rat err = json.Unmarshal(rMarshal, &rUnmarshal) - require.Nil(err) + require.Nil(t, err) - assert.True(r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) } func TestSerializationGoWire(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) + r := NewRat(1, 3) - r := New(1, 3) - - rMarshal, err := cdc.MarshalJSON(r) - require.Nil(err) + rMarshal, err := ratCdc.MarshalJSON(r) + require.Nil(t, err) var rUnmarshal Rat - err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) - require.Nil(err) + err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(t, err) - assert.True(r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) } type testEmbedStruct struct { @@ -255,20 +237,18 @@ type testEmbedStruct struct { } func TestEmbeddedStructSerializationGoWire(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) + r := testEmbedStruct{"foo", 10, NewRat(1, 3)} - r := testEmbedStruct{"foo", 10, New(1, 3)} - - rMarshal, err := cdc.MarshalJSON(r) - require.Nil(err) + rMarshal, err := ratCdc.MarshalJSON(r) + require.Nil(t, err) var rUnmarshal testEmbedStruct - err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) - require.Nil(err) + err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(t, err) - assert.Equal(r.Field1, rUnmarshal.Field1) - assert.Equal(r.Field2, rUnmarshal.Field2) - assert.True(r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.Equal(t, r.Field1, rUnmarshal.Field1) + assert.Equal(t, r.Field2, rUnmarshal.Field2) + assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) } @@ -279,18 +259,16 @@ type testEmbedInterface struct { } func TestEmbeddedInterfaceSerializationGoWire(t *testing.T) { - assert, require := asrt.New(t), rqr.New(t) + r := testEmbedInterface{"foo", 10, NewRat(1, 3)} - r := testEmbedInterface{"foo", 10, New(1, 3)} - - rMarshal, err := cdc.MarshalJSON(r) - require.Nil(err) + rMarshal, err := ratCdc.MarshalJSON(r) + require.Nil(t, err) var rUnmarshal testEmbedInterface - err = cdc.UnmarshalJSON(rMarshal, &rUnmarshal) - require.Nil(err) + err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) + require.Nil(t, err) - assert.Equal(r.Field1, rUnmarshal.Field1) - assert.Equal(r.Field2, rUnmarshal.Field2) - assert.True(r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.Equal(t, r.Field1, rUnmarshal.Field1) + assert.Equal(t, r.Field2, rUnmarshal.Field2) + assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) } diff --git a/x/stake/store.go b/x/stake/store.go index 54073875cd..59f8aa78a9 100644 --- a/x/stake/store.go +++ b/x/stake/store.go @@ -142,7 +142,7 @@ func updateValidator(store types.KVStore, validator *Validator) { func removeValidator(store types.KVStore, pubKey crypto.PubKey) { //add validator with zero power to the validator updates - b, err := cdc.MarshalJSON(Validator{pubKey, types.Zero}) + b, err := cdc.MarshalJSON(Validator{pubKey, types.ZeroRat}) if err != nil { panic(err) } diff --git a/x/stake/store_test.go b/x/stake/store_test.go index 3956b2379b..89a85a1cb3 100644 --- a/x/stake/store_test.go +++ b/x/stake/store_test.go @@ -26,12 +26,12 @@ import ( //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //// test a basic change in voting power -//candidates[0].Assets = types.New(500) +//candidates[0].Assets = types.NewRat(500) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //// test a swap in voting power -//candidates[1].Assets = types.New(600) +//candidates[1].Assets = types.NewRat(600) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) @@ -46,11 +46,11 @@ import ( //func TestValidatorsChanged(t *testing.T) { //require := require.New(t) -//v1 := (&Candidate{PubKey: pks[0], VotingPower: types.New(10)}).validator() -//v2 := (&Candidate{PubKey: pks[1], VotingPower: types.New(10)}).validator() -//v3 := (&Candidate{PubKey: pks[2], VotingPower: types.New(10)}).validator() -//v4 := (&Candidate{PubKey: pks[3], VotingPower: types.New(10)}).validator() -//v5 := (&Candidate{PubKey: pks[4], VotingPower: types.New(10)}).validator() +//v1 := (&Candidate{PubKey: pks[0], VotingPower: types.NewRat(10)}).validator() +//v2 := (&Candidate{PubKey: pks[1], VotingPower: types.NewRat(10)}).validator() +//v3 := (&Candidate{PubKey: pks[2], VotingPower: types.NewRat(10)}).validator() +//v4 := (&Candidate{PubKey: pks[3], VotingPower: types.NewRat(10)}).validator() +//v5 := (&Candidate{PubKey: pks[4], VotingPower: types.NewRat(10)}).validator() //// test from nothing to something //vs1 := []Validator{} @@ -72,17 +72,17 @@ import ( //vs1 = []Validator{v1, v2, v4} //vs2 = []Validator{v1, v2, v4} //changed = vs1.validatorsUpdated(vs2) -//require.Zero(len(changed)) +//require.ZeroRat(len(changed)) //// test single value change -//vs2[2].VotingPower = types.One +//vs2[2].VotingPower = types.OneRat //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testChange(t, vs2[2], changed[0]) //// test multiple value change -//vs2[0].VotingPower = types.New(11) -//vs2[2].VotingPower = types.New(5) +//vs2[0].VotingPower = types.NewRat(11) +//vs2[2].VotingPower = types.NewRat(5) //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[0], changed[0]) @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = types.New(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 +//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = types.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -141,11 +141,11 @@ import ( //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) //// mess with the power's of the candidates and test -//candidates[0].Assets = types.New(10) -//candidates[1].Assets = types.New(600) -//candidates[2].Assets = types.New(1000) -//candidates[3].Assets = types.One -//candidates[4].Assets = types.New(10) +//candidates[0].Assets = types.NewRat(10) +//candidates[1].Assets = types.NewRat(600) +//candidates[2].Assets = types.NewRat(1000) +//candidates[3].Assets = types.OneRat +//candidates[4].Assets = types.NewRat(10) //for _, c := range candidates { //saveCandidate(store, c) //} @@ -161,8 +161,6 @@ import ( //} func TestState(t *testing.T) { - assert, require := assert.New(t), require.New(t) - store := initTestStore(t) //delegator := crypto.Address{[]byte("addressdelegator")} @@ -179,9 +177,9 @@ func TestState(t *testing.T) { candidate := &Candidate{ Owner: validator, PubKey: pk, - Assets: types.New(9), - Liabilities: types.New(9), - VotingPower: types.Zero, + Assets: types.NewRat(9), + Liabilities: types.NewRat(9), + VotingPower: types.ZeroRat, } candidatesEqual := func(c1, c2 *Candidate) bool { @@ -196,33 +194,33 @@ func TestState(t *testing.T) { // check the empty store first resCand := loadCandidate(store, pk) - assert.Nil(resCand) + assert.Nil(t, resCand) resPks := loadCandidates(store) - assert.Zero(len(resPks)) + assert.Zero(t, len(resPks)) // set and retrieve a record saveCandidate(store, candidate) resCand = loadCandidate(store, pk) //assert.Equal(candidate, resCand) - assert.True(candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) + assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve - candidate.Liabilities = types.New(99) + candidate.Liabilities = types.NewRat(99) saveCandidate(store, candidate) resCand = loadCandidate(store, pk) - assert.True(candidatesEqual(candidate, resCand)) + assert.True(t, candidatesEqual(candidate, resCand)) // also test that the pubkey has been added to pubkey list resPks = loadCandidates(store) - require.Equal(1, len(resPks)) - assert.Equal(pk, resPks[0].PubKey) + require.Equal(t, 1, len(resPks)) + assert.Equal(t, pk, resPks[0].PubKey) //---------------------------------------------------------------------- // Bond checks bond := &DelegatorBond{ PubKey: pk, - Shares: types.New(9), + Shares: types.NewRat(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -232,18 +230,18 @@ func TestState(t *testing.T) { //check the empty store first resBond := loadDelegatorBond(store, delegator, pk) - assert.Nil(resBond) + assert.Nil(t, resBond) //Set and retrieve a record saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) - assert.True(bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = types.New(99) + bond.Shares = types.NewRat(99) saveDelegatorBond(store, delegator, bond) resBond = loadDelegatorBond(store, delegator, pk) - assert.True(bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond, resBond)) //---------------------------------------------------------------------- // Param checks @@ -252,25 +250,23 @@ func TestState(t *testing.T) { //check that the empty store loads the default resParams := loadParams(store) - assert.Equal(params, resParams) + assert.Equal(t, params, resParams) //modify a params, save, and retrieve params.MaxVals = 777 saveParams(store, params) resParams = loadParams(store) - assert.Equal(params, resParams) + assert.Equal(t, params, resParams) } func TestGetValidators(t *testing.T) { - assert, require := assert.New(t), require.New(t) - store := initTestStore(t) N := 5 addrs := newAddrs(N) candidatesFromActors(store, addrs, []int64{400, 200, 0, 0, 0}) validators := getValidators(store, 5) - require.Equal(2, len(validators)) - assert.Equal(pks[0], validators[0].PubKey) - assert.Equal(pks[1], validators[1].PubKey) + require.Equal(t, 2, len(validators)) + assert.Equal(t, pks[0], validators[0].PubKey) + assert.Equal(t, pks[1], validators[1].PubKey) } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 7628fbd1bd..6b600ba339 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -73,9 +73,9 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6 Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: sdk.New(amts[i]), - Liabilities: sdk.New(amts[i]), - VotingPower: sdk.New(amts[i]), + Assets: sdk.NewRat(amts[i]), + Liabilities: sdk.NewRat(amts[i]), + VotingPower: sdk.NewRat(amts[i]), } saveCandidate(store, c) } @@ -87,9 +87,9 @@ func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { Status: Unbonded, PubKey: pks[i], Owner: addrs[i], - Assets: sdk.Zero, - Liabilities: sdk.Zero, - VotingPower: sdk.Zero, + Assets: sdk.ZeroRat, + Liabilities: sdk.ZeroRat, + VotingPower: sdk.ZeroRat, } candidates = append(candidates, c) } diff --git a/x/stake/types.go b/x/stake/types.go index 2d2632674a..1a184e1d9f 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -30,10 +30,10 @@ func defaultParams() Params { return Params{ HoldBonded: []byte("77777777777777777777777777777777"), HoldUnbonded: []byte("88888888888888888888888888888888"), - InflationRateChange: sdk.New(13, 100), - InflationMax: sdk.New(20, 100), - InflationMin: sdk.New(7, 100), - GoalBonded: sdk.New(67, 100), + InflationRateChange: sdk.NewRat(13, 100), + InflationMax: sdk.NewRat(20, 100), + InflationMin: sdk.NewRat(7, 100), + GoalBonded: sdk.NewRat(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -61,41 +61,41 @@ type GlobalState struct { func initialGlobalState() *GlobalState { return &GlobalState{ TotalSupply: 0, - BondedShares: sdk.Zero, - UnbondedShares: sdk.Zero, + BondedShares: sdk.ZeroRat, + UnbondedShares: sdk.ZeroRat, BondedPool: 0, UnbondedPool: 0, InflationLastTime: 0, - Inflation: sdk.New(7, 100), + Inflation: sdk.NewRat(7, 100), } } // get the bond ratio of the global state func (gs *GlobalState) bondedRatio() sdk.Rational { if gs.TotalSupply > 0 { - return sdk.New(gs.BondedPool, gs.TotalSupply) + return sdk.NewRat(gs.BondedPool, gs.TotalSupply) } - return sdk.Zero + return sdk.ZeroRat } // get the exchange rate of bonded token per issued share func (gs *GlobalState) bondedShareExRate() sdk.Rational { if gs.BondedShares.IsZero() { - return sdk.One + return sdk.OneRat } - return gs.BondedShares.Inv().Mul(sdk.New(gs.BondedPool)) + return gs.BondedShares.Inv().Mul(sdk.NewRat(gs.BondedPool)) } // get the exchange rate of unbonded tokens held in candidates per issued share func (gs *GlobalState) unbondedShareExRate() sdk.Rational { if gs.UnbondedShares.IsZero() { - return sdk.One + return sdk.OneRat } - return gs.UnbondedShares.Inv().Mul(sdk.New(gs.UnbondedPool)) + return gs.UnbondedShares.Inv().Mul(sdk.NewRat(gs.UnbondedPool)) } func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) { - issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.New(amount)) // (tokens/shares)^-1 * tokens + issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.BondedPool += amount gs.BondedShares = gs.BondedShares.Add(issuedShares) return @@ -109,7 +109,7 @@ func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens in } func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rational) { - issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.New(amount)) // (tokens/shares)^-1 * tokens + issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) gs.UnbondedPool += amount return @@ -165,9 +165,9 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri Status: Unbonded, PubKey: pubKey, Owner: owner, - Assets: sdk.Zero, - Liabilities: sdk.Zero, - VotingPower: sdk.Zero, + Assets: sdk.ZeroRat, + Liabilities: sdk.ZeroRat, + VotingPower: sdk.ZeroRat, Description: description, } } @@ -177,7 +177,7 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri // get the exchange rate of global pool shares over delegator shares func (c *Candidate) delegatorShareExRate() sdk.Rational { if c.Liabilities.IsZero() { - return sdk.One + return sdk.OneRat } return c.Assets.Quo(c.Liabilities) } From 80d88c3a4cfbbfe286865316071c2b5a2f79b915 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 23 Feb 2018 23:57:31 +0000 Subject: [PATCH 16/54] porting staking, wip wip --- x/stake/errors.go | 103 ++++++++ x/stake/handler.go | 520 ++++++++++++++++++++++++++++++++++++++++ x/stake/handler_test.go | 328 +++++++++++++++++++++++++ x/stake/tick.go | 74 ++++++ x/stake/tick_test.go | 116 +++++++++ 5 files changed, 1141 insertions(+) create mode 100644 x/stake/errors.go create mode 100644 x/stake/handler.go create mode 100644 x/stake/handler_test.go create mode 100644 x/stake/tick.go create mode 100644 x/stake/tick_test.go diff --git a/x/stake/errors.go b/x/stake/errors.go new file mode 100644 index 0000000000..1f5ab2fc61 --- /dev/null +++ b/x/stake/errors.go @@ -0,0 +1,103 @@ +// nolint +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CodeType = sdk.CodeType + +const ( + // Gaia errors reserve 200 ~ 299. + CodeInvalidValidator CodeType = 201 + CodeInvalidCandidate CodeType = 202 + CodeInvalidBond CodeType = 203 + CodeInvalidInput CodeType = 204 + CodeUnauthorized CodeType = sdk.CodeUnauthorized + CodeInternal CodeType = sdk.CodeInternal + CodeUnknownRequest CodeType = sdk.CodeUnknownRequest +) + +// NOTE: Don't stringer this, we'll put better messages in later. +func codeToDefaultMsg(code CodeType) string { + switch code { + case CodeInvalidValidator: + return "Invalid Validator" + case CodeInvalidCandidate: + return "Invalid Candidate" + case CodeInvalidBond: + return "Invalid Bond" + case CodeInvalidInput: + return "Invalid Input" + case CodeUnauthorized: + return "Unauthorized" + case CodeInternal: + return "Internal Error" + case CodeUnknownRequest: + return "Unknown request" + default: + return sdk.CodeToDefaultMsg(code) + } +} + +//---------------------------------------- +// Error constructors + +func ErrCandidateEmpty() error { + return newError(CodeInvalidValidator, "Cannot bond to an empty candidate") +} +func ErrBadBondingDenom() error { + return newError(CodeInvalidValidator, "Invalid coin denomination") +} +func ErrBadBondingAmount() error { + return newError(CodeInvalidValidator, "Amount must be > 0") +} +func ErrNoBondingAcct() error { + return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair") +} +func ErrCommissionNegative() error { + return newError(CodeInvalidValidator, "Commission must be positive") +} +func ErrCommissionHuge() error { + return newError(CodeInvalidValidator, "Commission cannot be more than 100%") +} +func ErrBadValidatorAddr() error { + return newError(CodeInvalidValidator, "Validator does not exist for that address") +} +func ErrCandidateExistsAddr() error { + return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy") +} +func ErrMissingSignature() error { + return newError(CodeInvalidValidator, "Missing signature") +} +func ErrBondNotNominated() error { + return newError(CodeInvalidValidator, "Cannot bond to non-nominated account") +} +func ErrNoCandidateForAddress() error { + return newError(CodeInvalidValidator, "Validator does not exist for that address") +} +func ErrNoDelegatorForAddress() error { + return newError(CodeInvalidValidator, "Delegator does not contain validator bond") +} +func ErrInsufficientFunds() error { + return newError(CodeInvalidValidator, "Insufficient bond shares") +} +func ErrBadRemoveValidator() error { + return newError(CodeInvalidValidator, "Error removing validator") +} + +//---------------------------------------- + +// TODO group with code from x/bank/errors.go + +func msgOrDefaultMsg(msg string, code CodeType) string { + if msg != "" { + return msg + } + return codeToDefaultMsg(code) +} + +func newError(code CodeType, msg string) sdk.Error { + msg = msgOrDefaultMsg(msg, code) + return sdk.NewError(code, msg) +} diff --git a/x/stake/handler.go b/x/stake/handler.go new file mode 100644 index 0000000000..e03a31b043 --- /dev/null +++ b/x/stake/handler.go @@ -0,0 +1,520 @@ +package stake + +import ( + "fmt" + "strconv" + + "github.com/spf13/viper" + "github.com/tendermint/tmlibs/log" + "github.com/tendermint/tmlibs/rational" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix +) + +// nolint +const stakingModuleName = "stake" + +// Name is the name of the modules. +func Name() string { + return stakingModuleName +} + +//_______________________________________________________________________ + +// DelegatedProofOfStake - interface to enforce delegation stake +type delegatedProofOfStake interface { + declareCandidacy(TxDeclareCandidacy) error + editCandidacy(TxEditCandidacy) error + delegate(TxDelegate) error + unbond(TxUnbond) error +} + +type coinSend interface { + transferFn(sender, receiver sdk.Actor, coins coin.Coins) error +} + +//_______________________________________________________________________ + +// Handler - the transaction processing handler +type Handler struct { +} + +// NewHandler returns a new Handler with the default Params +func NewHandler() Handler { + return Handler{} +} + +// Name - return stake namespace +func (Handler) Name() string { + return stakingModuleName +} + +// InitState - set genesis parameters for staking +func (h Handler) InitState(l log.Logger, store types.KVStore, + module, key, value string, cb sdk.InitStater) (log string, err error) { + return "", h.initState(module, key, value, store) +} + +// separated for testing +func (Handler) initState(module, key, value string, store types.KVStore) error { + if module != stakingModuleName { + return sdk.ErrUnknownModule(module) + } + + params := loadParams(store) + switch key { + case "allowed_bond_denom": + params.AllowedBondDenom = value + case "max_vals", + "gas_bond", + "gas_unbond": + + // TODO: enforce non-negative integers in input + i, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("input must be integer, Error: %v", err.Error()) + } + + switch key { + case "max_vals": + params.MaxVals = uint16(i) + case "gas_bond": + params.GasDelegate = int64(i) + case "gas_unbound": + params.GasUnbond = int64(i) + } + default: + return sdk.ErrUnknownKey(key) + } + + saveParams(store, params) + return nil +} + +// CheckTx checks if the tx is properly structured +func (h Handler) CheckTx(ctx sdk.Context, store types.KVStore, + tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) { + + err = tx.ValidateBasic() + if err != nil { + return res, err + } + + // get the sender + sender, err := getTxSender(ctx) + if err != nil { + return res, err + } + + params := loadParams(store) + + // create the new checker object to + checker := check{ + store: store, + sender: sender, + } + + // return the fee for each tx type + switch txInner := tx.Unwrap().(type) { + case TxDeclareCandidacy: + return sdk.NewCheck(params.GasDeclareCandidacy, ""), + checker.declareCandidacy(txInner) + case TxEditCandidacy: + return sdk.NewCheck(params.GasEditCandidacy, ""), + checker.editCandidacy(txInner) + case TxDelegate: + return sdk.NewCheck(params.GasDelegate, ""), + checker.delegate(txInner) + case TxUnbond: + return sdk.NewCheck(params.GasUnbond, ""), + checker.unbond(txInner) + } + + return res, sdk.ErrUnknownTxType(tx) +} + +// DeliverTx executes the tx if valid +func (h Handler) DeliverTx(ctx sdk.Context, store types.KVStore, + tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) { + + // TODO: remove redundancy + // also we don't need to check the res - gas is already deducted in sdk + _, err = h.CheckTx(ctx, store, tx, nil) + if err != nil { + return + } + + sender, err := getTxSender(ctx) + if err != nil { + return + } + + params := loadParams(store) + deliverer := deliver{ + store: store, + sender: sender, + params: params, + transfer: coinSender{ + store: store, + dispatch: dispatch, + ctx: ctx, + }.transferFn, + } + + // Run the transaction + switch _tx := tx.Unwrap().(type) { + case TxDeclareCandidacy: + res.GasUsed = params.GasDeclareCandidacy + return res, deliverer.declareCandidacy(_tx) + case TxEditCandidacy: + res.GasUsed = params.GasEditCandidacy + return res, deliverer.editCandidacy(_tx) + case TxDelegate: + res.GasUsed = params.GasDelegate + return res, deliverer.delegate(_tx) + case TxUnbond: + //context with hold account permissions + params := loadParams(store) + res.GasUsed = params.GasUnbond + ctx2 := ctx.WithPermissions(params.HoldBonded) + deliverer.transfer = coinSender{ + store: store, + dispatch: dispatch, + ctx: ctx2, + }.transferFn + return res, deliverer.unbond(_tx) + } + return +} + +// get the sender from the ctx and ensure it matches the tx pubkey +func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) { + senders := ctx.GetPermissions("", auth.NameSigs) + if len(senders) != 1 { + return sender, ErrMissingSignature() + } + return senders[0], nil +} + +//_______________________________________________________________________ + +type coinSender struct { + store types.KVStore + dispatch sdk.Deliver + ctx sdk.Context +} + +var _ coinSend = coinSender{} // enforce interface at compile time + +func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { + send := coin.NewSendOneTx(sender, receiver, coins) + + // If the deduction fails (too high), abort the command + _, err := c.dispatch.DeliverTx(c.ctx, c.store, send) + return err +} + +//_____________________________________________________________________ + +type check struct { + store types.KVStore + sender sdk.Actor +} + +var _ delegatedProofOfStake = check{} // enforce interface at compile time + +func (c check) declareCandidacy(tx TxDeclareCandidacy) error { + + // check to see if the pubkey or sender has been registered before + candidate := loadCandidate(c.store, tx.PubKey) + if candidate != nil { + return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ + " PubKey %v already registered with %v candidate address", + candidate.PubKey, candidate.Owner) + } + + return checkDenom(tx.BondUpdate, c.store) +} + +func (c check) editCandidacy(tx TxEditCandidacy) error { + + // candidate must already be registered + candidate := loadCandidate(c.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + return nil +} + +func (c check) delegate(tx TxDelegate) error { + + candidate := loadCandidate(c.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + return checkDenom(tx.BondUpdate, c.store) +} + +func (c check) unbond(tx TxUnbond) error { + + // check if bond has any shares in it unbond + bond := loadDelegatorBond(c.store, c.sender, tx.PubKey) + sharesStr := viper.GetString(tx.Shares) + if bond.Shares.LT(rational.Zero) { // bond shares < tx shares + return fmt.Errorf("no shares in account to unbond") + } + + // if shares set to maximum shares then we're good + if sharesStr == "MAX" { + return nil + } + + // test getting rational number from decimal provided + shares, err := rational.NewFromDecimal(sharesStr) + if err != nil { + return err + } + + // test that there are enough shares to unbond + if bond.Shares.LT(shares) { + return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", + bond.Shares, tx.Shares) + } + return nil +} + +func checkDenom(tx BondUpdate, store types.KVStore) error { + if tx.Bond.Denom != loadParams(store).AllowedBondDenom { + return fmt.Errorf("Invalid coin denomination") + } + return nil +} + +//_____________________________________________________________________ + +type deliver struct { + store types.KVStore + sender sdk.Actor + params Params + gs *GlobalState + transfer transferFn +} + +type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error + +var _ delegatedProofOfStake = deliver{} // enforce interface at compile time + +//_____________________________________________________________________ +// deliver helper functions + +// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK + +// move a candidates asset pool from bonded to unbonded pool +func (d deliver) bondedToUnbondedPool(candidate *Candidate) error { + + // replace bonded shares with unbonded shares + tokens := d.gs.removeSharesBonded(candidate.Assets) + candidate.Assets = d.gs.addTokensUnbonded(tokens) + candidate.Status = Unbonded + + return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded, + coin.Coins{{d.params.AllowedBondDenom, tokens}}) +} + +// move a candidates asset pool from unbonded to bonded pool +func (d deliver) unbondedToBondedPool(candidate *Candidate) error { + + // replace bonded shares with unbonded shares + tokens := d.gs.removeSharesUnbonded(candidate.Assets) + candidate.Assets = d.gs.addTokensBonded(tokens) + candidate.Status = Bonded + + return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded, + coin.Coins{{d.params.AllowedBondDenom, tokens}}) +} + +//_____________________________________________________________________ + +// These functions assume everything has been authenticated, +// now we just perform action and save +func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { + + // create and save the empty candidate + bond := loadCandidate(d.store, tx.PubKey) + if bond != nil { + return ErrCandidateExistsAddr() + } + candidate := NewCandidate(tx.PubKey, d.sender, tx.Description) + saveCandidate(d.store, candidate) + + // move coins from the d.sender account to a (self-bond) delegator account + // the candidate account and global shares are updated within here + txDelegate := TxDelegate{tx.BondUpdate} + return d.delegateWithCandidate(txDelegate, candidate) +} + +func (d deliver) editCandidacy(tx TxEditCandidacy) error { + + // Get the pubKey bond account + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrBondNotNominated() + } + if candidate.Status == Unbonded { //candidate has been withdrawn + return ErrBondNotNominated() + } + + //check and edit any of the editable terms + if tx.Description.Moniker != "" { + candidate.Description.Moniker = tx.Description.Moniker + } + if tx.Description.Identity != "" { + candidate.Description.Identity = tx.Description.Identity + } + if tx.Description.Website != "" { + candidate.Description.Website = tx.Description.Website + } + if tx.Description.Details != "" { + candidate.Description.Details = tx.Description.Details + } + + saveCandidate(d.store, candidate) + return nil +} + +func (d deliver) delegate(tx TxDelegate) error { + // Get the pubKey bond account + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrBondNotNominated() + } + return d.delegateWithCandidate(tx, candidate) +} + +func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { + + if candidate.Status == Revoked { //candidate has been withdrawn + return ErrBondNotNominated() + } + + var poolAccount sdk.Actor + if candidate.Status == Bonded { + poolAccount = d.params.HoldBonded + } else { + poolAccount = d.params.HoldUnbonded + } + + // TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK + // Move coins from the delegator account to the bonded pool account + err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond}) + if err != nil { + return err + } + + // Get or create the delegator bond + bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + if bond == nil { + bond = &DelegatorBond{ + PubKey: tx.PubKey, + Shares: rational.Zero, + } + } + + // Account new shares, save + bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs)) + saveCandidate(d.store, candidate) + saveDelegatorBond(d.store, d.sender, bond) + saveGlobalState(d.store, d.gs) + return nil +} + +func (d deliver) unbond(tx TxUnbond) error { + + // get delegator bond + bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + if bond == nil { + return ErrNoDelegatorForAddress() + } + + // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) + var shares rational.Rat + if tx.Shares == "MAX" { + shares = bond.Shares + } else { + var err error + shares, err = rational.NewFromDecimal(tx.Shares) + if err != nil { + return err + } + } + + // subtract bond tokens from delegator bond + if bond.Shares.LT(shares) { // bond shares < tx shares + return ErrInsufficientFunds() + } + bond.Shares = bond.Shares.Sub(shares) + + // get pubKey candidate + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { + return ErrNoCandidateForAddress() + } + + revokeCandidacy := false + if bond.Shares.IsZero() { + + // if the bond is the owner of the candidate then + // trigger a revoke candidacy + if d.sender.Equals(candidate.Owner) && + candidate.Status != Revoked { + revokeCandidacy = true + } + + // remove the bond + removeDelegatorBond(d.store, d.sender, tx.PubKey) + } else { + saveDelegatorBond(d.store, d.sender, bond) + } + + // transfer coins back to account + var poolAccount sdk.Actor + if candidate.Status == Bonded { + poolAccount = d.params.HoldBonded + } else { + poolAccount = d.params.HoldUnbonded + } + + returnCoins := candidate.removeShares(shares, d.gs) + err := d.transfer(poolAccount, d.sender, + coin.Coins{{d.params.AllowedBondDenom, returnCoins}}) + if err != nil { + return err + } + + // lastly if an revoke candidate if necessary + if revokeCandidacy { + + // change the share types to unbonded if they were not already + if candidate.Status == Bonded { + err = d.bondedToUnbondedPool(candidate) + if err != nil { + return err + } + } + + // lastly update the status + candidate.Status = Revoked + } + + // deduct shares from the candidate and save + if candidate.Liabilities.IsZero() { + removeCandidate(d.store, tx.PubKey) + } else { + saveCandidate(d.store, candidate) + } + + saveGlobalState(d.store, d.gs) + return nil +} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go new file mode 100644 index 0000000000..b6ea3beaa3 --- /dev/null +++ b/x/stake/handler_test.go @@ -0,0 +1,328 @@ +package stake + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/rational" + + sdk "github.com/cosmos/cosmos-sdk/types" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix +) + +//______________________________________________________________________ + +// dummy transfer functions, represents store operations on account balances + +type testCoinSender struct { + store map[string]int64 +} + +var _ coinSend = testCoinSender{} // enforce interface at compile time + +func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { + c.store[string(sender.Address)] -= coins[0].Amount + c.store[string(receiver.Address)] += coins[0].Amount + return nil +} + +//______________________________________________________________________ + +func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) { + accStore := map[string]int64{} + senders := newActors(n) + for _, sender := range senders { + accStore[string(sender.Address)] = amount + } + return senders, accStore +} + +func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy { + return TxDeclareCandidacy{ + BondUpdate{ + PubKey: pubKey, + Bond: coin.Coin{"fermion", amt}, + }, + Description{}, + } +} + +func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate { + return TxDelegate{BondUpdate{ + PubKey: pubKey, + Bond: coin.Coin{"fermion", amt}, + }} +} + +func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond { + return TxUnbond{ + PubKey: pubKey, + Shares: shares, + } +} + +func paramsNoInflation() Params { + return Params{ + HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), + HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), + InflationRateChange: rational.Zero, + InflationMax: rational.Zero, + InflationMin: rational.Zero, + GoalBonded: rational.New(67, 100), + MaxVals: 100, + AllowedBondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +func newDeliver(t, sender sdk.Actor, accStore map[string]int64) deliver { + store := initTestStore() + params := paramsNoInflation() + saveParams(store, params) + return deliver{ + store: store, + sender: sender, + params: params, + gs: loadGlobalState(store), + transfer: testCoinSender{accStore}.transferFn, + } +} + +func TestDuplicatesTxDeclareCandidacy(t *testing.T) { + senders, accStore := initAccounts(2, 1000) // for accounts + + deliverer := newDeliver(t, senders[0], accStore) + checker := check{ + store: deliverer.store, + sender: senders[0], + } + + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(t, got, "expected no error on runTxDeclareCandidacy") + + // one sender can bond to two different pubKeys + txDeclareCandidacy.PubKey = pks[1] + err := checker.declareCandidacy(txDeclareCandidacy) + assert.Nil(t, err, "didn't expected error on checkTx") + + // two senders cant bond to the same pubkey + checker.sender = senders[1] + txDeclareCandidacy.PubKey = pks[0] + err = checker.declareCandidacy(txDeclareCandidacy) + assert.NotNil(t, err, "expected error on checkTx") +} + +func TestIncrementsTxDelegate(t *testing.T) { + initSender := int64(1000) + senders, accStore := initAccounts(1, initSender) // for accounts + deliverer := newDeliver(t, senders[0], accStore) + + // first declare candidacy + bondAmount := int64(10) + txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got) + expectedBond := bondAmount // 1 since we send 1 at the start of loop, + + // just send the same txbond multiple times + holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates + txDelegate := newTxDelegate(bondAmount, pks[0]) + for i := 0; i < 5; i++ { + got := deliverer.delegate(txDelegate) + assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the accounts and the bond account have the appropriate values + candidates := loadCandidates(deliverer.store) + expectedBond += bondAmount + expectedSender := initSender - expectedBond + gotBonded := candidates[0].Liabilities.Evaluate() + gotHolder := accStore[string(holder.Address)] + gotSender := accStore[string(deliverer.sender.Address)] + assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) + assert.Equal(t, expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder) + assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) + } +} + +func TestIncrementsTxUnbond(t *testing.T) { + initSender := int64(0) + senders, accStore := initAccounts(1, initSender) // for accounts + deliverer := newDeliver(t, senders[0], accStore) + + // set initial bond + initBond := int64(1000) + accStore[string(deliverer.sender.Address)] = initBond + got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0])) + assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got) + + // just send the same txunbond multiple times + holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded + + // XXX use decimals here + unbondShares, unbondSharesStr := int64(10), "10" + txUndelegate := newTxUnbond(unbondSharesStr, pks[0]) + nUnbonds := 5 + for i := 0; i < nUnbonds; i++ { + got := deliverer.unbond(txUndelegate) + assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the accounts and the bond account have the appropriate values + candidates := loadCandidates(deliverer.store) + 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() + gotHolder := accStore[string(holder.Address)] + gotSender := accStore[string(deliverer.sender.Address)] + + assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) + assert.Equal(t, expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder) + assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) + } + + // 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)) + txUndelegate := newTxUnbond(unbondShares, pks[0]) + got = deliverer.unbond(txUndelegate) + assert.Error(t, got, "expected unbond tx to fail") + } + + leftBonded := initBond - unbondShares*int64(nUnbonds) + + // should be unable to unbond one more than we have + txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) + got = deliverer.unbond(txUndelegate) + assert.Error(t, got, "expected unbond tx to fail") + + // should be able to unbond just what we have + txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0]) + got = deliverer.unbond(txUndelegate) + assert.NoError(t, got, "expected unbond tx to pass") +} + +func TestMultipleTxDeclareCandidacy(t *testing.T) { + initSender := int64(1000) + senders, accStore := initAccounts(3, initSender) + pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]} + deliverer := newDeliver(t, senders[0], accStore) + + // bond them all + for i, sender := range senders { + txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i]) + deliverer.sender = sender + got := deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is bonded + candidates := loadCandidates(deliverer.store) + val := candidates[i] + balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10 + 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, sender := range senders { + candidatePre := loadCandidate(deliverer.store, pubKeys[i]) + txUndelegate := newTxUnbond("10", pubKeys[i]) + deliverer.sender = sender + got := deliverer.unbond(txUndelegate) + assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is unbonded + candidates := loadCandidates(deliverer.store) + assert.Equal(t, len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates)) + + candidatePost := loadCandidate(deliverer.store, pubKeys[i]) + balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender + 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 TestMultipleTxDelegate(t *testing.T) { + accounts, accStore := initAccounts(3, 1000) + sender, delegators := accounts[0], accounts[1:] + deliverer := newDeliver(t, sender, accStore) + + //first make a candidate + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + require.NoError(t, got, "expected tx to be ok, got %v", got) + + // delegate multiple parties + for i, delegator := range delegators { + txDelegate := newTxDelegate(10, pks[0]) + deliverer.sender = delegator + got := deliverer.delegate(txDelegate) + require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is bonded + bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) + } + + // unbond them all + for i, delegator := range delegators { + txUndelegate := newTxUnbond("10", pks[0]) + deliverer.sender = delegator + got := deliverer.unbond(txUndelegate) + require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + + //Check that the account is unbonded + bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) + } +} + +func TestVoidCandidacy(t *testing.T) { + accounts, accStore := initAccounts(2, 1000) // for accounts + sender, delegator := accounts[0], accounts[1] + deliverer := newDeliver(t, sender, accStore) + + // create the candidate + txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + got := deliverer.declareCandidacy(txDeclareCandidacy) + require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + + // bond a delegator + txDelegate := newTxDelegate(10, pks[0]) + deliverer.sender = delegator + got = deliverer.delegate(txDelegate) + require.NoError(t, got, "expected ok, got %v", got) + + // unbond the candidates bond portion + txUndelegate := newTxUnbond("10", pks[0]) + deliverer.sender = sender + got = deliverer.unbond(txUndelegate) + require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + + // test that this pubkey cannot yet be bonded too + deliverer.sender = delegator + got = deliverer.delegate(txDelegate) + assert.Error(t, got, "expected error, got %v", got) + + // test that the delegator can still withdraw their bonds + got = deliverer.unbond(txUndelegate) + require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + + // verify that the pubkey can now be reused + got = deliverer.declareCandidacy(txDeclareCandidacy) + assert.NoError(t, got, "expected ok, got %v", got) +} diff --git a/x/stake/tick.go b/x/stake/tick.go new file mode 100644 index 0000000000..42c995da44 --- /dev/null +++ b/x/stake/tick.go @@ -0,0 +1,74 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/rational" +) + +// Tick - called at the end of every block +func Tick(ctx sdk.Context, store types.KVStore) (change []*abci.Validator, err error) { + + // retrieve params + params := loadParams(store) + gs := loadGlobalState(store) + height := ctx.BlockHeight() + + // Process Validator Provisions + // XXX right now just process every 5 blocks, in new SDK make hourly + if gs.InflationLastTime+5 <= height { + gs.InflationLastTime = height + processProvisions(store, gs, params) + } + + return UpdateValidatorSet(store, gs, params) +} + +var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days + +// process provisions for an hour period +func processProvisions(store types.KVStore, gs *GlobalState, params Params) { + + gs.Inflation = nextInflation(gs, params).Round(1000000000) + + // Because the validators hold a relative bonded share (`GlobalStakeShare`), when + // more bonded tokens are added proportionally to all validators the only term + // which needs to be updated is the `BondedPool`. So for each previsions cycle: + + provisions := gs.Inflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() + gs.BondedPool += provisions + gs.TotalSupply += provisions + + // XXX XXX XXX XXX XXX XXX XXX XXX XXX + // XXX Mint them to the hold account + // XXX XXX XXX XXX XXX XXX XXX XXX XXX + + // save the params + saveGlobalState(store, gs) +} + +// get the next inflation rate for the hour +func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) { + + // The target annual inflation rate is recalculated for each previsions cycle. The + // inflation is also subject to a rate change (positive of negative) depending or + // the distance from the desired ratio (67%). The maximum rate change possible is + // defined to be 13% per year, however the annual inflation is capped as between + // 7% and 20%. + + // (1 - bondedRatio/GoalBonded) * InflationRateChange + inflationRateChangePerYear := rational.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + + // increase the new annual inflation for this next cycle + inflation = gs.Inflation.Add(inflationRateChange) + if inflation.GT(params.InflationMax) { + inflation = params.InflationMax + } + if inflation.LT(params.InflationMin) { + inflation = params.InflationMin + } + + return +} diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go new file mode 100644 index 0000000000..add6be80b3 --- /dev/null +++ b/x/stake/tick_test.go @@ -0,0 +1,116 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tendermint/tmlibs/rational" +) + +func TestGetInflation(t *testing.T) { + store := initTestStore(t) + params := loadParams(store) + gs := loadGlobalState(store) + + // Governing Mechanism: + // bondedRatio = BondedPool / TotalSupply + // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + + tests := []struct { + setBondedPool, setTotalSupply int64 + setInflation, expectedChange rational.Rat + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + + // 100% bonded, starting at 20% inflation and being reduced + {1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // 50% bonded, starting at 10% inflation and being increased + {1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + + // test 7% minimum stop (testing with 100% bonded) + {1, 1, rational.New(7, 100), rational.Zero}, + {1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)}, + + // test 20% maximum stop (testing with 0% bonded) + {0, 0, rational.New(20, 100), rational.Zero}, + {0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)}, + + // perfect balance shouldn't change inflation + {67, 100, rational.New(15, 100), rational.Zero}, + } + for _, tc := range tests { + gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply + gs.Inflation = tc.setInflation + + inflation := nextInflation(gs, params) + diffInflation := inflation.Sub(tc.setInflation) + + assert.True(t, diffInflation.Equal(tc.expectedChange), + "%v, %v", diffInflation, tc.expectedChange) + } +} + +func TestProcessProvisions(t *testing.T) { + store := initTestStore(t) + params := loadParams(store) + gs := loadGlobalState(store) + + // create some candidates some bonded, some unbonded + n := 10 + actors := newActors(n) + candidates := candidatesFromActorsEmpty(actors) + for i, candidate := range candidates { + if i < 5 { + candidate.Status = Bonded + } + mintedTokens := int64((i + 1) * 10000000) + gs.TotalSupply += mintedTokens + candidate.addTokens(mintedTokens, gs) + saveCandidate(store, candidate) + } + var totalSupply int64 = 550000000 + var bondedShares int64 = 150000000 + var unbondedShares int64 = 400000000 + + // initial bonded ratio ~ 27% + assert.True(t, gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) + + // Supplies + assert.Equal(t, totalSupply, gs.TotalSupply) + assert.Equal(t, bondedShares, gs.BondedPool) + assert.Equal(t, unbondedShares, gs.UnbondedPool) + + // test the value of candidate shares + assert.True(t, gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate()) + + initialSupply := gs.TotalSupply + initialUnbonded := gs.TotalSupply - gs.BondedPool + + // process the provisions a year + for hr := 0; hr < 8766; hr++ { + expInflation := nextInflation(gs, params).Round(1000000000) + expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() + startBondedPool := gs.BondedPool + startTotalSupply := gs.TotalSupply + processProvisions(store, gs, params) + assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) + assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) + } + assert.NotEqual(t, initialSupply, gs.TotalSupply) + assert.Equal(t, initialUnbonded, gs.UnbondedPool) + //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) + + // initial bonded ratio ~ 35% ~ 30% increase for bonded holders + assert.True(t, gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio()) + + // global supply + assert.Equal(t, int64(611813022), gs.TotalSupply) + assert.Equal(t, int64(211813022), gs.BondedPool) + assert.Equal(t, unbondedShares, gs.UnbondedPool) + + // test the value of candidate shares + assert.True(t, gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate()) + +} From 5d3dc29ce1303e30ff5336272bced8e997b9a5df Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 24 Feb 2018 13:19:32 +0000 Subject: [PATCH 17/54] bringing in more --- x/stake/commands/query.go | 141 +++++++++++++++++++++++++++ x/stake/commands/tx.go | 195 ++++++++++++++++++++++++++++++++++++++ x/stake/tx.go | 146 ++++++++++++++++++++++++++++ x/stake/tx_test.go | 104 ++++++++++++++++++++ 4 files changed, 586 insertions(+) create mode 100644 x/stake/commands/query.go create mode 100644 x/stake/commands/tx.go create mode 100644 x/stake/tx.go create mode 100644 x/stake/tx_test.go diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go new file mode 100644 index 0000000000..aa455d3474 --- /dev/null +++ b/x/stake/commands/query.go @@ -0,0 +1,141 @@ +package commands + +import ( + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + crypto "github.com/tendermint/go-crypto" + + "github.com/cosmos/cosmos-sdk/client/commands" + "github.com/cosmos/cosmos-sdk/client/commands/query" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix + "github.com/cosmos/gaia/x/stake" +) + +// XXX remove dependancy +func PrefixedKey(app string, key []byte) []byte { + prefix := append([]byte(app), byte(0)) + return append(prefix, key...) +} + +//nolint +var ( + CmdQueryCandidates = &cobra.Command{ + Use: "candidates", + Short: "Query for the set of validator-candidates pubkeys", + RunE: cmdQueryCandidates, + } + + CmdQueryCandidate = &cobra.Command{ + Use: "candidate", + Short: "Query a validator-candidate account", + RunE: cmdQueryCandidate, + } + + CmdQueryDelegatorBond = &cobra.Command{ + Use: "delegator-bond", + Short: "Query a delegators bond based on address and candidate pubkey", + RunE: cmdQueryDelegatorBond, + } + + CmdQueryDelegatorCandidates = &cobra.Command{ + Use: "delegator-candidates", + RunE: cmdQueryDelegatorCandidates, + Short: "Query all delegators candidates' pubkeys based on address", + } + + FlagDelegatorAddress = "delegator-address" +) + +func init() { + //Add Flags + fsPk := flag.NewFlagSet("", flag.ContinueOnError) + fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") + fsAddr := flag.NewFlagSet("", flag.ContinueOnError) + fsAddr.String(FlagDelegatorAddress, "", "Delegator Hex Address") + + CmdQueryCandidate.Flags().AddFlagSet(fsPk) + CmdQueryDelegatorBond.Flags().AddFlagSet(fsPk) + CmdQueryDelegatorBond.Flags().AddFlagSet(fsAddr) + CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr) +} + +func cmdQueryCandidates(cmd *cobra.Command, args []string) error { + + var pks []crypto.PubKey + + prove := !viper.GetBool(commands.FlagTrustNode) + key := PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) + height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(pks, height) +} + +func cmdQueryCandidate(cmd *cobra.Command, args []string) error { + + var candidate stake.Candidate + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + prove := !viper.GetBool(commands.FlagTrustNode) + key := PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) + height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(candidate, height) +} + +func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error { + + var bond stake.DelegatorBond + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + delegatorAddr := viper.GetString(FlagDelegatorAddress) + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + return err + } + delegator = coin.ChainAddr(delegator) + + prove := !viper.GetBool(commands.FlagTrustNode) + key := PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) + height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(bond, height) +} + +func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error { + + delegatorAddr := viper.GetString(FlagDelegatorAddress) + delegator, err := commands.ParseActor(delegatorAddr) + if err != nil { + return err + } + delegator = coin.ChainAddr(delegator) + + prove := !viper.GetBool(commands.FlagTrustNode) + key := PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) + var candidates []crypto.PubKey + height, err := query.GetParsed(key, &candidates, query.GetHeight(), prove) + if err != nil { + return err + } + + return query.OutputProof(candidates, height) +} diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go new file mode 100644 index 0000000000..e14ac8cec1 --- /dev/null +++ b/x/stake/commands/tx.go @@ -0,0 +1,195 @@ +package commands + +import ( + "encoding/hex" + "fmt" + + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/rational" + + txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" + "github.com/cosmos/cosmos-sdk/modules/coin" + + "github.com/cosmos/gaia/modules/stake" +) + +// nolint +const ( + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagShares = "shares" + + FlagMoniker = "moniker" + FlagIdentity = "keybase-sig" + FlagWebsite = "website" + FlagDetails = "details" +) + +// nolint +var ( + CmdDeclareCandidacy = &cobra.Command{ + Use: "declare-candidacy", + Short: "create new validator-candidate account and delegate some coins to it", + RunE: cmdDeclareCandidacy, + } + CmdEditCandidacy = &cobra.Command{ + Use: "edit-candidacy", + Short: "edit and existing validator-candidate account", + RunE: cmdEditCandidacy, + } + CmdDelegate = &cobra.Command{ + Use: "delegate", + Short: "delegate coins to an existing validator/candidate", + RunE: cmdDelegate, + } + CmdUnbond = &cobra.Command{ + Use: "unbond", + Short: "unbond coins from a validator/candidate", + RunE: cmdUnbond, + } +) + +func init() { + + // define the flags + fsPk := flag.NewFlagSet("", flag.ContinueOnError) + fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") + + fsAmount := flag.NewFlagSet("", flag.ContinueOnError) + fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond") + + fsShares := flag.NewFlagSet("", flag.ContinueOnError) + fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") + + fsCandidate := flag.NewFlagSet("", flag.ContinueOnError) + fsCandidate.String(FlagMoniker, "", "validator-candidate name") + fsCandidate.String(FlagIdentity, "", "optional keybase signature") + fsCandidate.String(FlagWebsite, "", "optional website") + fsCandidate.String(FlagDetails, "", "optional detailed description space") + + // add the flags + CmdDelegate.Flags().AddFlagSet(fsPk) + CmdDelegate.Flags().AddFlagSet(fsAmount) + + CmdUnbond.Flags().AddFlagSet(fsPk) + CmdUnbond.Flags().AddFlagSet(fsShares) + + CmdDeclareCandidacy.Flags().AddFlagSet(fsPk) + CmdDeclareCandidacy.Flags().AddFlagSet(fsAmount) + CmdDeclareCandidacy.Flags().AddFlagSet(fsCandidate) + + CmdEditCandidacy.Flags().AddFlagSet(fsPk) + CmdEditCandidacy.Flags().AddFlagSet(fsCandidate) +} + +func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { + amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + if viper.GetString(FlagMoniker) == "" { + return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") + } + + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + + tx := stake.NewTxDeclareCandidacy(amount, pk, description) + return txcmd.DoTx(tx) +} + +func cmdEditCandidacy(cmd *cobra.Command, args []string) error { + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + + tx := stake.NewTxEditCandidacy(pk, description) + return txcmd.DoTx(tx) +} + +func cmdDelegate(cmd *cobra.Command, args []string) error { + amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + tx := stake.NewTxDelegate(amount, pk) + return txcmd.DoTx(tx) +} + +func cmdUnbond(cmd *cobra.Command, args []string) error { + + // TODO once go-wire refactored the shares can be broadcast as a Rat instead of a string + + // check the shares before broadcasting + sharesStr := viper.GetString(FlagShares) + var shares rational.Rat + if sharesStr != "MAX" { + var err error + shares, err = rational.NewFromDecimal(sharesStr) + if err != nil { + return err + } + if !shares.GT(rational.Zero) { + return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") + } + } + + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + + tx := stake.NewTxUnbond(sharesStr, pk) + return txcmd.DoTx(tx) +} + +// GetPubKey - create the pubkey from a pubkey string +func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { + + if len(pubKeyStr) == 0 { + err = fmt.Errorf("must use --pubkey flag") + return + } + if len(pubKeyStr) != 64 { //if len(pkBytes) != 32 { + err = fmt.Errorf("pubkey must be Ed25519 hex encoded string which is 64 characters long") + return + } + var pkBytes []byte + pkBytes, err = hex.DecodeString(pubKeyStr) + if err != nil { + return + } + var pkEd crypto.PubKeyEd25519 + copy(pkEd[:], pkBytes[:]) + pk = pkEd.Wrap() + return +} diff --git a/x/stake/tx.go b/x/stake/tx.go new file mode 100644 index 0000000000..3d5a6c4083 --- /dev/null +++ b/x/stake/tx.go @@ -0,0 +1,146 @@ +package stake + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix + crypto "github.com/tendermint/go-crypto" +) + +// Tx +//-------------------------------------------------------------------------------- + +// register the tx type with its validation logic +// make sure to use the name of the handler as the prefix in the tx type, +// so it gets routed properly +const ( + ByteTxDeclareCandidacy = 0x55 + ByteTxEditCandidacy = 0x56 + ByteTxDelegate = 0x57 + ByteTxUnbond = 0x58 + TypeTxDeclareCandidacy = stakingModuleName + "/declareCandidacy" + TypeTxEditCandidacy = stakingModuleName + "/editCandidacy" + TypeTxDelegate = stakingModuleName + "/delegate" + TypeTxUnbond = stakingModuleName + "/unbond" +) + +//func init() { +//sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy) +//sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy) +//sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate) +//sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond) +//} + +//Verify interface at compile time +//var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{} + +// BondUpdate - struct for bonding or unbonding transactions +type BondUpdate struct { + PubKey crypto.PubKey `json:"pub_key"` + Bond coin.Coin `json:"amount"` +} + +// ValidateBasic - Check for non-empty candidate, and valid coins +func (tx BondUpdate) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + coins := coin.Coins{tx.Bond} + if !coins.IsValid() { + return coin.ErrInvalidCoins() + } + if !coins.IsPositive() { + return fmt.Errorf("Amount must be > 0") + } + return nil +} + +// TxDeclareCandidacy - struct for unbonding transactions +type TxDeclareCandidacy struct { + BondUpdate + Description +} + +// NewTxDeclareCandidacy - new TxDeclareCandidacy +func NewTxDeclareCandidacy(bond coin.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { + return TxDeclareCandidacy{ + BondUpdate{ + PubKey: pubKey, + Bond: bond, + }, + description, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// TxEditCandidacy - struct for editing a candidate +type TxEditCandidacy struct { + PubKey crypto.PubKey `json:"pub_key"` + Description +} + +// NewTxEditCandidacy - new TxEditCandidacy +func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx { + return TxEditCandidacy{ + PubKey: pubKey, + Description: description, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// ValidateBasic - Check for non-empty candidate, +func (tx TxEditCandidacy) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + + empty := Description{} + if tx.Description == empty { + return fmt.Errorf("Transaction must include some information to modify") + } + return nil +} + +// TxDelegate - struct for bonding transactions +type TxDelegate struct{ BondUpdate } + +// NewTxDelegate - new TxDelegate +func NewTxDelegate(bond coin.Coin, pubKey crypto.PubKey) sdk.Tx { + return TxDelegate{BondUpdate{ + PubKey: pubKey, + Bond: bond, + }}.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// TxUnbond - struct for unbonding transactions +type TxUnbond struct { + PubKey crypto.PubKey `json:"pub_key"` + Shares string `json:"amount"` +} + +// NewTxUnbond - new TxUnbond +func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx { + return TxUnbond{ + PubKey: pubKey, + Shares: shares, + }.Wrap() +} + +// Wrap - Wrap a Tx as a Basecoin Tx +func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} } + +// ValidateBasic - Check for non-empty candidate, positive shares +func (tx TxUnbond) ValidateBasic() error { + if tx.PubKey.Empty() { + return errCandidateEmpty + } + return nil +} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go new file mode 100644 index 0000000000..f6d814589d --- /dev/null +++ b/x/stake/tx_test.go @@ -0,0 +1,104 @@ +package stake + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cosmos/cosmos-sdk" + "github.com/cosmos/cosmos-sdk/modules/coin" + + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" +) + +var ( + validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")} + empty sdk.Actor + + coinPos = coin.Coin{"fermion", 1000} + coinZero = coin.Coin{"fermion", 0} + coinNeg = coin.Coin{"fermion", -10000} + coinPosNotAtoms = coin.Coin{"foo", 10000} + coinZeroNotAtoms = coin.Coin{"foo", 0} + coinNegNotAtoms = coin.Coin{"foo", -10000} +) + +func TestBondUpdateValidateBasic(t *testing.T) { + tests := []struct { + name string + PubKey crypto.PubKey + Bond coin.Coin + wantErr bool + }{ + {"basic good", pks[0], coinPos, false}, + {"empty delegator", crypto.PubKey{}, coinPos, true}, + {"zero coin", pks[0], coinZero, true}, + {"neg coin", pks[0], coinNeg, true}, + } + + for _, tc := range tests { + tx := TxDelegate{BondUpdate{ + PubKey: tc.PubKey, + Bond: tc.Bond, + }} + assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) + } +} + +func TestAllAreTx(t *testing.T) { + assert := assert.New(t) + + // make sure all types construct properly + pubKey := newPubKey("1234567890") + bondAmt := 1234321 + bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + + // Note that Wrap is only defined on BondUpdate, so when you call it, + // you lose all info on the embedding type. Please add Wrap() + // method to all the parents + txDelegate := NewTxDelegate(bond, pubKey) + _, ok := txDelegate.Unwrap().(TxDelegate) + assert.True(ok, "%#v", txDelegate) + + txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey) + _, ok = txUnbond.Unwrap().(TxUnbond) + assert.True(ok, "%#v", txUnbond) + + txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{}) + _, ok = txDecl.Unwrap().(TxDeclareCandidacy) + assert.True(ok, "%#v", txDecl) + + txEditCan := NewTxEditCandidacy(pubKey, Description{}) + _, ok = txEditCan.Unwrap().(TxEditCandidacy) + assert.True(ok, "%#v", txEditCan) +} + +func TestSerializeTx(t *testing.T) { + assert := assert.New(t) + + // make sure all types construct properly + pubKey := newPubKey("1234567890") + bondAmt := 1234321 + bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + + tests := []struct { + tx sdk.Tx + }{ + {NewTxUnbond(strconv.Itoa(bondAmt), pubKey)}, + {NewTxDeclareCandidacy(bond, pubKey, Description{})}, + {NewTxDeclareCandidacy(bond, pubKey, Description{})}, + // {NewTxRevokeCandidacy(pubKey)}, + } + + for i, tc := range tests { + var tx sdk.Tx + bs := wire.BinaryBytes(tc.tx) + err := wire.ReadBinaryBytes(bs, &tx) + if assert.NoError(err, "%d", i) { + assert.Equal(tc.tx, tx, "%d", i) + } + } +} From 7fc56327aa60e11714ff3458749f478ff07d6057 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sun, 25 Feb 2018 23:45:20 +0000 Subject: [PATCH 18/54] wip porting old gaia cli --- x/stake/commands/query.go | 115 +++++++++++++++++++++++++++++++------- x/stake/commands/tx.go | 58 ++++++++++++++----- x/stake/tx.go | 13 ++--- x/stake/tx_test.go | 37 ++++++------ 4 files changed, 161 insertions(+), 62 deletions(-) diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index aa455d3474..46fc029526 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -1,16 +1,21 @@ package commands import ( + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/pkg/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" crypto "github.com/tendermint/go-crypto" - "github.com/cosmos/cosmos-sdk/client/commands" - "github.com/cosmos/cosmos-sdk/client/commands/query" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix - "github.com/cosmos/gaia/x/stake" + "github.com/cosmos/cosmos-sdk/x/stake" ) // XXX remove dependancy @@ -61,18 +66,59 @@ func init() { CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr) } +// XXX move to common directory in client helpers +func makeQuery(key, storeName string) (res []byte, err error) { + + path := fmt.Sprintf("/%s/key", a.storeName) + + uri := viper.GetString(client.FlagNode) + if uri == "" { + return res, errors.New("Must define which node to query with --node") + } + node := client.GetNode(uri) + + opts := rpcclient.ABCIQueryOptions{ + Height: viper.GetInt64(client.FlagHeight), + Trusted: viper.GetBool(client.FlagTrustNode), + } + result, err := node.ABCIQueryWithOptions(path, key, opts) + if err != nil { + return res, err + } + resp := result.Response + if resp.Code != uint32(0) { + return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log) + } + return resp.val, nil +} + func cmdQueryCandidates(cmd *cobra.Command, args []string) error { var pks []crypto.PubKey - prove := !viper.GetBool(commands.FlagTrustNode) + prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) - height, err := query.GetParsed(key, &pks, query.GetHeight(), prove) + + res, err := makeQuery(key, "gaia-store-name") // XXX move gaia store name out of here if err != nil { return err } - return query.OutputProof(pks, height) + // parse out the candidates + candidates := new(stake.Candidates) + cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module + err = cdc.UnmarshalBinary(res, candidates) + if err != nil { + return err + } + output, err := json.MarshalIndent(candidates, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. } func cmdQueryCandidate(cmd *cobra.Command, args []string) error { @@ -84,58 +130,85 @@ func cmdQueryCandidate(cmd *cobra.Command, args []string) error { return err } - prove := !viper.GetBool(commands.FlagTrustNode) + prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) - height, err := query.GetParsed(key, &candidate, query.GetHeight(), prove) + + // parse out the candidate + candidate := new(stake.Candidate) + cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module + err = cdc.UnmarshalBinary(res, candidate) if err != nil { return err } + output, err := json.MarshalIndent(candidate, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil - return query.OutputProof(candidate, height) + // TODO output with proofs / machine parseable etc. } func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error { - var bond stake.DelegatorBond - pk, err := GetPubKey(viper.GetString(FlagPubKey)) if err != nil { return err } - delegatorAddr := viper.GetString(FlagDelegatorAddress) - delegator, err := commands.ParseActor(delegatorAddr) + bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddress)) if err != nil { return err } + delegator := crypto.Address(bz) delegator = coin.ChainAddr(delegator) - prove := !viper.GetBool(commands.FlagTrustNode) + prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) - height, err := query.GetParsed(key, &bond, query.GetHeight(), prove) + + // parse out the bond + var bond stake.DelegatorBond + cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module + err = cdc.UnmarshalBinary(res, bond) if err != nil { return err } + output, err := json.MarshalIndent(bond, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil - return query.OutputProof(bond, height) + // TODO output with proofs / machine parseable etc. } func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error { - delegatorAddr := viper.GetString(FlagDelegatorAddress) - delegator, err := commands.ParseActor(delegatorAddr) + bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddress)) if err != nil { return err } + delegator := crypto.Address(bz) delegator = coin.ChainAddr(delegator) - prove := !viper.GetBool(commands.FlagTrustNode) + prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) + + // parse out the candidates list var candidates []crypto.PubKey - height, err := query.GetParsed(key, &candidates, query.GetHeight(), prove) + cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module + err = cdc.UnmarshalBinary(res, candidates) if err != nil { return err } + output, err := json.MarshalIndent(candidates, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil - return query.OutputProof(candidates, height) + // TODO output with proofs / machine parseable etc. } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index e14ac8cec1..76bf5753dd 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -4,17 +4,16 @@ import ( "encoding/hex" "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" - txcmd "github.com/cosmos/cosmos-sdk/client/commands/txs" - "github.com/cosmos/cosmos-sdk/modules/coin" - - "github.com/cosmos/gaia/modules/stake" + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" ) // nolint @@ -87,7 +86,7 @@ func init() { } func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { - amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err } @@ -109,7 +108,7 @@ func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { } tx := stake.NewTxDeclareCandidacy(amount, pk, description) - return txcmd.DoTx(tx) + return doTx(tx) } func cmdEditCandidacy(cmd *cobra.Command, args []string) error { @@ -127,11 +126,11 @@ func cmdEditCandidacy(cmd *cobra.Command, args []string) error { } tx := stake.NewTxEditCandidacy(pk, description) - return txcmd.DoTx(tx) + return doTx(tx) } func cmdDelegate(cmd *cobra.Command, args []string) error { - amount, err := coin.ParseCoin(viper.GetString(FlagAmount)) + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) if err != nil { return err } @@ -142,7 +141,7 @@ func cmdDelegate(cmd *cobra.Command, args []string) error { } tx := stake.NewTxDelegate(amount, pk) - return txcmd.DoTx(tx) + return doTx(tx) } func cmdUnbond(cmd *cobra.Command, args []string) error { @@ -151,14 +150,14 @@ func cmdUnbond(cmd *cobra.Command, args []string) error { // check the shares before broadcasting sharesStr := viper.GetString(FlagShares) - var shares rational.Rat + var shares sdk.Rat if sharesStr != "MAX" { var err error - shares, err = rational.NewFromDecimal(sharesStr) + shares, err = sdk.NewRatFromDecimal(sharesStr) if err != nil { return err } - if !shares.GT(rational.Zero) { + if !shares.GT(sdk.ZeroRat) { return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") } } @@ -169,7 +168,7 @@ func cmdUnbond(cmd *cobra.Command, args []string) error { } tx := stake.NewTxUnbond(sharesStr, pk) - return txcmd.DoTx(tx) + return doTx(tx) } // GetPubKey - create the pubkey from a pubkey string @@ -193,3 +192,34 @@ func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { pk = pkEd.Wrap() return } + +//-------------------------------------------------------------------- +// XXX consolidate to client + +func doTx(tx []byte) { + + uri := viper.GetString(client.FlagNode) + if uri == "" { + return errors.New("Must define which node to query with --node") + } + node := client.GetNode(uri) + + result, err := node.BroadcastTxCommit(tx) + if err != nil { + return err + } + + if result.CheckTx.Code != uint32(0) { + fmt.Printf("CheckTx failed: (%d) %s\n", + result.CheckTx.Code, + result.CheckTx.Log) + } + if result.DeliverTx.Code != uint32(0) { + fmt.Printf("DeliverTx failed: (%d) %s\n", + result.DeliverTx.Code, + result.DeliverTx.Log) + } + + fmt.Printf("Committed at block %d. Hash: %s\n", result.Height, result.Hash.String()) + return nil +} diff --git a/x/stake/tx.go b/x/stake/tx.go index 3d5a6c4083..13080bf9bc 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -4,7 +4,6 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix crypto "github.com/tendermint/go-crypto" ) @@ -38,7 +37,7 @@ const ( // BondUpdate - struct for bonding or unbonding transactions type BondUpdate struct { PubKey crypto.PubKey `json:"pub_key"` - Bond coin.Coin `json:"amount"` + Bond sdk.Coin `json:"amount"` } // ValidateBasic - Check for non-empty candidate, and valid coins @@ -46,9 +45,9 @@ func (tx BondUpdate) ValidateBasic() error { if tx.PubKey.Empty() { return errCandidateEmpty } - coins := coin.Coins{tx.Bond} - if !coins.IsValid() { - return coin.ErrInvalidCoins() + coins := sdk.Coins{tx.Bond} + if !sdk.IsValid() { + return sdk.ErrInvalidCoins() } if !coins.IsPositive() { return fmt.Errorf("Amount must be > 0") @@ -63,7 +62,7 @@ type TxDeclareCandidacy struct { } // NewTxDeclareCandidacy - new TxDeclareCandidacy -func NewTxDeclareCandidacy(bond coin.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { +func NewTxDeclareCandidacy(bond sdk.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { return TxDeclareCandidacy{ BondUpdate{ PubKey: pubKey, @@ -110,7 +109,7 @@ func (tx TxEditCandidacy) ValidateBasic() error { type TxDelegate struct{ BondUpdate } // NewTxDelegate - new TxDelegate -func NewTxDelegate(bond coin.Coin, pubKey crypto.PubKey) sdk.Tx { +func NewTxDelegate(bond sdk.Coin, pubKey crypto.PubKey) sdk.Tx { return TxDelegate{BondUpdate{ PubKey: pubKey, Bond: bond, diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go index f6d814589d..4324d9446a 100644 --- a/x/stake/tx_test.go +++ b/x/stake/tx_test.go @@ -6,30 +6,29 @@ import ( "github.com/stretchr/testify/assert" - "github.com/cosmos/cosmos-sdk" - "github.com/cosmos/cosmos-sdk/modules/coin" - crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" + + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")} empty sdk.Actor - coinPos = coin.Coin{"fermion", 1000} - coinZero = coin.Coin{"fermion", 0} - coinNeg = coin.Coin{"fermion", -10000} - coinPosNotAtoms = coin.Coin{"foo", 10000} - coinZeroNotAtoms = coin.Coin{"foo", 0} - coinNegNotAtoms = coin.Coin{"foo", -10000} + coinPos = sdk.Coin{"fermion", 1000} + coinZero = sdk.Coin{"fermion", 0} + coinNeg = sdk.Coin{"fermion", -10000} + coinPosNotAtoms = sdk.Coin{"foo", 10000} + coinZeroNotAtoms = sdk.Coin{"foo", 0} + coinNegNotAtoms = sdk.Coin{"foo", -10000} ) func TestBondUpdateValidateBasic(t *testing.T) { tests := []struct { name string PubKey crypto.PubKey - Bond coin.Coin + Bond sdk.Coin wantErr bool }{ {"basic good", pks[0], coinPos, false}, @@ -49,40 +48,38 @@ func TestBondUpdateValidateBasic(t *testing.T) { } func TestAllAreTx(t *testing.T) { - assert := assert.New(t) // make sure all types construct properly pubKey := newPubKey("1234567890") bondAmt := 1234321 - bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)} // Note that Wrap is only defined on BondUpdate, so when you call it, // you lose all info on the embedding type. Please add Wrap() // method to all the parents txDelegate := NewTxDelegate(bond, pubKey) _, ok := txDelegate.Unwrap().(TxDelegate) - assert.True(ok, "%#v", txDelegate) + assert.True(t, ok, "%#v", txDelegate) txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey) _, ok = txUnbond.Unwrap().(TxUnbond) - assert.True(ok, "%#v", txUnbond) + assert.True(t, ok, "%#v", txUnbond) txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{}) _, ok = txDecl.Unwrap().(TxDeclareCandidacy) - assert.True(ok, "%#v", txDecl) + assert.True(t, ok, "%#v", txDecl) txEditCan := NewTxEditCandidacy(pubKey, Description{}) _, ok = txEditCan.Unwrap().(TxEditCandidacy) - assert.True(ok, "%#v", txEditCan) + assert.True(t, ok, "%#v", txEditCan) } func TestSerializeTx(t *testing.T) { - assert := assert.New(t) // make sure all types construct properly pubKey := newPubKey("1234567890") bondAmt := 1234321 - bond := coin.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)} tests := []struct { tx sdk.Tx @@ -97,8 +94,8 @@ func TestSerializeTx(t *testing.T) { var tx sdk.Tx bs := wire.BinaryBytes(tc.tx) err := wire.ReadBinaryBytes(bs, &tx) - if assert.NoError(err, "%d", i) { - assert.Equal(tc.tx, tx, "%d", i) + if assert.NoError(t, err, "%d", i) { + assert.Equal(t, tc.tx, tx, "%d", i) } } } From b284fd190fb9899dfd827e2484a28d2a1be89e82 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 1 Mar 2018 16:47:07 +0000 Subject: [PATCH 19/54] added gaia working cli from frey --- examples/gaia/README.md | 3 + examples/gaia/gaiacli/client.go | 131 ++++++++++++++++++++++++++++++++ examples/gaia/gaiacli/key.go | 77 +++++++++++++++++++ examples/gaia/gaiacli/main.go | 77 +++++++++++++++++++ examples/gaia/gaiad/main.go | 71 +++++++++++++++++ 5 files changed, 359 insertions(+) create mode 100644 examples/gaia/README.md create mode 100644 examples/gaia/gaiacli/client.go create mode 100644 examples/gaia/gaiacli/key.go create mode 100644 examples/gaia/gaiacli/main.go create mode 100644 examples/gaia/gaiad/main.go diff --git a/examples/gaia/README.md b/examples/gaia/README.md new file mode 100644 index 0000000000..485af236f0 --- /dev/null +++ b/examples/gaia/README.md @@ -0,0 +1,3 @@ +Gaiad is the abci application, which can be run stand-alone, or in-process with tendermint. + +Gaiacli is a client application, which connects to tendermint rpc, and sends transactions and queries the state. It uses light-client proofs to guarantee the results even if it doesn't have 100% trust in the node it connects to. diff --git a/examples/gaia/gaiacli/client.go b/examples/gaia/gaiacli/client.go new file mode 100644 index 0000000000..682a571e6e --- /dev/null +++ b/examples/gaia/gaiacli/client.go @@ -0,0 +1,131 @@ +package main + +import "github.com/spf13/cobra" + +const ( + // these are needed for every init + flagChainID = "chain-id" + flagNode = "node" + + // one of the following should be provided to verify the connection + flagGenesis = "genesis" + flagCommit = "commit" + flagValHash = "validator-set" + + flagSelect = "select" + flagTags = "tag" + flagAny = "any" + + flagBind = "bind" + flagCORS = "cors" + flagTrustNode = "trust-node" + + // this is for signing + flagName = "name" +) + +var ( + statusCmd = &cobra.Command{ + Use: "status", + Short: "Query remote node for status", + RunE: todoNotImplemented, + } +) + +// AddClientCommands returns a sub-tree of all basic client commands +// +// Call AddGetCommand and AddPostCommand to add custom txs and queries +func AddClientCommands(cmd *cobra.Command) { + cmd.AddCommand( + initClientCommand(), + statusCmd, + blockCommand(), + validatorCommand(), + lineBreak, + txSearchCommand(), + txCommand(), + lineBreak, + ) +} + +// GetCommands adds common flags to query commands +func GetCommands(cmds ...*cobra.Command) []*cobra.Command { + for _, c := range cmds { + c.Flags().Bool(flagTrustNode, false, "Don't verify proofs for responses") + } + return cmds +} + +// PostCommands adds common flags for commands to post tx +func PostCommands(cmds ...*cobra.Command) []*cobra.Command { + for _, c := range cmds { + c.Flags().String(flagName, "", "Name of private key with which to sign") + c.Flags().String(flagPassword, "", "Password to use the named private key") + } + return cmds +} + +func initClientCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "init", + Short: "Initialize light client", + RunE: todoNotImplemented, + } + cmd.Flags().StringP(flagChainID, "c", "", "ID of chain we connect to") + cmd.Flags().StringP(flagNode, "n", "tcp://localhost:46657", "Node to connect to") + cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity") + cmd.Flags().String(flagCommit, "", "File with trusted and signed header") + cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") + return cmd +} + +func blockCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "block ", + Short: "Get verified data for a the block at given height", + RunE: todoNotImplemented, + } + cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)") + return cmd +} + +func validatorCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "validatorset ", + Short: "Get the full validator set at given height", + RunE: todoNotImplemented, + } + return cmd +} + +func serveCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "serve", + Short: "Start LCD (light-client daemon), a local REST server", + RunE: todoNotImplemented, + } + // TODO: handle unix sockets also? + cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to") + cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)") + return cmd +} + +func txSearchCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "txs", + Short: "Search for all transactions that match the given tags", + RunE: todoNotImplemented, + } + cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)") + cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL") + return cmd +} + +func txCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tx ", + Short: "Matches this txhash over all committed blocks", + RunE: todoNotImplemented, + } + return cmd +} diff --git a/examples/gaia/gaiacli/key.go b/examples/gaia/gaiacli/key.go new file mode 100644 index 0000000000..b3ffa323ae --- /dev/null +++ b/examples/gaia/gaiacli/key.go @@ -0,0 +1,77 @@ +package main + +import "github.com/spf13/cobra" + +const ( + flagPassword = "password" + flagNewPassword = "new-password" + flagType = "type" + flagSeed = "seed" + flagDryRun = "dry-run" +) + +var ( + listKeysCmd = &cobra.Command{ + Use: "list", + Short: "List all locally availably keys", + RunE: todoNotImplemented, + } + + showKeysCmd = &cobra.Command{ + Use: "show ", + Short: "Show key info for the given name", + RunE: todoNotImplemented, + } +) + +// KeyCommands registers a sub-tree of commands to interact with +// local private key storage. +func KeyCommands() *cobra.Command { + cmd := &cobra.Command{ + Use: "keys", + Short: "Add or view local private keys", + } + cmd.AddCommand( + addKeyCommand(), + listKeysCmd, + showKeysCmd, + lineBreak, + deleteKeyCommand(), + updateKeyCommand(), + ) + return cmd +} + +func addKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "add ", + Short: "Create a new key, or import from seed", + RunE: todoNotImplemented, + } + cmd.Flags().StringP(flagPassword, "p", "", "Password to encrypt private key") + cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)") + cmd.Flags().StringP(flagSeed, "s", "", "Provide seed phrase to recover existing key instead of creating") + cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore") + return cmd +} + +func updateKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "update ", + Short: "Change the password used to protect private key", + RunE: todoNotImplemented, + } + cmd.Flags().StringP(flagPassword, "p", "", "Current password to decrypt key") + cmd.Flags().String(flagNewPassword, "", "New password to use to protect key") + return cmd +} + +func deleteKeyCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete ", + Short: "Delete the given key", + RunE: todoNotImplemented, + } + cmd.Flags().StringP(flagPassword, "p", "", "Password of existing key to delete") + return cmd +} diff --git a/examples/gaia/gaiacli/main.go b/examples/gaia/gaiacli/main.go new file mode 100644 index 0000000000..dce125acbb --- /dev/null +++ b/examples/gaia/gaiacli/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "errors" + "os" + + "github.com/spf13/cobra" + + "github.com/tendermint/tmlibs/cli" + + "github.com/cosmos/cosmos-sdk/version" +) + +const ( + flagTo = "to" + flagAmount = "amount" + flagFee = "fee" +) + +// gaiacliCmd is the entry point for this binary +var ( + gaiacliCmd = &cobra.Command{ + Use: "gaiacli", + Short: "Gaia light-client", + } + + lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}} + + getAccountCmd = &cobra.Command{ + Use: "account
", + Short: "Query account balance", + RunE: todoNotImplemented, + } +) + +func todoNotImplemented(_ *cobra.Command, _ []string) error { + return errors.New("TODO: Command not yet implemented") +} + +func postSendCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "send", + Short: "Create and sign a send tx", + RunE: todoNotImplemented, + } + cmd.Flags().String(flagTo, "", "Address to send coins") + cmd.Flags().String(flagAmount, "", "Amount of coins to send") + cmd.Flags().String(flagFee, "", "Fee to pay along with transaction") + return cmd +} + +func main() { + // disable sorting + cobra.EnableCommandSorting = false + + // generic client commands + AddClientCommands(gaiacliCmd) + // query commands (custom to binary) + gaiacliCmd.AddCommand( + GetCommands(getAccountCmd)...) + // post tx commands (custom to binary) + gaiacliCmd.AddCommand( + PostCommands(postSendCommand())...) + + // add proxy, version and key info + gaiacliCmd.AddCommand( + lineBreak, + serveCommand(), + KeyCommands(), + lineBreak, + version.VersionCmd, + ) + + // prepare and add flags + executor := cli.PrepareBaseCmd(gaiacliCmd, "GA", os.ExpandEnv("$HOME/.gaiacli")) + executor.Execute() +} diff --git a/examples/gaia/gaiad/main.go b/examples/gaia/gaiad/main.go new file mode 100644 index 0000000000..0c4c49eec7 --- /dev/null +++ b/examples/gaia/gaiad/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/spf13/cobra" + + abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tmlibs/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/version" +) + +// gaiadCmd is the entry point for this binary +var ( + gaiadCmd = &cobra.Command{ + Use: "gaiad", + Short: "Gaia Daemon (server)", + } +) + +// defaultOptions sets up the app_options for the +// default genesis file +func defaultOptions(args []string) (json.RawMessage, error) { + addr, secret, err := server.GenerateCoinKey() + if err != nil { + return nil, err + } + fmt.Println("Secret phrase to access coins:") + fmt.Println(secret) + + opts := fmt.Sprintf(`{ + "accounts": [{ + "address": "%s", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + }] + }`, addr) + return json.RawMessage(opts), nil +} + +func generateApp(rootDir string, logger log.Logger) (abci.Application, error) { + // TODO: set this to something real + app := new(baseapp.BaseApp) + return app, nil +} + +func main() { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). + With("module", "main") + + gaiadCmd.AddCommand( + server.InitCmd(defaultOptions, logger), + server.StartCmd(generateApp, logger), + server.UnsafeResetAllCmd(logger), + version.VersionCmd, + ) + + // prepare and add flags + executor := cli.PrepareBaseCmd(gaiadCmd, "GA", os.ExpandEnv("$HOME/.gaiad")) + executor.Execute() +} From d6b4c80465ecbeea902bb391fc4515eac5ea8e63 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 26 Feb 2018 00:47:59 +0000 Subject: [PATCH 20/54] wip next steps gaia tx to msg type --- x/stake/handler.go | 84 ++++++++++++++++++++++++---------------------- x/stake/wire.go | 12 +++++++ 2 files changed, 55 insertions(+), 41 deletions(-) create mode 100644 x/stake/wire.go diff --git a/x/stake/handler.go b/x/stake/handler.go index e03a31b043..ef2f08db70 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,16 +1,17 @@ package stake import ( + "errors" "fmt" + "reflect" "strconv" "github.com/spf13/viper" - "github.com/tendermint/tmlibs/log" + crypto "github.com/tendermint/go-crypto" "github.com/tendermint/tmlibs/rational" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix ) // nolint @@ -32,46 +33,44 @@ type delegatedProofOfStake interface { } type coinSend interface { - transferFn(sender, receiver sdk.Actor, coins coin.Coins) error + transferFn(sender, receiver crypto.Address, coins sdk.Coins) error } //_______________________________________________________________________ // Handler - the transaction processing handler -type Handler struct { -} +type Handler struct{} -// NewHandler returns a new Handler with the default Params -func NewHandler() Handler { - return Handler{} -} +// Handle all "bank" type messages. +func NewHandler() sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { -// Name - return stake namespace -func (Handler) Name() string { - return stakingModuleName + switch msg := msg.(type) { + case SendMsg: + return handleSendMsg(ctx, ck, msg) + case IssueMsg: + return handleIssueMsg(ctx, ck, msg) + default: + errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() + return sdk.ErrUnknownRequest(errMsg).Result() + } + } } // InitState - set genesis parameters for staking -func (h Handler) InitState(l log.Logger, store types.KVStore, - module, key, value string, cb sdk.InitStater) (log string, err error) { - return "", h.initState(module, key, value, store) +func (h Handler) InitState(key, value string, store sdk.KVStore) error { + return h.initState(key, value, store) } // separated for testing -func (Handler) initState(module, key, value string, store types.KVStore) error { - if module != stakingModuleName { - return sdk.ErrUnknownModule(module) - } +func (Handler) initState(key, value string, store sdk.KVStore) error { params := loadParams(store) switch key { case "allowed_bond_denom": params.AllowedBondDenom = value - case "max_vals", - "gas_bond", - "gas_unbond": + case "max_vals", "gas_bond", "gas_unbond": - // TODO: enforce non-negative integers in input i, err := strconv.Atoi(value) if err != nil { return fmt.Errorf("input must be integer, Error: %v", err.Error()) @@ -79,6 +78,9 @@ func (Handler) initState(module, key, value string, store types.KVStore) error { switch key { case "max_vals": + if i < 0 { + return errors.New("cannot designate negative max validators") + } params.MaxVals = uint16(i) case "gas_bond": params.GasDelegate = int64(i) @@ -94,7 +96,7 @@ func (Handler) initState(module, key, value string, store types.KVStore) error { } // CheckTx checks if the tx is properly structured -func (h Handler) CheckTx(ctx sdk.Context, store types.KVStore, +func (h Handler) CheckTx(ctx sdk.Context, store sdk.KVStore, tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) { err = tx.ValidateBasic() @@ -136,7 +138,7 @@ func (h Handler) CheckTx(ctx sdk.Context, store types.KVStore, } // DeliverTx executes the tx if valid -func (h Handler) DeliverTx(ctx sdk.Context, store types.KVStore, +func (h Handler) DeliverTx(ctx sdk.Context, store sdk.KVStore, tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) { // TODO: remove redundancy @@ -190,7 +192,7 @@ func (h Handler) DeliverTx(ctx sdk.Context, store types.KVStore, } // get the sender from the ctx and ensure it matches the tx pubkey -func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) { +func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) { senders := ctx.GetPermissions("", auth.NameSigs) if len(senders) != 1 { return sender, ErrMissingSignature() @@ -201,15 +203,15 @@ func getTxSender(ctx sdk.Context) (sender sdk.Actor, err error) { //_______________________________________________________________________ type coinSender struct { - store types.KVStore + store sdk.KVStore dispatch sdk.Deliver ctx sdk.Context } var _ coinSend = coinSender{} // enforce interface at compile time -func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { - send := coin.NewSendOneTx(sender, receiver, coins) +func (c coinSender) transferFn(sender, receiver crypto.Address, coins sdk.Coins) error { + send := sdk.NewSendOneTx(sender, receiver, coins) // If the deduction fails (too high), abort the command _, err := c.dispatch.DeliverTx(c.ctx, c.store, send) @@ -219,8 +221,8 @@ func (c coinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) err //_____________________________________________________________________ type check struct { - store types.KVStore - sender sdk.Actor + store sdk.KVStore + sender crypto.Address } var _ delegatedProofOfStake = check{} // enforce interface at compile time @@ -285,7 +287,7 @@ func (c check) unbond(tx TxUnbond) error { return nil } -func checkDenom(tx BondUpdate, store types.KVStore) error { +func checkDenom(tx BondUpdate, store sdk.KVStore) error { if tx.Bond.Denom != loadParams(store).AllowedBondDenom { return fmt.Errorf("Invalid coin denomination") } @@ -295,14 +297,14 @@ func checkDenom(tx BondUpdate, store types.KVStore) error { //_____________________________________________________________________ type deliver struct { - store types.KVStore - sender sdk.Actor + store sdk.KVStore + sender crypto.Address params Params gs *GlobalState transfer transferFn } -type transferFn func(sender, receiver sdk.Actor, coins coin.Coins) error +type transferFn func(sender, receiver crypto.Address, coins sdk.Coins) error var _ delegatedProofOfStake = deliver{} // enforce interface at compile time @@ -320,7 +322,7 @@ func (d deliver) bondedToUnbondedPool(candidate *Candidate) error { candidate.Status = Unbonded return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded, - coin.Coins{{d.params.AllowedBondDenom, tokens}}) + sdk.Coins{{d.params.AllowedBondDenom, tokens}}) } // move a candidates asset pool from unbonded to bonded pool @@ -332,7 +334,7 @@ func (d deliver) unbondedToBondedPool(candidate *Candidate) error { candidate.Status = Bonded return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded, - coin.Coins{{d.params.AllowedBondDenom, tokens}}) + sdk.Coins{{d.params.AllowedBondDenom, tokens}}) } //_____________________________________________________________________ @@ -399,7 +401,7 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro return ErrBondNotNominated() } - var poolAccount sdk.Actor + var poolAccount crypto.Address if candidate.Status == Bonded { poolAccount = d.params.HoldBonded } else { @@ -408,7 +410,7 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro // TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK // Move coins from the delegator account to the bonded pool account - err := d.transfer(d.sender, poolAccount, coin.Coins{tx.Bond}) + err := d.transfer(d.sender, poolAccount, sdk.Coins{tx.Bond}) if err != nil { return err } @@ -479,7 +481,7 @@ func (d deliver) unbond(tx TxUnbond) error { } // transfer coins back to account - var poolAccount sdk.Actor + var poolAccount crypto.Address if candidate.Status == Bonded { poolAccount = d.params.HoldBonded } else { @@ -488,7 +490,7 @@ func (d deliver) unbond(tx TxUnbond) error { returnCoins := candidate.removeShares(shares, d.gs) err := d.transfer(poolAccount, d.sender, - coin.Coins{{d.params.AllowedBondDenom, returnCoins}}) + sdk.Coins{{d.params.AllowedBondDenom, returnCoins}}) if err != nil { return err } diff --git a/x/stake/wire.go b/x/stake/wire.go new file mode 100644 index 0000000000..5473a3838e --- /dev/null +++ b/x/stake/wire.go @@ -0,0 +1,12 @@ +package bank + +import ( + "github.com/tendermint/go-wire" +) + +// XXX complete +func RegisterWire(cdc *wire.Codec) { + // TODO include option to always include prefix bytes. + //cdc.RegisterConcrete(SendMsg{}, "cosmos-sdk/SendMsg", nil) + //cdc.RegisterConcrete(IssueMsg{}, "cosmos-sdk/IssueMsg", nil) +} From e8cea08978a399d37f5102285cd2f5f717d4090e Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Feb 2018 02:07:20 +0000 Subject: [PATCH 21/54] working gaia port --- x/stake/handler.go | 375 +++++++++------------- x/stake/{store.go => mapper.go} | 0 x/stake/{store_test.go => mapper_test.go} | 0 x/stake/wire.go | 2 +- 4 files changed, 145 insertions(+), 232 deletions(-) rename x/stake/{store.go => mapper.go} (100%) rename x/stake/{store_test.go => mapper_test.go} (100%) diff --git a/x/stake/handler.go b/x/stake/handler.go index ef2f08db70..3ade6a927c 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -3,25 +3,16 @@ package stake import ( "errors" "fmt" - "reflect" "strconv" "github.com/spf13/viper" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" ) -// nolint -const stakingModuleName = "stake" - -// Name is the name of the modules. -func Name() string { - return stakingModuleName -} - //_______________________________________________________________________ // DelegatedProofOfStake - interface to enforce delegation stake @@ -38,32 +29,8 @@ type coinSend interface { //_______________________________________________________________________ -// Handler - the transaction processing handler -type Handler struct{} - -// Handle all "bank" type messages. -func NewHandler() sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - - switch msg := msg.(type) { - case SendMsg: - return handleSendMsg(ctx, ck, msg) - case IssueMsg: - return handleIssueMsg(ctx, ck, msg) - default: - errMsg := "Unrecognized bank Msg type: " + reflect.TypeOf(msg).Name() - return sdk.ErrUnknownRequest(errMsg).Result() - } - } -} - -// InitState - set genesis parameters for staking -func (h Handler) InitState(key, value string, store sdk.KVStore) error { - return h.initState(key, value, store) -} - // separated for testing -func (Handler) initState(key, value string, store sdk.KVStore) error { +func InitState(key, value string, store sdk.KVStore) error { params := loadParams(store) switch key { @@ -95,100 +62,81 @@ func (Handler) initState(key, value string, store sdk.KVStore) error { return nil } -// CheckTx checks if the tx is properly structured -func (h Handler) CheckTx(ctx sdk.Context, store sdk.KVStore, - tx sdk.Tx, _ sdk.Checker) (res sdk.CheckResult, err error) { +//_______________________________________________________________________ - err = tx.ValidateBasic() - if err != nil { - return res, err - } +func NewHandler(ck bank.CoinKeeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - // get the sender - sender, err := getTxSender(ctx) - if err != nil { - return res, err - } - - params := loadParams(store) - - // create the new checker object to - checker := check{ - store: store, - sender: sender, - } - - // return the fee for each tx type - switch txInner := tx.Unwrap().(type) { - case TxDeclareCandidacy: - return sdk.NewCheck(params.GasDeclareCandidacy, ""), - checker.declareCandidacy(txInner) - case TxEditCandidacy: - return sdk.NewCheck(params.GasEditCandidacy, ""), - checker.editCandidacy(txInner) - case TxDelegate: - return sdk.NewCheck(params.GasDelegate, ""), - checker.delegate(txInner) - case TxUnbond: - return sdk.NewCheck(params.GasUnbond, ""), - checker.unbond(txInner) - } - - return res, sdk.ErrUnknownTxType(tx) -} - -// DeliverTx executes the tx if valid -func (h Handler) DeliverTx(ctx sdk.Context, store sdk.KVStore, - tx sdk.Tx, dispatch sdk.Deliver) (res sdk.DeliverResult, err error) { - - // TODO: remove redundancy - // also we don't need to check the res - gas is already deducted in sdk - _, err = h.CheckTx(ctx, store, tx, nil) - if err != nil { - return - } - - sender, err := getTxSender(ctx) - if err != nil { - return - } - - params := loadParams(store) - deliverer := deliver{ - store: store, - sender: sender, - params: params, - transfer: coinSender{ - store: store, - dispatch: dispatch, - ctx: ctx, - }.transferFn, - } - - // Run the transaction - switch _tx := tx.Unwrap().(type) { - case TxDeclareCandidacy: - res.GasUsed = params.GasDeclareCandidacy - return res, deliverer.declareCandidacy(_tx) - case TxEditCandidacy: - res.GasUsed = params.GasEditCandidacy - return res, deliverer.editCandidacy(_tx) - case TxDelegate: - res.GasUsed = params.GasDelegate - return res, deliverer.delegate(_tx) - case TxUnbond: - //context with hold account permissions params := loadParams(store) - res.GasUsed = params.GasUnbond - ctx2 := ctx.WithPermissions(params.HoldBonded) - deliverer.transfer = coinSender{ - store: store, - dispatch: dispatch, - ctx: ctx2, - }.transferFn - return res, deliverer.unbond(_tx) + params := loadGlobalState(store) + + if ctx.IsCheckTx() { + + err = tx.ValidateBasic() + if err != nil { + return res, err + } + + res := sdk.Result{} + + // return the fee for each tx type + switch txInner := tx.Unwrap().(type) { + case TxDeclareCandidacy: + return sdk.NewCheck(params.GasDeclareCandidacy, "") + case TxEditCandidacy: + return sdk.NewCheck(params.GasEditCandidacy, "") + case TxDelegate: + return sdk.NewCheck(params.GasDelegate, "") + case TxUnbond: + return sdk.NewCheck(params.GasUnbond, "") + } + + // TODO: add some tags so we can search it! + return sdk.ErrUnknownTxType(tx) + //return sdk.Result{} // TODO + } + + // TODO: remove redundancy + // also we don't need to check the res - gas is already deducted in sdk + _, err = h.CheckTx(ctx, store, tx, nil) + if err != nil { + return + } + + sender, err := getTxSender(ctx) + if err != nil { + return + } + + params := loadParams(store) + deliverer := deliver{ + store: store, + sender: sender, + params: params, + ck: ck, + gs: gs, + } + + // Run the transaction + switch _tx := tx.Unwrap().(type) { + case TxDeclareCandidacy: + res.GasUsed = params.GasDeclareCandidacy + return res, deliverer.declareCandidacy(_tx) + case TxEditCandidacy: + res.GasUsed = params.GasEditCandidacy + return res, deliverer.editCandidacy(_tx) + case TxDelegate: + res.GasUsed = params.GasDelegate + return res, deliverer.delegate(_tx) + case TxUnbond: + //context with hold account permissions + params := loadParams(store) + res.GasUsed = params.GasUnbond + //ctx2 := ctx.WithPermissions(params.HoldBonded) //TODO remove this line if non-permissioned ctx works + return res, deliverer.unbond(_tx) + } + return } - return } // get the sender from the ctx and ensure it matches the tx pubkey @@ -200,117 +148,20 @@ func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) { return senders[0], nil } -//_______________________________________________________________________ - -type coinSender struct { - store sdk.KVStore - dispatch sdk.Deliver - ctx sdk.Context -} - -var _ coinSend = coinSender{} // enforce interface at compile time - -func (c coinSender) transferFn(sender, receiver crypto.Address, coins sdk.Coins) error { - send := sdk.NewSendOneTx(sender, receiver, coins) - - // If the deduction fails (too high), abort the command - _, err := c.dispatch.DeliverTx(c.ctx, c.store, send) - return err -} - -//_____________________________________________________________________ - -type check struct { - store sdk.KVStore - sender crypto.Address -} - -var _ delegatedProofOfStake = check{} // enforce interface at compile time - -func (c check) declareCandidacy(tx TxDeclareCandidacy) error { - - // check to see if the pubkey or sender has been registered before - candidate := loadCandidate(c.store, tx.PubKey) - if candidate != nil { - return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ - " PubKey %v already registered with %v candidate address", - candidate.PubKey, candidate.Owner) - } - - return checkDenom(tx.BondUpdate, c.store) -} - -func (c check) editCandidacy(tx TxEditCandidacy) error { - - // candidate must already be registered - candidate := loadCandidate(c.store, tx.PubKey) - if candidate == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) - } - return nil -} - -func (c check) delegate(tx TxDelegate) error { - - candidate := loadCandidate(c.store, tx.PubKey) - if candidate == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) - } - return checkDenom(tx.BondUpdate, c.store) -} - -func (c check) unbond(tx TxUnbond) error { - - // check if bond has any shares in it unbond - bond := loadDelegatorBond(c.store, c.sender, tx.PubKey) - sharesStr := viper.GetString(tx.Shares) - if bond.Shares.LT(rational.Zero) { // bond shares < tx shares - return fmt.Errorf("no shares in account to unbond") - } - - // if shares set to maximum shares then we're good - if sharesStr == "MAX" { - return nil - } - - // test getting rational number from decimal provided - shares, err := rational.NewFromDecimal(sharesStr) - if err != nil { - return err - } - - // test that there are enough shares to unbond - if bond.Shares.LT(shares) { - return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", - bond.Shares, tx.Shares) - } - return nil -} - -func checkDenom(tx BondUpdate, store sdk.KVStore) error { - if tx.Bond.Denom != loadParams(store).AllowedBondDenom { - return fmt.Errorf("Invalid coin denomination") - } - return nil -} - //_____________________________________________________________________ type deliver struct { - store sdk.KVStore - sender crypto.Address - params Params - gs *GlobalState - transfer transferFn + store sdk.KVStore + sender crypto.Address + params Params + ck bank.CoinKeeper + gs *GlobalState } -type transferFn func(sender, receiver crypto.Address, coins sdk.Coins) error - var _ delegatedProofOfStake = deliver{} // enforce interface at compile time //_____________________________________________________________________ -// deliver helper functions - +// helper functions // TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK // move a candidates asset pool from bonded to unbonded pool @@ -337,12 +188,33 @@ func (d deliver) unbondedToBondedPool(candidate *Candidate) error { sdk.Coins{{d.params.AllowedBondDenom, tokens}}) } +// return an error if the bonds coins are incorrect +func checkDenom(tx BondUpdate, store sdk.KVStore) error { + if tx.Bond.Denom != loadParams(store).AllowedBondDenom { + return fmt.Errorf("Invalid coin denomination") + } + return nil +} + //_____________________________________________________________________ // These functions assume everything has been authenticated, // now we just perform action and save func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { + // check to see if the pubkey or sender has been registered before + candidate := loadCandidate(d.store, tx.PubKey) + if candidate != nil { + return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ + " PubKey %v already registered with %v candidate address", + candidate.PubKey, candidate.Owner) + } + err := checkDenom(tx.BondUpdate, d.store) + if err != nil { + return err + } + // XXX end of old check tx + // create and save the empty candidate bond := loadCandidate(d.store, tx.PubKey) if bond != nil { @@ -359,6 +231,13 @@ func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { func (d deliver) editCandidacy(tx TxEditCandidacy) error { + // candidate must already be registered + candidate := loadCandidate(d.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + // XXX end of old check tx + // Get the pubKey bond account candidate := loadCandidate(d.store, tx.PubKey) if candidate == nil { @@ -387,6 +266,17 @@ func (d deliver) editCandidacy(tx TxEditCandidacy) error { } func (d deliver) delegate(tx TxDelegate) error { + + candidate := loadCandidate(c.store, tx.PubKey) + if candidate == nil { // does PubKey exist + return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + } + err := checkDenom(tx.BondUpdate, c.store) + if err != nil { + return err + } + // end of old check tx + // Get the pubKey bond account candidate := loadCandidate(d.store, tx.PubKey) if candidate == nil { @@ -420,7 +310,7 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro if bond == nil { bond = &DelegatorBond{ PubKey: tx.PubKey, - Shares: rational.Zero, + Shares: sdk.ZeroRat, } } @@ -434,6 +324,29 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro func (d deliver) unbond(tx TxUnbond) error { + // check if bond has any shares in it unbond + bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + sharesStr := viper.GetString(tx.Shares) + if bond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares + return fmt.Errorf("no shares in account to unbond") + } + + // if shares set to special case Max then we're good + if sharesStr != "MAX" { + // test getting rational number from decimal provided + shares, err := sdk.NewRatFromDecimal(sharesStr) + if err != nil { + return err + } + + // test that there are enough shares to unbond + if bond.Shares.LT(shares) { + return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", + bond.Shares, tx.Shares) + } + } + // XXX end of old checkTx + // get delegator bond bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) if bond == nil { @@ -441,12 +354,12 @@ func (d deliver) unbond(tx TxUnbond) error { } // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - var shares rational.Rat + var shares sdk.Rat if tx.Shares == "MAX" { shares = bond.Shares } else { var err error - shares, err = rational.NewFromDecimal(tx.Shares) + shares, err = sdk.NewRatFromDecimal(tx.Shares) if err != nil { return err } diff --git a/x/stake/store.go b/x/stake/mapper.go similarity index 100% rename from x/stake/store.go rename to x/stake/mapper.go diff --git a/x/stake/store_test.go b/x/stake/mapper_test.go similarity index 100% rename from x/stake/store_test.go rename to x/stake/mapper_test.go diff --git a/x/stake/wire.go b/x/stake/wire.go index 5473a3838e..a1b675a186 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -1,4 +1,4 @@ -package bank +package stake import ( "github.com/tendermint/go-wire" From 59b10d33c16bffef59f790a4aeee85188ab0316c Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Feb 2018 16:27:07 +0000 Subject: [PATCH 22/54] porting handler, refactoring mapper/types --- x/stake/handler.go | 197 ++++++++++----------- x/stake/{mapper.go => keeper.go} | 147 +++++++-------- x/stake/{mapper_test.go => keeper_test.go} | 0 x/stake/types.go | 8 +- 4 files changed, 170 insertions(+), 182 deletions(-) rename x/stake/{mapper.go => keeper.go} (66%) rename x/stake/{mapper_test.go => keeper_test.go} (100%) diff --git a/x/stake/handler.go b/x/stake/handler.go index 3ade6a927c..7865fe0762 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -13,24 +13,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) -//_______________________________________________________________________ - -// DelegatedProofOfStake - interface to enforce delegation stake -type delegatedProofOfStake interface { - declareCandidacy(TxDeclareCandidacy) error - editCandidacy(TxEditCandidacy) error - delegate(TxDelegate) error - unbond(TxUnbond) error -} - -type coinSend interface { - transferFn(sender, receiver crypto.Address, coins sdk.Coins) error -} - -//_______________________________________________________________________ - // separated for testing -func InitState(key, value string, store sdk.KVStore) error { +func InitState(ctx sdk.Context, key, value string) error { params := loadParams(store) switch key { @@ -64,22 +48,19 @@ func InitState(key, value string, store sdk.KVStore) error { //_______________________________________________________________________ -func NewHandler(ck bank.CoinKeeper) sdk.Handler { +func NewHandler(stakeKey sdk.StoreKey, ck bank.CoinKeeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - params := loadParams(store) - params := loadGlobalState(store) + res := sdk.Result{} + err := msg.ValidateBasic() + if err != nil { + return res, err + } + + // return the fee for each tx type if ctx.IsCheckTx() { - - err = tx.ValidateBasic() - if err != nil { - return res, err - } - - res := sdk.Result{} - - // return the fee for each tx type + // XXX: add some tags so we can search it! switch txInner := tx.Unwrap().(type) { case TxDeclareCandidacy: return sdk.NewCheck(params.GasDeclareCandidacy, "") @@ -89,11 +70,9 @@ func NewHandler(ck bank.CoinKeeper) sdk.Handler { return sdk.NewCheck(params.GasDelegate, "") case TxUnbond: return sdk.NewCheck(params.GasUnbond, "") + default: + return sdk.ErrUnknownTxType(tx) } - - // TODO: add some tags so we can search it! - return sdk.ErrUnknownTxType(tx) - //return sdk.Result{} // TODO } // TODO: remove redundancy @@ -108,32 +87,26 @@ func NewHandler(ck bank.CoinKeeper) sdk.Handler { return } - params := loadParams(store) - deliverer := deliver{ - store: store, - sender: sender, - params: params, - ck: ck, - gs: gs, - } + keeper := NewKeeper(ctx, stakeKey) + transact := NewTransact(ctx, ck) // Run the transaction switch _tx := tx.Unwrap().(type) { case TxDeclareCandidacy: res.GasUsed = params.GasDeclareCandidacy - return res, deliverer.declareCandidacy(_tx) + return res, transact.declareCandidacy(_tx) case TxEditCandidacy: res.GasUsed = params.GasEditCandidacy - return res, deliverer.editCandidacy(_tx) + return res, transact.editCandidacy(_tx) case TxDelegate: res.GasUsed = params.GasDelegate - return res, deliverer.delegate(_tx) + return res, transact.delegate(_tx) case TxUnbond: //context with hold account permissions params := loadParams(store) res.GasUsed = params.GasUnbond //ctx2 := ctx.WithPermissions(params.HoldBonded) //TODO remove this line if non-permissioned ctx works - return res, deliverer.unbond(_tx) + return res, transact.unbond(_tx) } return } @@ -150,47 +123,57 @@ func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) { //_____________________________________________________________________ -type deliver struct { - store sdk.KVStore - sender crypto.Address - params Params - ck bank.CoinKeeper - gs *GlobalState +// common fields to all transactions +type transact struct { + sender crypto.Address + keeper Keeper + coinKeeper bank.CoinKeeper + params Params + gs *GlobalState } -var _ delegatedProofOfStake = deliver{} // enforce interface at compile time +// XXX move keeper creation to application? +func newTransact(ctx sdk.Context, keeper Keeper, ck bank.CoinKeeper) transact { + return transact{ + sender: sender, + keeper: keeper, + coinKeeper: ck, + params: keeper.loadParams(), + gs: keeper.loadGlobalState(), + } +} //_____________________________________________________________________ // helper functions // TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK // move a candidates asset pool from bonded to unbonded pool -func (d deliver) bondedToUnbondedPool(candidate *Candidate) error { +func (tr transact) bondedToUnbondedPool(candidate *Candidate) error { // replace bonded shares with unbonded shares - tokens := d.gs.removeSharesBonded(candidate.Assets) - candidate.Assets = d.gs.addTokensUnbonded(tokens) + tokens := tr.gs.removeSharesBonded(candidate.Assets) + candidate.Assets = tr.gs.addTokensUnbonded(tokens) candidate.Status = Unbonded - return d.transfer(d.params.HoldBonded, d.params.HoldUnbonded, - sdk.Coins{{d.params.AllowedBondDenom, tokens}}) + return tr.transfer(tr.params.HoldBonded, tr.params.HoldUnbonded, + sdk.Coins{{tr.params.AllowedBondDenom, tokens}}) } // move a candidates asset pool from unbonded to bonded pool -func (d deliver) unbondedToBondedPool(candidate *Candidate) error { +func (tr transact) unbondedToBondedPool(candidate *Candidate) error { // replace bonded shares with unbonded shares - tokens := d.gs.removeSharesUnbonded(candidate.Assets) - candidate.Assets = d.gs.addTokensBonded(tokens) + tokens := tr.gs.removeSharesUnbonded(candidate.Assets) + candidate.Assets = tr.gs.addTokensBonded(tokens) candidate.Status = Bonded - return d.transfer(d.params.HoldUnbonded, d.params.HoldBonded, - sdk.Coins{{d.params.AllowedBondDenom, tokens}}) + return tr.transfer(tr.params.HoldUnbonded, tr.params.HoldBonded, + sdk.Coins{{tr.params.AllowedBondDenom, tokens}}) } // return an error if the bonds coins are incorrect -func checkDenom(tx BondUpdate, store sdk.KVStore) error { - if tx.Bond.Denom != loadParams(store).AllowedBondDenom { +func checkDenom(keeper Keeper, tx BondUpdate) error { + if tx.Bond.Denom != keeper.loadParams().AllowedBondDenom { return fmt.Errorf("Invalid coin denomination") } return nil @@ -200,46 +183,46 @@ func checkDenom(tx BondUpdate, store sdk.KVStore) error { // These functions assume everything has been authenticated, // now we just perform action and save -func (d deliver) declareCandidacy(tx TxDeclareCandidacy) error { +func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error { // check to see if the pubkey or sender has been registered before - candidate := loadCandidate(d.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate != nil { return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ " PubKey %v already registered with %v candidate address", candidate.PubKey, candidate.Owner) } - err := checkDenom(tx.BondUpdate, d.store) + err := checkDenom(tx.BondUpdate, tr.keeper) if err != nil { return err } // XXX end of old check tx // create and save the empty candidate - bond := loadCandidate(d.store, tx.PubKey) + bond := tr.keeper.loadCandidate(tx.PubKey) if bond != nil { return ErrCandidateExistsAddr() } - candidate := NewCandidate(tx.PubKey, d.sender, tx.Description) - saveCandidate(d.store, candidate) + candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description) + tr.keeper.saveCandidate(candidate) - // move coins from the d.sender account to a (self-bond) delegator account + // move coins from the tr.sender account to a (self-bond) delegator account // the candidate account and global shares are updated within here txDelegate := TxDelegate{tx.BondUpdate} - return d.delegateWithCandidate(txDelegate, candidate) + return tr.delegateWithCandidate(txDelegate, candidate) } -func (d deliver) editCandidacy(tx TxEditCandidacy) error { +func (tr transact) editCandidacy(tx TxEditCandidacy) error { // candidate must already be registered - candidate := loadCandidate(d.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate == nil { // does PubKey exist return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) } // XXX end of old check tx // Get the pubKey bond account - candidate := loadCandidate(d.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate == nil { return ErrBondNotNominated() } @@ -261,31 +244,31 @@ func (d deliver) editCandidacy(tx TxEditCandidacy) error { candidate.Description.Details = tx.Description.Details } - saveCandidate(d.store, candidate) + tr.keeper.saveCandidate(candidate) return nil } -func (d deliver) delegate(tx TxDelegate) error { +func (tr transact) delegate(tx TxDelegate) error { - candidate := loadCandidate(c.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate == nil { // does PubKey exist return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) } - err := checkDenom(tx.BondUpdate, c.store) + err := checkDenom(tx.BondUpdate, tr.keeper) if err != nil { return err } // end of old check tx // Get the pubKey bond account - candidate := loadCandidate(d.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate == nil { return ErrBondNotNominated() } - return d.delegateWithCandidate(tx, candidate) + return tr.delegateWithCandidate(tx, candidate) } -func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { +func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { if candidate.Status == Revoked { //candidate has been withdrawn return ErrBondNotNominated() @@ -293,20 +276,20 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro var poolAccount crypto.Address if candidate.Status == Bonded { - poolAccount = d.params.HoldBonded + poolAccount = tr.params.HoldBonded } else { - poolAccount = d.params.HoldUnbonded + poolAccount = tr.params.HoldUnbonded } - // TODO maybe refactor into GlobalState.addBondedTokens(), maybe with new SDK + // XXX refactor all steps like this into GlobalState.addBondedTokens() // Move coins from the delegator account to the bonded pool account - err := d.transfer(d.sender, poolAccount, sdk.Coins{tx.Bond}) + err := tr.transfer(tr.sender, poolAccount, sdk.Coins{tx.Bond}) if err != nil { return err } // Get or create the delegator bond - bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) if bond == nil { bond = &DelegatorBond{ PubKey: tx.PubKey, @@ -315,17 +298,17 @@ func (d deliver) delegateWithCandidate(tx TxDelegate, candidate *Candidate) erro } // Account new shares, save - bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, d.gs)) - saveCandidate(d.store, candidate) - saveDelegatorBond(d.store, d.sender, bond) - saveGlobalState(d.store, d.gs) + bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, tr.gs)) + tr.keeper.saveCandidate(candidate) + tr.keeper.saveDelegatorBond(tr.sender, bond) + tr.keeper.saveGlobalState(tr.gs) return nil } -func (d deliver) unbond(tx TxUnbond) error { +func (tr transact) unbond(tx TxUnbond) error { // check if bond has any shares in it unbond - bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) sharesStr := viper.GetString(tx.Shares) if bond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares return fmt.Errorf("no shares in account to unbond") @@ -348,7 +331,7 @@ func (d deliver) unbond(tx TxUnbond) error { // XXX end of old checkTx // get delegator bond - bond := loadDelegatorBond(d.store, d.sender, tx.PubKey) + bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) if bond == nil { return ErrNoDelegatorForAddress() } @@ -372,7 +355,7 @@ func (d deliver) unbond(tx TxUnbond) error { bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate := loadCandidate(d.store, tx.PubKey) + candidate := tr.keeper.loadCandidate(tx.PubKey) if candidate == nil { return ErrNoCandidateForAddress() } @@ -382,28 +365,28 @@ func (d deliver) unbond(tx TxUnbond) error { // if the bond is the owner of the candidate then // trigger a revoke candidacy - if d.sender.Equals(candidate.Owner) && + if tr.sender.Equals(candidate.Owner) && candidate.Status != Revoked { revokeCandidacy = true } // remove the bond - removeDelegatorBond(d.store, d.sender, tx.PubKey) + tr.keeper.removeDelegatorBond(tr.sender, tx.PubKey) } else { - saveDelegatorBond(d.store, d.sender, bond) + tr.keeper.saveDelegatorBond(tr.sender, bond) } // transfer coins back to account var poolAccount crypto.Address if candidate.Status == Bonded { - poolAccount = d.params.HoldBonded + poolAccount = tr.params.HoldBonded } else { - poolAccount = d.params.HoldUnbonded + poolAccount = tr.params.HoldUnbonded } - returnCoins := candidate.removeShares(shares, d.gs) - err := d.transfer(poolAccount, d.sender, - sdk.Coins{{d.params.AllowedBondDenom, returnCoins}}) + returnCoins := candidate.removeShares(shares, tr.gs) + err := tr.transfer(poolAccount, tr.sender, + sdk.Coins{{tr.params.AllowedBondDenom, returnCoins}}) if err != nil { return err } @@ -413,7 +396,7 @@ func (d deliver) unbond(tx TxUnbond) error { // change the share types to unbonded if they were not already if candidate.Status == Bonded { - err = d.bondedToUnbondedPool(candidate) + err = tr.bondedToUnbondedPool(candidate) if err != nil { return err } @@ -425,11 +408,11 @@ func (d deliver) unbond(tx TxUnbond) error { // deduct shares from the candidate and save if candidate.Liabilities.IsZero() { - removeCandidate(d.store, tx.PubKey) + tr.keeper.removeCandidate(tx.PubKey) } else { - saveCandidate(d.store, candidate) + tr.keeper.saveCandidate(candidate) } - saveGlobalState(d.store, d.gs) + tr.keeper.saveGlobalState(tr.gs) return nil } diff --git a/x/stake/mapper.go b/x/stake/keeper.go similarity index 66% rename from x/stake/mapper.go rename to x/stake/keeper.go index 59f8aa78a9..b7b58cf0ee 100644 --- a/x/stake/mapper.go +++ b/x/stake/keeper.go @@ -7,12 +7,8 @@ import ( "github.com/cosmos/cosmos-sdk/types" ) -// nolint +//nolint var ( - - // internal wire codec - cdc *wire.Codec - // Keys for store prefixes CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys ParamKey = []byte{0x02} // key for global parameters relating to staking @@ -26,13 +22,6 @@ var ( DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond ) -func init() { - cdc = wire.NewCodec() - cdc.RegisterInterface((*types.Rational)(nil), nil) // XXX make like crypto.RegisterWire() - cdc.RegisterConcrete(types.Rat{}, "rat", nil) - crypto.RegisterWire(cdc) -} - // GetCandidateKey - get the key for the candidate with pubKey func GetCandidateKey(pubKey crypto.PubKey) []byte { return append(CandidateKeyPrefix, pubKey.Bytes()...) @@ -72,10 +61,28 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { return append(DelegatorBondsKeyPrefix, res...) } -//--------------------------------------------------------------------- +//___________________________________________________________________________ -func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { - b := store.Get(GetCandidateKey(pubKey)) +// keeper of the staking store +type Keeper struct { + store types.KVStore + cdc *wire.Codec +} + +func NewKeeper(ctx sdk.Context, key sdk.StoreKey) Keeper { + cdc := wire.NewCodec() + cdc.RegisterInterface((*types.Rational)(nil), nil) // XXX make like crypto.RegisterWire() + cdc.RegisterConcrete(types.Rat{}, "rat", nil) + crypto.RegisterWire(cdc) + + return StakeKeeper{ + store: ctx.KVStore(k.key), + cdc: cdc, + } +} + +func (k Keeper) loadCandidate(pubKey crypto.PubKey) *Candidate { + b := k.store.Get(GetCandidateKey(pubKey)) if b == nil { return nil } @@ -87,31 +94,31 @@ func loadCandidate(store types.KVStore, pubKey crypto.PubKey) *Candidate { return candidate } -func saveCandidate(store types.KVStore, candidate *Candidate) { +func (k Keeper) saveCandidate(candidate *Candidate) { // XXX should only remove validator if we know candidate is a validator - removeValidator(store, candidate.PubKey) + removeValidator(k.store, candidate.PubKey) validator := &Validator{candidate.PubKey, candidate.VotingPower} - updateValidator(store, validator) + updateValidator(k.store, validator) b, err := cdc.MarshalJSON(*candidate) if err != nil { panic(err) } - store.Set(GetCandidateKey(candidate.PubKey), b) + k.store.Set(GetCandidateKey(candidate.PubKey), b) } -func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { +func (k Keeper) removeCandidate(pubKey crypto.PubKey) { // XXX should only remove validator if we know candidate is a validator - removeValidator(store, pubKey) - store.Delete(GetCandidateKey(pubKey)) + removeValidator(k.store, pubKey) + k.store.Delete(GetCandidateKey(pubKey)) } -//--------------------------------------------------------------------- +//___________________________________________________________________________ -//func loadValidator(store types.KVStore, pubKey crypto.PubKey, votingPower types.Rational) *Validator { -//b := store.Get(GetValidatorKey(pubKey, votingPower)) +//func loadValidator(k.store types.KVStore, pubKey crypto.PubKey, votingPower types.Rational) *Validator { +//b := k.store.Get(GetValidatorKey(pubKey, votingPower)) //if b == nil { //return nil //} @@ -125,7 +132,7 @@ func removeCandidate(store types.KVStore, pubKey crypto.PubKey) { // updateValidator - update a validator and create accumulate any changes // in the changed validator substore -func updateValidator(store types.KVStore, validator *Validator) { +func (k Keeper) updateValidator(validator *Validator) { b, err := cdc.MarshalJSON(*validator) if err != nil { @@ -133,34 +140,34 @@ func updateValidator(store types.KVStore, validator *Validator) { } // add to the validators to update list if necessary - store.Set(GetValidatorUpdatesKey(validator.PubKey), b) + k.store.Set(GetValidatorUpdatesKey(validator.PubKey), b) // update the list ordered by voting power - store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) + k.store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) } -func removeValidator(store types.KVStore, pubKey crypto.PubKey) { +func (k Keeper) removeValidator(pubKey crypto.PubKey) { //add validator with zero power to the validator updates b, err := cdc.MarshalJSON(Validator{pubKey, types.ZeroRat}) if err != nil { panic(err) } - store.Set(GetValidatorUpdatesKey(pubKey), b) + k.store.Set(GetValidatorUpdatesKey(pubKey), b) // now actually delete from the validator set - candidate := loadCandidate(store, pubKey) + candidate := loadCandidate(k.store, pubKey) if candidate != nil { - store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) + k.store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) } } // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower -func getValidators(store types.KVStore, maxVal int) (validators []Validator) { +func (k Keeper) getValidators(maxVal int) (validators []Validator) { - iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest + iterator := k.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators = make([]Validator, maxVal) for i := 0; ; i++ { @@ -181,12 +188,12 @@ func getValidators(store types.KVStore, maxVal int) (validators []Validator) { return } -//--------------------------------------------------------------------- +//_________________________________________________________________________ // get the most updated validators -func getValidatorUpdates(store types.KVStore) (updates []Validator) { +func (k Keeper) getValidatorUpdates() (updates []Validator) { - iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest + iterator := k.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() @@ -203,10 +210,10 @@ func getValidatorUpdates(store types.KVStore) (updates []Validator) { } // remove all validator update entries -func clearValidatorUpdates(store types.KVStore, maxVal int) { - iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) +func (k Keeper) clearValidatorUpdates(maxVal int) { + iterator := k.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + k.store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop } iterator.Close() } @@ -214,11 +221,11 @@ func clearValidatorUpdates(store types.KVStore, maxVal int) { //--------------------------------------------------------------------- // loadCandidates - get the active list of all candidates TODO replace with multistore -func loadCandidates(store types.KVStore) (candidates Candidates) { +func (k Keeper) loadCandidates() (candidates Candidates) { - iterator := store.Iterator(subspace(CandidateKeyPrefix)) - //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) - //iterator := store.Iterator([]byte{}, []byte(nil)) + iterator := k.store.Iterator(subspace(CandidateKeyPrefix)) + //iterator := k.store.Iterator(CandidateKeyPrefix, []byte(nil)) + //iterator := k.store.Iterator([]byte{}, []byte(nil)) for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() @@ -233,13 +240,12 @@ func loadCandidates(store types.KVStore) (candidates Candidates) { return candidates } -//--------------------------------------------------------------------- +//_____________________________________________________________________ // load the pubkeys of all candidates a delegator is delegated too -func loadDelegatorCandidates(store types.KVStore, - delegator crypto.Address) (candidates []crypto.PubKey) { +func (k Keeper) loadDelegatorCandidates(delegator crypto.Address) (candidates []crypto.PubKey) { - candidateBytes := store.Get(GetDelegatorBondsKey(delegator)) + candidateBytes := k.store.Get(GetDelegatorBondsKey(delegator)) if candidateBytes == nil { return nil } @@ -251,12 +257,12 @@ func loadDelegatorCandidates(store types.KVStore, return } -//--------------------------------------------------------------------- +//_____________________________________________________________________ -func loadDelegatorBond(store types.KVStore, - delegator crypto.Address, candidate crypto.PubKey) *DelegatorBond { +func (k Keeper) loadDelegatorBond(delegator crypto.Address, + candidate crypto.PubKey) *DelegatorBond { - delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate)) + delegatorBytes := k.store.Get(GetDelegatorBondKey(delegator, candidate)) if delegatorBytes == nil { return nil } @@ -269,17 +275,18 @@ func loadDelegatorBond(store types.KVStore, return bond } -func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *DelegatorBond) { +func (k Keeper) saveDelegatorBond(delegator crypto.Address, + bond *DelegatorBond) { // if a new bond add to the list of bonds - if loadDelegatorBond(store, delegator, bond.PubKey) == nil { - pks := loadDelegatorCandidates(store, delegator) + if loadDelegatorBond(k.store, delegator, bond.PubKey) == nil { + pks := loadDelegatorCandidates(k.store, delegator) pks = append(pks, (*bond).PubKey) b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } - store.Set(GetDelegatorBondsKey(delegator), b) + k.store.Set(GetDelegatorBondsKey(delegator), b) } // now actually save the bond @@ -287,14 +294,14 @@ func saveDelegatorBond(store types.KVStore, delegator crypto.Address, bond *Dele if err != nil { panic(err) } - store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) + k.store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) //updateDelegatorBonds(store, delegator) } -func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidate crypto.PubKey) { +func (k Keeper) removeDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) { // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds - pks := loadDelegatorCandidates(store, delegator) + pks := loadDelegatorCandidates(k.store, delegator) for i, pk := range pks { if candidate.Equals(pk) { pks = append(pks[:i], pks[i+1:]...) @@ -304,18 +311,18 @@ func removeDelegatorBond(store types.KVStore, delegator crypto.Address, candidat if err != nil { panic(err) } - store.Set(GetDelegatorBondsKey(delegator), b) + k.store.Set(GetDelegatorBondsKey(delegator), b) // now remove the actual bond - store.Delete(GetDelegatorBondKey(delegator, candidate)) + k.store.Delete(GetDelegatorBondKey(delegator, candidate)) //updateDelegatorBonds(store, delegator) } //_______________________________________________________________________ // load/save the global staking params -func loadParams(store types.KVStore) (params Params) { - b := store.Get(ParamKey) +func (k Keeper) loadParams() (params Params) { + b := k.store.Get(ParamKey) if b == nil { return defaultParams() } @@ -324,22 +331,21 @@ func loadParams(store types.KVStore) (params Params) { if err != nil { panic(err) // This error should never occur big problem if does } - return } -func saveParams(store types.KVStore, params Params) { +func (k Keeper) saveParams(params Params) { b, err := cdc.MarshalJSON(params) if err != nil { panic(err) } - store.Set(ParamKey, b) + k.store.Set(ParamKey, b) } //_______________________________________________________________________ // load/save the global staking state -func loadGlobalState(store types.KVStore) (gs *GlobalState) { - b := store.Get(GlobalStateKey) +func (k Keeper) loadGlobalState() (gs *GlobalState) { + b := k.store.Get(GlobalStateKey) if b == nil { return initialGlobalState() } @@ -350,10 +356,11 @@ func loadGlobalState(store types.KVStore) (gs *GlobalState) { } return } -func saveGlobalState(store types.KVStore, gs *GlobalState) { + +func (k Keeper) saveGlobalState(gs *GlobalState) { b, err := cdc.MarshalJSON(*gs) if err != nil { panic(err) } - store.Set(GlobalStateKey, b) + k.store.Set(GlobalStateKey, b) } diff --git a/x/stake/mapper_test.go b/x/stake/keeper_test.go similarity index 100% rename from x/stake/mapper_test.go rename to x/stake/keeper_test.go diff --git a/x/stake/types.go b/x/stake/types.go index 1a184e1d9f..ccaf7c1b07 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -8,9 +8,6 @@ import ( // Params defines the high level settings for staking type Params struct { - HoldBonded crypto.Address `json:"hold_bonded"` // account where all bonded coins are held - HoldUnbonded crypto.Address `json:"hold_unbonded"` // account where all delegated but unbonded coins are held - InflationRateChange sdk.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate InflationMax sdk.Rational `json:"inflation_max"` // maximum inflation rate InflationMin sdk.Rational `json:"inflation_min"` // minimum inflation rate @@ -28,8 +25,6 @@ type Params struct { func defaultParams() Params { return Params{ - HoldBonded: []byte("77777777777777777777777777777777"), - HoldUnbonded: []byte("88888888888888888888888888888888"), InflationRateChange: sdk.NewRat(13, 100), InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), @@ -94,6 +89,9 @@ func (gs *GlobalState) unbondedShareExRate() sdk.Rational { return gs.UnbondedShares.Inv().Mul(sdk.NewRat(gs.UnbondedPool)) } +// XXX XXX XXX +// expand to include the function of actually transfering the tokens + func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) { issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.BondedPool += amount From 292e15687213f49b3dce746e950de69797f8d287 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 13 Mar 2018 19:27:52 +0100 Subject: [PATCH 23/54] werkin to compile working working refactor staking --- glide.yaml | 55 -------- types/rational.go | 68 +++++----- x/stake/handler.go | 145 ++++++++++----------- x/stake/handler_test.go | 41 ++---- x/stake/{keeper.go => mapper.go} | 124 +++++++++--------- x/stake/{keeper_test.go => mapper_test.go} | 4 +- x/stake/test_common.go | 16 ++- x/stake/tick.go | 14 +- x/stake/tick_test.go | 34 ++--- x/stake/tx.go | 9 +- x/stake/tx_test.go | 4 +- x/stake/types.go | 2 + x/stake/wire.go | 2 +- 13 files changed, 215 insertions(+), 303 deletions(-) delete mode 100644 glide.yaml rename x/stake/{keeper.go => mapper.go} (69%) rename x/stake/{keeper_test.go => mapper_test.go} (99%) diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index 34cdb13d8f..0000000000 --- a/glide.yaml +++ /dev/null @@ -1,55 +0,0 @@ -package: github.com/cosmos/cosmos-sdk -import: -- package: github.com/golang/protobuf - version: ^1.0.0 - subpackages: - - proto -- package: github.com/bgentry/speakeasy - version: ^0.1.0 -- package: github.com/mattn/go-isatty - version: ~0.0.3 -- package: github.com/pkg/errors - version: ^0.8.0 -- package: github.com/rigelrozanski/common -- package: github.com/tendermint/abci - version: develop - subpackages: - - server - - types -- package: github.com/tendermint/go-crypto - version: develop -- package: github.com/tendermint/go-wire - version: develop -- package: github.com/tendermint/iavl - version: develop -- package: github.com/tendermint/tmlibs - version: develop - subpackages: - - common - - db - - log - - logger - - merkle -- package: github.com/tendermint/tendermint - version: breaking/wire-sdk2 - subpackages: - - cmd/tendermint/commands - - config - - lite - - rpc/client - - types -- package: golang.org/x/crypto - subpackages: - - ripemd160 -- package: github.com/spf13/pflag - version: v1.0.0 -- package: github.com/spf13/cobra - version: v0.0.1 -- package: github.com/spf13/viper - version: ^1.0.0 -testImport: -- package: github.com/stretchr/testify - version: ^1.2.1 - subpackages: - - assert - - require diff --git a/types/rational.go b/types/rational.go index 556fd0eef4..857a6696eb 100644 --- a/types/rational.go +++ b/types/rational.go @@ -2,23 +2,11 @@ package types import ( "errors" - "fmt" "math/big" "strconv" "strings" - - wire "github.com/tendermint/go-wire" ) -var ratCdc = RegisterWire(wire.NewCodec()) - -// add rational codec elements to provided codec -func RegisterWire(cdc *wire.Codec) *wire.Codec { - cdc.RegisterInterface((*Rational)(nil), nil) - cdc.RegisterConcrete(Rat{}, "rat", nil) - return cdc -} - // "that's one big rat!" // ______ // / / /\ \____oo @@ -175,32 +163,44 @@ func (r Rat) Round(precisionFactor int64) Rational { //___________________________________________________________________________________ +//var ratCdc = RegisterWire(wire.NewCodec()) +//// add rational codec elements to provided codec +//func RegisterWire(cdc *wire.Codec) *wire.Codec { +//cdc.RegisterInterface((*Rational)(nil), nil) +//cdc.RegisterConcrete(Rat{}, "rat", nil) +//return cdc +//} + //TODO there has got to be a better way using native MarshalText and UnmarshalText // RatMarshal - Marshable Rat Struct -type RatMarshal struct { - Numerator int64 `json:"numerator"` - Denominator int64 `json:"denominator"` -} +//type RatMarshal struct { +//Numerator int64 `json:"numerator"` +//Denominator int64 `json:"denominator"` +//} -// MarshalJSON - custom implementation of JSON Marshal -func (r Rat) MarshalJSON() ([]byte, error) { - return ratCdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) -} +//// MarshalJSON - custom implementation of JSON Marshal +//func (r Rat) MarshalJSON() ([]byte, error) { +//return ratCdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) +//} -// UnmarshalJSON - custom implementation of JSON Unmarshal -func (r *Rat) UnmarshalJSON(data []byte) (err error) { - defer func() { - if rcv := recover(); rcv != nil { - err = fmt.Errorf("Panic during UnmarshalJSON: %v", rcv) - } - }() +//// UnmarshalJSON - custom implementation of JSON Unmarshal +//func (r *Rat) UnmarshalJSON(data []byte) (err error) { +//defer func() { +//if rcv := recover(); rcv != nil { +//err = fmt.Errorf("Panic during UnmarshalJSON: %v", rcv) +//} +//}() - ratMar := new(RatMarshal) - if err := ratCdc.UnmarshalJSON(data, ratMar); err != nil { - return err - } - r.Rat = big.NewRat(ratMar.Numerator, ratMar.Denominator) +//ratMar := new(RatMarshal) +//if err := ratCdc.UnmarshalJSON(data, ratMar); err != nil { +//return err +//} +//r.Rat = big.NewRat(ratMar.Numerator, ratMar.Denominator) - return nil -} +//return nil +//} + +//nolint +func (r Rat) MarshalJSON() ([]byte, error) { return r.MarshalText() } +func (r *Rat) UnmarshalJSON(data []byte) (err error) { return r.UnmarshalText(data) } diff --git a/x/stake/handler.go b/x/stake/handler.go index 7865fe0762..5e79dd586b 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -14,9 +14,9 @@ import ( ) // separated for testing -func InitState(ctx sdk.Context, key, value string) error { +func InitState(ctx sdk.Context, mapper Mapper, key, value string) error { - params := loadParams(store) + params := mapper.loadParams() switch key { case "allowed_bond_denom": params.AllowedBondDenom = value @@ -39,74 +39,57 @@ func InitState(ctx sdk.Context, key, value string) error { params.GasUnbond = int64(i) } default: - return sdk.ErrUnknownKey(key) + return sdk.ErrUnknownRequest(key) } - saveParams(store, params) + mapper.saveParams(params) return nil } //_______________________________________________________________________ -func NewHandler(stakeKey sdk.StoreKey, ck bank.CoinKeeper) sdk.Handler { +func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - res := sdk.Result{} + params := mapper.loadParams() - err := msg.ValidateBasic() - if err != nil { - return res, err + res := msg.ValidateBasic().Result() + if res.Code != sdk.CodeOK { + return res } - - // return the fee for each tx type - if ctx.IsCheckTx() { - // XXX: add some tags so we can search it! - switch txInner := tx.Unwrap().(type) { - case TxDeclareCandidacy: - return sdk.NewCheck(params.GasDeclareCandidacy, "") - case TxEditCandidacy: - return sdk.NewCheck(params.GasEditCandidacy, "") - case TxDelegate: - return sdk.NewCheck(params.GasDelegate, "") - case TxUnbond: - return sdk.NewCheck(params.GasUnbond, "") - default: - return sdk.ErrUnknownTxType(tx) - } - } - - // TODO: remove redundancy - // also we don't need to check the res - gas is already deducted in sdk - _, err = h.CheckTx(ctx, store, tx, nil) - if err != nil { - return - } - sender, err := getTxSender(ctx) if err != nil { return } - keeper := NewKeeper(ctx, stakeKey) transact := NewTransact(ctx, ck) // Run the transaction switch _tx := tx.Unwrap().(type) { case TxDeclareCandidacy: - res.GasUsed = params.GasDeclareCandidacy + if !ctx.IsCheckTx() { + res.GasUsed = params.GasDeclareCandidacy + } return res, transact.declareCandidacy(_tx) case TxEditCandidacy: - res.GasUsed = params.GasEditCandidacy + if !ctx.IsCheckTx() { + res.GasUsed = params.GasEditCandidacy + } return res, transact.editCandidacy(_tx) case TxDelegate: - res.GasUsed = params.GasDelegate + if !ctx.IsCheckTx() { + res.GasUsed = params.GasDelegate + } return res, transact.delegate(_tx) case TxUnbond: //context with hold account permissions - params := loadParams(store) - res.GasUsed = params.GasUnbond - //ctx2 := ctx.WithPermissions(params.HoldBonded) //TODO remove this line if non-permissioned ctx works + if !ctx.IsCheckTx() { + params := loadParams(store) + res.GasUsed = params.GasUnbond + } return res, transact.unbond(_tx) + default: + return sdk.ErrUnknownTxType(msgType) } return } @@ -126,20 +109,21 @@ func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) { // common fields to all transactions type transact struct { sender crypto.Address - keeper Keeper + mapper Mapper coinKeeper bank.CoinKeeper params Params gs *GlobalState + isCheckTx sdk.Context } -// XXX move keeper creation to application? -func newTransact(ctx sdk.Context, keeper Keeper, ck bank.CoinKeeper) transact { +func newTransact(ctx sdk.Context, sender sdk.Address, mapper Mapper, ck bank.CoinKeeper) transact { return transact{ sender: sender, - keeper: keeper, + mapper: mapper, coinKeeper: ck, - params: keeper.loadParams(), - gs: keeper.loadGlobalState(), + params: mapper.loadParams(), + gs: mapper.loadGlobalState(), + isCheckTx: ctx.IsCheckTx(), } } @@ -172,8 +156,8 @@ func (tr transact) unbondedToBondedPool(candidate *Candidate) error { } // return an error if the bonds coins are incorrect -func checkDenom(keeper Keeper, tx BondUpdate) error { - if tx.Bond.Denom != keeper.loadParams().AllowedBondDenom { +func checkDenom(mapper Mapper, tx BondUpdate) error { + if tx.Bond.Denom != mapper.loadParams().AllowedBondDenom { return fmt.Errorf("Invalid coin denomination") } return nil @@ -186,25 +170,26 @@ func checkDenom(keeper Keeper, tx BondUpdate) error { func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error { // check to see if the pubkey or sender has been registered before - candidate := tr.keeper.loadCandidate(tx.PubKey) - if candidate != nil { + if tr.mapper.loadCandidate(tx.PubKey) != nil { return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ " PubKey %v already registered with %v candidate address", candidate.PubKey, candidate.Owner) } - err := checkDenom(tx.BondUpdate, tr.keeper) + err := checkDenom(tx.BondUpdate, tr.mapper) if err != nil { return err } - // XXX end of old check tx + if tr.IsCheckTx { + return nil + } // create and save the empty candidate - bond := tr.keeper.loadCandidate(tx.PubKey) + bond := tr.mapper.loadCandidate(tx.PubKey) if bond != nil { return ErrCandidateExistsAddr() } candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description) - tr.keeper.saveCandidate(candidate) + tr.mapper.saveCandidate(candidate) // move coins from the tr.sender account to a (self-bond) delegator account // the candidate account and global shares are updated within here @@ -215,14 +200,15 @@ func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error { func (tr transact) editCandidacy(tx TxEditCandidacy) error { // candidate must already be registered - candidate := tr.keeper.loadCandidate(tx.PubKey) - if candidate == nil { // does PubKey exist + if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) } - // XXX end of old check tx + if tr.IsCheckTx { + return nil + } // Get the pubKey bond account - candidate := tr.keeper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.PubKey) if candidate == nil { return ErrBondNotNominated() } @@ -244,24 +230,25 @@ func (tr transact) editCandidacy(tx TxEditCandidacy) error { candidate.Description.Details = tx.Description.Details } - tr.keeper.saveCandidate(candidate) + tr.mapper.saveCandidate(candidate) return nil } func (tr transact) delegate(tx TxDelegate) error { - candidate := tr.keeper.loadCandidate(tx.PubKey) - if candidate == nil { // does PubKey exist + if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) } - err := checkDenom(tx.BondUpdate, tr.keeper) + err := checkDenom(tx.BondUpdate, tr.mapper) if err != nil { return err } - // end of old check tx + if tr.IsCheckTx { + return nil + } // Get the pubKey bond account - candidate := tr.keeper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.PubKey) if candidate == nil { return ErrBondNotNominated() } @@ -289,7 +276,7 @@ func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) er } // Get or create the delegator bond - bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) + bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) if bond == nil { bond = &DelegatorBond{ PubKey: tx.PubKey, @@ -299,19 +286,19 @@ func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) er // Account new shares, save bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, tr.gs)) - tr.keeper.saveCandidate(candidate) - tr.keeper.saveDelegatorBond(tr.sender, bond) - tr.keeper.saveGlobalState(tr.gs) + tr.mapper.saveCandidate(candidate) + tr.mapper.saveDelegatorBond(tr.sender, bond) + tr.mapper.saveGlobalState(tr.gs) return nil } func (tr transact) unbond(tx TxUnbond) error { // check if bond has any shares in it unbond - bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) + existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) sharesStr := viper.GetString(tx.Shares) - if bond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares - return fmt.Errorf("no shares in account to unbond") + if existingBond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares + return errors.New("no shares in account to unbond") } // if shares set to special case Max then we're good @@ -331,7 +318,7 @@ func (tr transact) unbond(tx TxUnbond) error { // XXX end of old checkTx // get delegator bond - bond := tr.keeper.loadDelegatorBond(tr.sender, tx.PubKey) + bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) if bond == nil { return ErrNoDelegatorForAddress() } @@ -355,7 +342,7 @@ func (tr transact) unbond(tx TxUnbond) error { bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate := tr.keeper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.PubKey) if candidate == nil { return ErrNoCandidateForAddress() } @@ -371,9 +358,9 @@ func (tr transact) unbond(tx TxUnbond) error { } // remove the bond - tr.keeper.removeDelegatorBond(tr.sender, tx.PubKey) + tr.mapper.removeDelegatorBond(tr.sender, tx.PubKey) } else { - tr.keeper.saveDelegatorBond(tr.sender, bond) + tr.mapper.saveDelegatorBond(tr.sender, bond) } // transfer coins back to account @@ -408,11 +395,11 @@ func (tr transact) unbond(tx TxUnbond) error { // deduct shares from the candidate and save if candidate.Liabilities.IsZero() { - tr.keeper.removeCandidate(tx.PubKey) + tr.mapper.removeCandidate(tx.PubKey) } else { - tr.keeper.saveCandidate(candidate) + tr.mapper.saveCandidate(candidate) } - tr.keeper.saveGlobalState(tr.gs) + tr.mapper.saveGlobalState(tr.gs) return nil } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index b6ea3beaa3..4cae995604 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/tmlibs/rational" sdk "github.com/cosmos/cosmos-sdk/types" coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix @@ -16,23 +15,7 @@ import ( //______________________________________________________________________ -// dummy transfer functions, represents store operations on account balances - -type testCoinSender struct { - store map[string]int64 -} - -var _ coinSend = testCoinSender{} // enforce interface at compile time - -func (c testCoinSender) transferFn(sender, receiver sdk.Actor, coins coin.Coins) error { - c.store[string(sender.Address)] -= coins[0].Amount - c.store[string(receiver.Address)] += coins[0].Amount - return nil -} - -//______________________________________________________________________ - -func initAccounts(n int, amount int64) ([]sdk.Actor, map[string]int64) { +func initAccounts(n int, amount int64) ([]sdk.Address, map[string]int64) { accStore := map[string]int64{} senders := newActors(n) for _, sender := range senders { @@ -69,10 +52,10 @@ func paramsNoInflation() Params { return Params{ HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), - InflationRateChange: rational.Zero, - InflationMax: rational.Zero, - InflationMin: rational.Zero, - GoalBonded: rational.New(67, 100), + InflationRateChange: sdk.Zero, + InflationMax: sdk.Zero, + InflationMin: sdk.Zero, + GoalBonded: sdk.New(67, 100), MaxVals: 100, AllowedBondDenom: "fermion", GasDeclareCandidacy: 20, @@ -82,17 +65,11 @@ func paramsNoInflation() Params { } } -func newDeliver(t, sender sdk.Actor, accStore map[string]int64) deliver { - store := initTestStore() +func newTestTransact(t, sender sdk.Address, isCheckTx bool) transact { + store, mapper, coinKeeper := createTestInput(t, isCheckTx) params := paramsNoInflation() - saveParams(store, params) - return deliver{ - store: store, - sender: sender, - params: params, - gs: loadGlobalState(store), - transfer: testCoinSender{accStore}.transferFn, - } + mapper.saveParams(params) + newTransact(ctx, sender, mapper, coinKeeper) } func TestDuplicatesTxDeclareCandidacy(t *testing.T) { diff --git a/x/stake/keeper.go b/x/stake/mapper.go similarity index 69% rename from x/stake/keeper.go rename to x/stake/mapper.go index b7b58cf0ee..296fe0aa32 100644 --- a/x/stake/keeper.go +++ b/x/stake/mapper.go @@ -2,9 +2,9 @@ package stake import ( crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" ) //nolint @@ -28,7 +28,7 @@ func GetCandidateKey(pubKey crypto.PubKey) []byte { } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(pubKey crypto.PubKey, power types.Rational) []byte { +func GetValidatorKey(pubKey crypto.PubKey, power sdk.Rational) []byte { b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store } @@ -63,26 +63,26 @@ func GetDelegatorBondsKey(delegator crypto.Address) []byte { //___________________________________________________________________________ -// keeper of the staking store -type Keeper struct { - store types.KVStore +// mapper of the staking store +type Mapper struct { + store sdk.KVStore cdc *wire.Codec } -func NewKeeper(ctx sdk.Context, key sdk.StoreKey) Keeper { +func NewMapper(ctx sdk.Context, key sdk.StoreKey) Mapper { cdc := wire.NewCodec() - cdc.RegisterInterface((*types.Rational)(nil), nil) // XXX make like crypto.RegisterWire() - cdc.RegisterConcrete(types.Rat{}, "rat", nil) + cdc.RegisterInterface((*sdk.Rational)(nil), nil) // XXX make like crypto.RegisterWire() + cdc.RegisterConcrete(sdk.Rat{}, "rat", nil) crypto.RegisterWire(cdc) - return StakeKeeper{ - store: ctx.KVStore(k.key), + return StakeMapper{ + store: ctx.KVStore(m.key), cdc: cdc, } } -func (k Keeper) loadCandidate(pubKey crypto.PubKey) *Candidate { - b := k.store.Get(GetCandidateKey(pubKey)) +func (m Mapper) loadCandidate(pubKey crypto.PubKey) *Candidate { + b := m.store.Get(GetCandidateKey(pubKey)) if b == nil { return nil } @@ -94,31 +94,31 @@ func (k Keeper) loadCandidate(pubKey crypto.PubKey) *Candidate { return candidate } -func (k Keeper) saveCandidate(candidate *Candidate) { +func (m Mapper) saveCandidate(candidate *Candidate) { // XXX should only remove validator if we know candidate is a validator - removeValidator(k.store, candidate.PubKey) + removeValidator(m.store, candidate.PubKey) validator := &Validator{candidate.PubKey, candidate.VotingPower} - updateValidator(k.store, validator) + updateValidator(m.store, validator) b, err := cdc.MarshalJSON(*candidate) if err != nil { panic(err) } - k.store.Set(GetCandidateKey(candidate.PubKey), b) + m.store.Set(GetCandidateKey(candidate.PubKey), b) } -func (k Keeper) removeCandidate(pubKey crypto.PubKey) { +func (m Mapper) removeCandidate(pubKey crypto.PubKey) { // XXX should only remove validator if we know candidate is a validator - removeValidator(k.store, pubKey) - k.store.Delete(GetCandidateKey(pubKey)) + removeValidator(m.store, pubKey) + m.store.Delete(GetCandidateKey(pubKey)) } //___________________________________________________________________________ -//func loadValidator(k.store types.KVStore, pubKey crypto.PubKey, votingPower types.Rational) *Validator { -//b := k.store.Get(GetValidatorKey(pubKey, votingPower)) +//func loadValidator(m.store sdk.KVStore, pubKey crypto.PubKey, votingPower sdk.Rational) *Validator { +//b := m.store.Get(GetValidatorKey(pubKey, votingPower)) //if b == nil { //return nil //} @@ -132,7 +132,7 @@ func (k Keeper) removeCandidate(pubKey crypto.PubKey) { // updateValidator - update a validator and create accumulate any changes // in the changed validator substore -func (k Keeper) updateValidator(validator *Validator) { +func (m Mapper) updateValidator(validator *Validator) { b, err := cdc.MarshalJSON(*validator) if err != nil { @@ -140,34 +140,34 @@ func (k Keeper) updateValidator(validator *Validator) { } // add to the validators to update list if necessary - k.store.Set(GetValidatorUpdatesKey(validator.PubKey), b) + m.store.Set(GetValidatorUpdatesKey(validator.PubKey), b) // update the list ordered by voting power - k.store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) + m.store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) } -func (k Keeper) removeValidator(pubKey crypto.PubKey) { +func (m Mapper) removeValidator(pubKey crypto.PubKey) { //add validator with zero power to the validator updates - b, err := cdc.MarshalJSON(Validator{pubKey, types.ZeroRat}) + b, err := cdc.MarshalJSON(Validator{pubKey, sdk.ZeroRat}) if err != nil { panic(err) } - k.store.Set(GetValidatorUpdatesKey(pubKey), b) + m.store.Set(GetValidatorUpdatesKey(pubKey), b) // now actually delete from the validator set - candidate := loadCandidate(k.store, pubKey) + candidate := loadCandidate(m.store, pubKey) if candidate != nil { - k.store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) + m.store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) } } // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower -func (k Keeper) getValidators(maxVal int) (validators []Validator) { +func (m Mapper) getValidators(maxVal int) (validators []Validator) { - iterator := k.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest + iterator := m.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators = make([]Validator, maxVal) for i := 0; ; i++ { @@ -191,9 +191,9 @@ func (k Keeper) getValidators(maxVal int) (validators []Validator) { //_________________________________________________________________________ // get the most updated validators -func (k Keeper) getValidatorUpdates() (updates []Validator) { +func (m Mapper) getValidatorUpdates() (updates []Validator) { - iterator := k.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest + iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() @@ -210,10 +210,10 @@ func (k Keeper) getValidatorUpdates() (updates []Validator) { } // remove all validator update entries -func (k Keeper) clearValidatorUpdates(maxVal int) { - iterator := k.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) +func (m Mapper) clearValidatorUpdates(maxVal int) { + iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) for ; iterator.Valid(); iterator.Next() { - k.store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + m.store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop } iterator.Close() } @@ -221,11 +221,11 @@ func (k Keeper) clearValidatorUpdates(maxVal int) { //--------------------------------------------------------------------- // loadCandidates - get the active list of all candidates TODO replace with multistore -func (k Keeper) loadCandidates() (candidates Candidates) { +func (m Mapper) loadCandidates() (candidates Candidates) { - iterator := k.store.Iterator(subspace(CandidateKeyPrefix)) - //iterator := k.store.Iterator(CandidateKeyPrefix, []byte(nil)) - //iterator := k.store.Iterator([]byte{}, []byte(nil)) + iterator := m.store.Iterator(subspace(CandidateKeyPrefix)) + //iterator := m.store.Iterator(CandidateKeyPrefix, []byte(nil)) + //iterator := m.store.Iterator([]byte{}, []byte(nil)) for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() @@ -243,9 +243,9 @@ func (k Keeper) loadCandidates() (candidates Candidates) { //_____________________________________________________________________ // load the pubkeys of all candidates a delegator is delegated too -func (k Keeper) loadDelegatorCandidates(delegator crypto.Address) (candidates []crypto.PubKey) { +func (m Mapper) loadDelegatorCandidates(delegator crypto.Address) (candidates []crypto.PubKey) { - candidateBytes := k.store.Get(GetDelegatorBondsKey(delegator)) + candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator)) if candidateBytes == nil { return nil } @@ -259,10 +259,10 @@ func (k Keeper) loadDelegatorCandidates(delegator crypto.Address) (candidates [] //_____________________________________________________________________ -func (k Keeper) loadDelegatorBond(delegator crypto.Address, +func (m Mapper) loadDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) *DelegatorBond { - delegatorBytes := k.store.Get(GetDelegatorBondKey(delegator, candidate)) + delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate)) if delegatorBytes == nil { return nil } @@ -275,18 +275,18 @@ func (k Keeper) loadDelegatorBond(delegator crypto.Address, return bond } -func (k Keeper) saveDelegatorBond(delegator crypto.Address, +func (m Mapper) saveDelegatorBond(delegator crypto.Address, bond *DelegatorBond) { // if a new bond add to the list of bonds - if loadDelegatorBond(k.store, delegator, bond.PubKey) == nil { - pks := loadDelegatorCandidates(k.store, delegator) + if loadDelegatorBond(m.store, delegator, bond.PubKey) == nil { + pks := loadDelegatorCandidates(m.store, delegator) pks = append(pks, (*bond).PubKey) b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) } - k.store.Set(GetDelegatorBondsKey(delegator), b) + m.store.Set(GetDelegatorBondsKey(delegator), b) } // now actually save the bond @@ -294,14 +294,14 @@ func (k Keeper) saveDelegatorBond(delegator crypto.Address, if err != nil { panic(err) } - k.store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) + m.store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) //updateDelegatorBonds(store, delegator) } -func (k Keeper) removeDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) { +func (m Mapper) removeDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) { // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds - pks := loadDelegatorCandidates(k.store, delegator) + pks := loadDelegatorCandidates(m.store, delegator) for i, pk := range pks { if candidate.Equals(pk) { pks = append(pks[:i], pks[i+1:]...) @@ -311,18 +311,18 @@ func (k Keeper) removeDelegatorBond(delegator crypto.Address, candidate crypto.P if err != nil { panic(err) } - k.store.Set(GetDelegatorBondsKey(delegator), b) + m.store.Set(GetDelegatorBondsKey(delegator), b) // now remove the actual bond - k.store.Delete(GetDelegatorBondKey(delegator, candidate)) + m.store.Delete(GetDelegatorBondKey(delegator, candidate)) //updateDelegatorBonds(store, delegator) } //_______________________________________________________________________ // load/save the global staking params -func (k Keeper) loadParams() (params Params) { - b := k.store.Get(ParamKey) +func (m Mapper) loadParams() (params Params) { + b := m.store.Get(ParamKey) if b == nil { return defaultParams() } @@ -333,19 +333,19 @@ func (k Keeper) loadParams() (params Params) { } return } -func (k Keeper) saveParams(params Params) { +func (m Mapper) saveParams(params Params) { b, err := cdc.MarshalJSON(params) if err != nil { panic(err) } - k.store.Set(ParamKey, b) + m.store.Set(ParamKey, b) } //_______________________________________________________________________ // load/save the global staking state -func (k Keeper) loadGlobalState() (gs *GlobalState) { - b := k.store.Get(GlobalStateKey) +func (m Mapper) loadGlobalState() (gs *GlobalState) { + b := m.store.Get(GlobalStateKey) if b == nil { return initialGlobalState() } @@ -357,10 +357,10 @@ func (k Keeper) loadGlobalState() (gs *GlobalState) { return } -func (k Keeper) saveGlobalState(gs *GlobalState) { +func (m Mapper) saveGlobalState(gs *GlobalState) { b, err := cdc.MarshalJSON(*gs) if err != nil { panic(err) } - k.store.Set(GlobalStateKey, b) + m.store.Set(GlobalStateKey, b) } diff --git a/x/stake/keeper_test.go b/x/stake/mapper_test.go similarity index 99% rename from x/stake/keeper_test.go rename to x/stake/mapper_test.go index 89a85a1cb3..36c179788e 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/mapper_test.go @@ -161,7 +161,7 @@ import ( //} func TestState(t *testing.T) { - store := initTestStore(t) + store := createTestInput(t) //delegator := crypto.Address{[]byte("addressdelegator")} //validator := crypto.Address{[]byte("addressvalidator")} @@ -260,7 +260,7 @@ func TestState(t *testing.T) { } func TestGetValidators(t *testing.T) { - store := initTestStore(t) + store, ctx, key := createTestInput(t, false) N := 5 addrs := newAddrs(N) candidatesFromActors(store, addrs, []int64{400, 200, 0, 0, 0}) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 6b600ba339..997b55102a 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -7,10 +7,10 @@ import ( "github.com/stretchr/testify/require" + abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" dbm "github.com/tendermint/tmlibs/db" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -21,16 +21,18 @@ func subspace(prefix []byte) (start, end []byte) { return prefix, end } -func initTestStore(t *testing.T) sdk.KVStore { - // Capabilities key to access the main KVStore. - //db, err := dbm.NewGoLevelDB("stake", "data") +func createTestInput(t *testing.T, isCheckTx bool) (store sdk.KVStore, ctx sdk.Context, key sdk.StoreKey) { db := dbm.NewMemDB() - stakeStoreKey := sdk.NewKVStoreKey("stake") + key = sdk.NewKVStoreKey("stake") + ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(stakeStoreKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) - return ms.GetKVStore(stakeStoreKey) + + ctx = sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) + store = ms.GetKVStore(key) + return } func newAddrs(n int) (addrs []crypto.Address) { diff --git a/x/stake/tick.go b/x/stake/tick.go index 42c995da44..3da581ae3f 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -2,13 +2,11 @@ package stake import ( sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" - "github.com/tendermint/tmlibs/rational" ) // Tick - called at the end of every block -func Tick(ctx sdk.Context, store types.KVStore) (change []*abci.Validator, err error) { +func Tick(ctx sdk.Context, store sdk.KVStore) (change []*abci.Validator, err error) { // retrieve params params := loadParams(store) @@ -25,10 +23,10 @@ func Tick(ctx sdk.Context, store types.KVStore) (change []*abci.Validator, err e return UpdateValidatorSet(store, gs, params) } -var hrsPerYr = rational.New(8766) // as defined by a julian year of 365.25 days +var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func processProvisions(store types.KVStore, gs *GlobalState, params Params) { +func processProvisions(store sdk.KVStore, gs *GlobalState, params Params) { gs.Inflation = nextInflation(gs, params).Round(1000000000) @@ -36,7 +34,7 @@ func processProvisions(store types.KVStore, gs *GlobalState, params Params) { // more bonded tokens are added proportionally to all validators the only term // which needs to be updated is the `BondedPool`. So for each previsions cycle: - provisions := gs.Inflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() + provisions := gs.Inflation.Mul(sdk.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() gs.BondedPool += provisions gs.TotalSupply += provisions @@ -49,7 +47,7 @@ func processProvisions(store types.KVStore, gs *GlobalState, params Params) { } // get the next inflation rate for the hour -func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) { +func nextInflation(gs *GlobalState, params Params) (inflation sdk.Rat) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive of negative) depending or @@ -58,7 +56,7 @@ func nextInflation(gs *GlobalState, params Params) (inflation rational.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := rational.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) // increase the new annual inflation for this next cycle diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index add6be80b3..8489b7c517 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -3,12 +3,12 @@ package stake import ( "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" - "github.com/tendermint/tmlibs/rational" ) func TestGetInflation(t *testing.T) { - store := initTestStore(t) + store, ctx, key := createTestInput(t, false) params := loadParams(store) gs := loadGlobalState(store) @@ -18,27 +18,27 @@ func TestGetInflation(t *testing.T) { tests := []struct { setBondedPool, setTotalSupply int64 - setInflation, expectedChange rational.Rat + setInflation, expectedChange sdk.Rat }{ // with 0% bonded atom supply the inflation should increase by InflationRateChange - {0, 0, rational.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + {0, 0, sdk.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, // 100% bonded, starting at 20% inflation and being reduced - {1, 1, rational.New(20, 100), rational.One.Sub(rational.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + {1, 1, sdk.New(20, 100), sdk.One.Sub(sdk.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, // 50% bonded, starting at 10% inflation and being increased - {1, 2, rational.New(10, 100), rational.One.Sub(rational.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + {1, 2, sdk.New(10, 100), sdk.One.Sub(sdk.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, // test 7% minimum stop (testing with 100% bonded) - {1, 1, rational.New(7, 100), rational.Zero}, - {1, 1, rational.New(70001, 1000000), rational.New(-1, 1000000)}, + {1, 1, sdk.New(7, 100), sdk.Zero}, + {1, 1, sdk.New(70001, 1000000), sdk.New(-1, 1000000)}, // test 20% maximum stop (testing with 0% bonded) - {0, 0, rational.New(20, 100), rational.Zero}, - {0, 0, rational.New(199999, 1000000), rational.New(1, 1000000)}, + {0, 0, sdk.New(20, 100), sdk.Zero}, + {0, 0, sdk.New(199999, 1000000), sdk.New(1, 1000000)}, // perfect balance shouldn't change inflation - {67, 100, rational.New(15, 100), rational.Zero}, + {67, 100, sdk.New(15, 100), sdk.Zero}, } for _, tc := range tests { gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply @@ -53,7 +53,7 @@ func TestGetInflation(t *testing.T) { } func TestProcessProvisions(t *testing.T) { - store := initTestStore(t) + store, ctx, key := createTestInput(t, false) params := loadParams(store) gs := loadGlobalState(store) @@ -75,7 +75,7 @@ func TestProcessProvisions(t *testing.T) { var unbondedShares int64 = 400000000 // initial bonded ratio ~ 27% - assert.True(t, gs.bondedRatio().Equal(rational.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) + assert.True(t, gs.bondedRatio().Equal(sdk.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) // Supplies assert.Equal(t, totalSupply, gs.TotalSupply) @@ -83,7 +83,7 @@ func TestProcessProvisions(t *testing.T) { assert.Equal(t, unbondedShares, gs.UnbondedPool) // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Equal(rational.One), "%v", gs.bondedShareExRate()) + assert.True(t, gs.bondedShareExRate().Equal(sdk.One), "%v", gs.bondedShareExRate()) initialSupply := gs.TotalSupply initialUnbonded := gs.TotalSupply - gs.BondedPool @@ -91,7 +91,7 @@ func TestProcessProvisions(t *testing.T) { // process the provisions a year for hr := 0; hr < 8766; hr++ { expInflation := nextInflation(gs, params).Round(1000000000) - expProvisions := (expInflation.Mul(rational.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() + expProvisions := (expInflation.Mul(sdk.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() startBondedPool := gs.BondedPool startTotalSupply := gs.TotalSupply processProvisions(store, gs, params) @@ -103,7 +103,7 @@ func TestProcessProvisions(t *testing.T) { //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) // initial bonded ratio ~ 35% ~ 30% increase for bonded holders - assert.True(t, gs.bondedRatio().Equal(rational.New(105906511, 305906511)), "%v", gs.bondedRatio()) + assert.True(t, gs.bondedRatio().Equal(sdk.New(105906511, 305906511)), "%v", gs.bondedRatio()) // global supply assert.Equal(t, int64(611813022), gs.TotalSupply) @@ -111,6 +111,6 @@ func TestProcessProvisions(t *testing.T) { assert.Equal(t, unbondedShares, gs.UnbondedPool) // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Mul(rational.New(bondedShares)).Equal(rational.New(211813022)), "%v", gs.bondedShareExRate()) + assert.True(t, gs.bondedShareExRate().Mul(sdk.New(bondedShares)).Equal(sdk.New(211813022)), "%v", gs.bondedShareExRate()) } diff --git a/x/stake/tx.go b/x/stake/tx.go index 13080bf9bc..8ed6089870 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -18,10 +18,11 @@ const ( ByteTxEditCandidacy = 0x56 ByteTxDelegate = 0x57 ByteTxUnbond = 0x58 - TypeTxDeclareCandidacy = stakingModuleName + "/declareCandidacy" - TypeTxEditCandidacy = stakingModuleName + "/editCandidacy" - TypeTxDelegate = stakingModuleName + "/delegate" - TypeTxUnbond = stakingModuleName + "/unbond" + + TypeTxDeclareCandidacy = "staking/declareCandidacy" + TypeTxEditCandidacy = "staking/editCandidacy" + TypeTxDelegate = "staking/delegate" + TypeTxUnbond = "staking/unbond" ) //func init() { diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go index 4324d9446a..f828782cda 100644 --- a/x/stake/tx_test.go +++ b/x/stake/tx_test.go @@ -13,8 +13,8 @@ import ( ) var ( - validator = sdk.Actor{"testChain", "testapp", []byte("addressvalidator1")} - empty sdk.Actor + validator = []byte("addressvalidator1") + empty sdk.Address coinPos = sdk.Coin{"fermion", 1000} coinZero = sdk.Coin{"fermion", 0} diff --git a/x/stake/types.go b/x/stake/types.go index ccaf7c1b07..7bf2546ff3 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -139,6 +139,8 @@ const ( // bond shares is based on the amount of coins delegated divided by the current // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. + +// XXX update to use Address as the main key NOT the pubkey type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate diff --git a/x/stake/wire.go b/x/stake/wire.go index a1b675a186..4516f89f26 100644 --- a/x/stake/wire.go +++ b/x/stake/wire.go @@ -1,7 +1,7 @@ package stake import ( - "github.com/tendermint/go-wire" + "github.com/cosmos/cosmos-sdk/wire" ) // XXX complete From dc8636390c0dbbdf32b8c49e58f597cdf14b895d Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 14 Mar 2018 19:42:50 +0100 Subject: [PATCH 24/54] refactor, staking uses addresses instead of pubkey staking refactoring working working --- docs/spec/staking/old/spec.md | 2 +- examples/basecoin/x/cool/types.go | 18 +- types/rational.go | 15 +- types/tx_msg.go | 1 + x/stake/commands/tx.go | 8 +- x/stake/errors.go | 31 +-- x/stake/handler.go | 172 +++++++---------- x/stake/handler_test.go | 79 ++++---- x/stake/mapper.go | 88 ++++----- x/stake/tx.go | 307 ++++++++++++++++++------------ x/stake/tx_test.go | 58 +++--- x/stake/types.go | 55 ++++-- 12 files changed, 451 insertions(+), 383 deletions(-) diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index bd87ec0285..7010ee153d 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -50,7 +50,7 @@ type Params struct { ReserveTax rational.Rational // Tax collected on all fees MaxVals uint16 // maximum number of validators - AllowedBondDenom string // bondable coin denomination + BondDenom string // bondable coin denomination // gas costs for txs GasDeclareCandidacy int64 diff --git a/examples/basecoin/x/cool/types.go b/examples/basecoin/x/cool/types.go index a3fa6ca48e..76e7bb2920 100644 --- a/examples/basecoin/x/cool/types.go +++ b/examples/basecoin/x/cool/types.go @@ -34,6 +34,15 @@ func (msg SetTrendMsg) String() string { return fmt.Sprintf("SetTrendMsg{Sender: %v, Cool: %v}", msg.Sender, msg.Cool) } +// Get the bytes for the message signer to sign on +func (msg SetTrendMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + // Validate Basic is used to quickly disqualify obviously invalid messages quickly func (msg SetTrendMsg) ValidateBasic() sdk.Error { if len(msg.Sender) == 0 { @@ -48,15 +57,6 @@ func (msg SetTrendMsg) ValidateBasic() sdk.Error { return nil } -// Get the bytes for the message signer to sign on -func (msg SetTrendMsg) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - //_______________________________________________________________________ // A message type to quiz how cool you are. these fields are can be entirely diff --git a/types/rational.go b/types/rational.go index 857a6696eb..907ff2f319 100644 --- a/types/rational.go +++ b/types/rational.go @@ -1,7 +1,6 @@ package types import ( - "errors" "math/big" "strconv" "strings" @@ -57,7 +56,7 @@ func NewRat(Numerator int64, Denominator ...int64) Rat { } //NewFromDecimal - create a rational from decimal string or integer string -func NewRatFromDecimal(decimalStr string) (f Rat, err error) { +func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { // first extract any negative symbol neg := false @@ -73,23 +72,23 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err error) { switch len(str) { case 1: if len(str[0]) == 0 { - return f, errors.New("not a decimal string") + return f, NewError(CodeUnknownRequest, "not a decimal string") } numStr = str[0] case 2: if len(str[0]) == 0 || len(str[1]) == 0 { - return f, errors.New("not a decimal string") + return f, NewError(CodeUnknownRequest, "not a decimal string") } numStr = str[0] + str[1] len := int64(len(str[1])) denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64() default: - return f, errors.New("not a decimal string") + return f, NewError(CodeUnknownRequest, "not a decimal string") } - num, err := strconv.Atoi(numStr) - if err != nil { - return f, err + num, errConv := strconv.Atoi(numStr) + if errConv != nil { + return f, NewError(CodeUnknownRequest, errConv.Error()) } if neg { diff --git a/types/tx_msg.go b/types/tx_msg.go index 79272f3862..16e5c59eb8 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -147,6 +147,7 @@ type StdSignMsg struct { // XXX: Alt } +// get message bytes func (msg StdSignMsg) Bytes() []byte { return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg) } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 76bf5753dd..c264dfd80a 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -107,7 +107,7 @@ func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { Details: viper.GetString(FlagDetails), } - tx := stake.NewTxDeclareCandidacy(amount, pk, description) + tx := stake.NewMsgDeclareCandidacy(amount, pk, description) return doTx(tx) } @@ -125,7 +125,7 @@ func cmdEditCandidacy(cmd *cobra.Command, args []string) error { Details: viper.GetString(FlagDetails), } - tx := stake.NewTxEditCandidacy(pk, description) + tx := stake.NewMsgEditCandidacy(pk, description) return doTx(tx) } @@ -140,7 +140,7 @@ func cmdDelegate(cmd *cobra.Command, args []string) error { return err } - tx := stake.NewTxDelegate(amount, pk) + tx := stake.NewMsgDelegate(amount, pk) return doTx(tx) } @@ -167,7 +167,7 @@ func cmdUnbond(cmd *cobra.Command, args []string) error { return err } - tx := stake.NewTxUnbond(sharesStr, pk) + tx := stake.NewMsgUnbond(sharesStr, pk) return doTx(tx) } diff --git a/x/stake/errors.go b/x/stake/errors.go index 1f5ab2fc61..fae872f1c1 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -43,46 +43,49 @@ func codeToDefaultMsg(code CodeType) string { //---------------------------------------- // Error constructors -func ErrCandidateEmpty() error { +func ErrCandidateEmpty() sdk.Error { return newError(CodeInvalidValidator, "Cannot bond to an empty candidate") } -func ErrBadBondingDenom() error { +func ErrBadBondingDenom() sdk.Error { return newError(CodeInvalidValidator, "Invalid coin denomination") } -func ErrBadBondingAmount() error { +func ErrBadBondingAmount() sdk.Error { return newError(CodeInvalidValidator, "Amount must be > 0") } -func ErrNoBondingAcct() error { +func ErrNoBondingAcct() sdk.Error { return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair") } -func ErrCommissionNegative() error { +func ErrCommissionNegative() sdk.Error { return newError(CodeInvalidValidator, "Commission must be positive") } -func ErrCommissionHuge() error { +func ErrCommissionHuge() sdk.Error { return newError(CodeInvalidValidator, "Commission cannot be more than 100%") } -func ErrBadValidatorAddr() error { +func ErrBadValidatorAddr() sdk.Error { return newError(CodeInvalidValidator, "Validator does not exist for that address") } -func ErrCandidateExistsAddr() error { +func ErrBadCandidateAddr() sdk.Error { + return newError(CodeInvalidValidator, "Candidate does not exist for that address") +} +func ErrCandidateExistsAddr() sdk.Error { return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy") } -func ErrMissingSignature() error { +func ErrMissingSignature() sdk.Error { return newError(CodeInvalidValidator, "Missing signature") } -func ErrBondNotNominated() error { +func ErrBondNotNominated() sdk.Error { return newError(CodeInvalidValidator, "Cannot bond to non-nominated account") } -func ErrNoCandidateForAddress() error { +func ErrNoCandidateForAddress() sdk.Error { return newError(CodeInvalidValidator, "Validator does not exist for that address") } -func ErrNoDelegatorForAddress() error { +func ErrNoDelegatorForAddress() sdk.Error { return newError(CodeInvalidValidator, "Delegator does not contain validator bond") } -func ErrInsufficientFunds() error { +func ErrInsufficientFunds() sdk.Error { return newError(CodeInvalidValidator, "Insufficient bond shares") } -func ErrBadRemoveValidator() error { +func ErrBadRemoveValidator() sdk.Error { return newError(CodeInvalidValidator, "Error removing validator") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 5e79dd586b..ae5dc42836 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,7 +1,6 @@ package stake import ( - "errors" "fmt" "strconv" @@ -9,28 +8,27 @@ import ( crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" ) // separated for testing -func InitState(ctx sdk.Context, mapper Mapper, key, value string) error { +func InitState(ctx sdk.Context, mapper Mapper, key, value string) sdk.Error { params := mapper.loadParams() switch key { case "allowed_bond_denom": - params.AllowedBondDenom = value + params.BondDenom = value case "max_vals", "gas_bond", "gas_unbond": i, err := strconv.Atoi(value) if err != nil { - return fmt.Errorf("input must be integer, Error: %v", err.Error()) + return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error())) } switch key { case "max_vals": if i < 0 { - return errors.New("cannot designate negative max validators") + return sdk.ErrUnknownRequest("cannot designate negative max validators") } params.MaxVals = uint16(i) case "gas_bond": @@ -53,112 +51,98 @@ func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler { params := mapper.loadParams() - res := msg.ValidateBasic().Result() - if res.Code != sdk.CodeOK { - return res - } - sender, err := getTxSender(ctx) + err := msg.ValidateBasic() if err != nil { - return + return err.Result() // TODO should also return gasUsed? } + signers := msg.GetSigners() + if len(signers) != 1 { + return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result() + } + sender := signers[0] - transact := NewTransact(ctx, ck) + transact := newTransact(ctx, sender, mapper, ck) // Run the transaction - switch _tx := tx.Unwrap().(type) { - case TxDeclareCandidacy: + switch msg := msg.(type) { + case MsgDeclareCandidacy: + res := transact.declareCandidacy(msg).Result() if !ctx.IsCheckTx() { res.GasUsed = params.GasDeclareCandidacy } - return res, transact.declareCandidacy(_tx) - case TxEditCandidacy: + return res + case MsgEditCandidacy: + res := transact.editCandidacy(msg).Result() if !ctx.IsCheckTx() { res.GasUsed = params.GasEditCandidacy } - return res, transact.editCandidacy(_tx) - case TxDelegate: + return res + case MsgDelegate: + res := transact.delegate(msg).Result() if !ctx.IsCheckTx() { res.GasUsed = params.GasDelegate } - return res, transact.delegate(_tx) - case TxUnbond: - //context with hold account permissions + return res + case MsgUnbond: + res := transact.unbond(msg).Result() if !ctx.IsCheckTx() { - params := loadParams(store) res.GasUsed = params.GasUnbond } - return res, transact.unbond(_tx) + return res default: - return sdk.ErrUnknownTxType(msgType) + return sdk.ErrTxParse("invalid message parse in staking module").Result() } - return } } -// get the sender from the ctx and ensure it matches the tx pubkey -func getTxSender(ctx sdk.Context) (sender crypto.Address, err error) { - senders := ctx.GetPermissions("", auth.NameSigs) - if len(senders) != 1 { - return sender, ErrMissingSignature() - } - return senders[0], nil -} - //_____________________________________________________________________ // common fields to all transactions type transact struct { + ctx sdk.Context sender crypto.Address mapper Mapper coinKeeper bank.CoinKeeper params Params gs *GlobalState - isCheckTx sdk.Context } func newTransact(ctx sdk.Context, sender sdk.Address, mapper Mapper, ck bank.CoinKeeper) transact { return transact{ + ctx: ctx, sender: sender, mapper: mapper, coinKeeper: ck, params: mapper.loadParams(), gs: mapper.loadGlobalState(), - isCheckTx: ctx.IsCheckTx(), } } //_____________________________________________________________________ // helper functions -// TODO move from deliver with new SDK should only be dependant on store to send coins in NEW SDK // move a candidates asset pool from bonded to unbonded pool -func (tr transact) bondedToUnbondedPool(candidate *Candidate) error { +func (tr transact) bondedToUnbondedPool(candidate *Candidate) { // replace bonded shares with unbonded shares tokens := tr.gs.removeSharesBonded(candidate.Assets) candidate.Assets = tr.gs.addTokensUnbonded(tokens) candidate.Status = Unbonded - - return tr.transfer(tr.params.HoldBonded, tr.params.HoldUnbonded, - sdk.Coins{{tr.params.AllowedBondDenom, tokens}}) } // move a candidates asset pool from unbonded to bonded pool -func (tr transact) unbondedToBondedPool(candidate *Candidate) error { +func (tr transact) unbondedToBondedPool(candidate *Candidate) { - // replace bonded shares with unbonded shares + // replace unbonded shares with bonded shares tokens := tr.gs.removeSharesUnbonded(candidate.Assets) candidate.Assets = tr.gs.addTokensBonded(tokens) candidate.Status = Bonded - - return tr.transfer(tr.params.HoldUnbonded, tr.params.HoldBonded, - sdk.Coins{{tr.params.AllowedBondDenom, tokens}}) } // return an error if the bonds coins are incorrect -func checkDenom(mapper Mapper, tx BondUpdate) error { - if tx.Bond.Denom != mapper.loadParams().AllowedBondDenom { - return fmt.Errorf("Invalid coin denomination") +func checkDenom(mapper Mapper, bond sdk.Coin) sdk.Error { + if bond.Denom != mapper.loadParams().BondDenom { + return ErrBadBondingDenom() } return nil } @@ -167,48 +151,42 @@ func checkDenom(mapper Mapper, tx BondUpdate) error { // These functions assume everything has been authenticated, // now we just perform action and save -func (tr transact) declareCandidacy(tx TxDeclareCandidacy) error { + +func (tr transact) declareCandidacy(tx MsgDeclareCandidacy) sdk.Error { // check to see if the pubkey or sender has been registered before - if tr.mapper.loadCandidate(tx.PubKey) != nil { - return fmt.Errorf("cannot bond to pubkey which is already declared candidacy"+ - " PubKey %v already registered with %v candidate address", - candidate.PubKey, candidate.Owner) + if tr.mapper.loadCandidate(tx.Address) != nil { + return ErrCandidateExistsAddr() } - err := checkDenom(tx.BondUpdate, tr.mapper) + err := checkDenom(tr.mapper, tx.Bond) if err != nil { return err } - if tr.IsCheckTx { + if tr.ctx.IsCheckTx() { return nil } - // create and save the empty candidate - bond := tr.mapper.loadCandidate(tx.PubKey) - if bond != nil { - return ErrCandidateExistsAddr() - } candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description) tr.mapper.saveCandidate(candidate) // move coins from the tr.sender account to a (self-bond) delegator account // the candidate account and global shares are updated within here - txDelegate := TxDelegate{tx.BondUpdate} + txDelegate := NewMsgDelegate(tx.Address, tx.Bond) return tr.delegateWithCandidate(txDelegate, candidate) } -func (tr transact) editCandidacy(tx TxEditCandidacy) error { +func (tr transact) editCandidacy(tx MsgEditCandidacy) sdk.Error { // candidate must already be registered - if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + if tr.mapper.loadCandidate(tx.Address) == nil { + return ErrBadCandidateAddr() } - if tr.IsCheckTx { + if tr.ctx.IsCheckTx() { return nil } // Get the pubKey bond account - candidate := tr.mapper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.Address) if candidate == nil { return ErrBondNotNominated() } @@ -234,28 +212,28 @@ func (tr transact) editCandidacy(tx TxEditCandidacy) error { return nil } -func (tr transact) delegate(tx TxDelegate) error { +func (tr transact) delegate(tx MsgDelegate) sdk.Error { - if tr.mapper.loadCandidate(tx.PubKey) == nil { // does PubKey exist - return fmt.Errorf("cannot delegate to non-existant PubKey %v", tx.PubKey) + if tr.mapper.loadCandidate(tx.Address) == nil { // does PubKey exist + return ErrBadCandidateAddr() } - err := checkDenom(tx.BondUpdate, tr.mapper) + err := checkDenom(tr.mapper, tx.Bond) if err != nil { return err } - if tr.IsCheckTx { + if tr.ctx.IsCheckTx() { return nil } // Get the pubKey bond account - candidate := tr.mapper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.Address) if candidate == nil { return ErrBondNotNominated() } return tr.delegateWithCandidate(tx, candidate) } -func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) error { +func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) sdk.Error { if candidate.Status == Revoked { //candidate has been withdrawn return ErrBondNotNominated() @@ -268,37 +246,33 @@ func (tr transact) delegateWithCandidate(tx TxDelegate, candidate *Candidate) er poolAccount = tr.params.HoldUnbonded } - // XXX refactor all steps like this into GlobalState.addBondedTokens() - // Move coins from the delegator account to the bonded pool account - err := tr.transfer(tr.sender, poolAccount, sdk.Coins{tx.Bond}) - if err != nil { - return err - } - // Get or create the delegator bond - bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) + bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) if bond == nil { bond = &DelegatorBond{ - PubKey: tx.PubKey, + PubKey: tx.Address, Shares: sdk.ZeroRat, } } // Account new shares, save - bond.Shares = bond.Shares.Add(candidate.addTokens(tx.Bond.Amount, tr.gs)) - tr.mapper.saveCandidate(candidate) + err := bond.BondTokens(candidate, tx.Bond, tr) + if err != nil { + return err + } tr.mapper.saveDelegatorBond(tr.sender, bond) + tr.mapper.saveCandidate(candidate) tr.mapper.saveGlobalState(tr.gs) return nil } -func (tr transact) unbond(tx TxUnbond) error { +func (tr transact) unbond(tx MsgUnbond) sdk.Error { // check if bond has any shares in it unbond - existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) + existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) sharesStr := viper.GetString(tx.Shares) if existingBond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares - return errors.New("no shares in account to unbond") + return ErrInsufficientFunds() } // if shares set to special case Max then we're good @@ -315,10 +289,12 @@ func (tr transact) unbond(tx TxUnbond) error { bond.Shares, tx.Shares) } } - // XXX end of old checkTx + if tr.ctx.IsCheckTx() { + return nil + } // get delegator bond - bond := tr.mapper.loadDelegatorBond(tr.sender, tx.PubKey) + bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) if bond == nil { return ErrNoDelegatorForAddress() } @@ -328,7 +304,6 @@ func (tr transact) unbond(tx TxUnbond) error { if tx.Shares == "MAX" { shares = bond.Shares } else { - var err error shares, err = sdk.NewRatFromDecimal(tx.Shares) if err != nil { return err @@ -342,7 +317,7 @@ func (tr transact) unbond(tx TxUnbond) error { bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate := tr.mapper.loadCandidate(tx.PubKey) + candidate := tr.mapper.loadCandidate(tx.Address) if candidate == nil { return ErrNoCandidateForAddress() } @@ -358,7 +333,7 @@ func (tr transact) unbond(tx TxUnbond) error { } // remove the bond - tr.mapper.removeDelegatorBond(tr.sender, tx.PubKey) + tr.mapper.removeDelegatorBond(tr.sender, tx.Address) } else { tr.mapper.saveDelegatorBond(tr.sender, bond) } @@ -373,7 +348,7 @@ func (tr transact) unbond(tx TxUnbond) error { returnCoins := candidate.removeShares(shares, tr.gs) err := tr.transfer(poolAccount, tr.sender, - sdk.Coins{{tr.params.AllowedBondDenom, returnCoins}}) + sdk.Coins{{tr.params.BondDenom, returnCoins}}) if err != nil { return err } @@ -383,10 +358,7 @@ func (tr transact) unbond(tx TxUnbond) error { // change the share types to unbonded if they were not already if candidate.Status == Bonded { - err = tr.bondedToUnbondedPool(candidate) - if err != nil { - return err - } + tr.bondedToUnbondedPool(candidate) } // lastly update the status @@ -395,7 +367,7 @@ func (tr transact) unbond(tx TxUnbond) error { // deduct shares from the candidate and save if candidate.Liabilities.IsZero() { - tr.mapper.removeCandidate(tx.PubKey) + tr.mapper.removeCandidate(tx.Address) } else { tr.mapper.saveCandidate(candidate) } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 4cae995604..4e5a1ee4ca 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -24,25 +24,24 @@ func initAccounts(n int, amount int64) ([]sdk.Address, map[string]int64) { return senders, accStore } -func newTxDeclareCandidacy(amt int64, pubKey crypto.PubKey) TxDeclareCandidacy { - return TxDeclareCandidacy{ - BondUpdate{ - PubKey: pubKey, - Bond: coin.Coin{"fermion", amt}, - }, +func newTestMsgDeclareCandidacy(amt int64, pubKey crypto.PubKey, address sdk.Address) MsgDeclareCandidacy { + return MsgDeclareCandidacy{ + MsgAddr: NewMsgAddr(address), + PubKey: pubKey, + Bond: coin.Coin{"fermion", amt}, Description{}, } } -func newTxDelegate(amt int64, pubKey crypto.PubKey) TxDelegate { - return TxDelegate{BondUpdate{ - PubKey: pubKey, - Bond: coin.Coin{"fermion", amt}, - }} +func newTestMsgDelegate(amt int64, address sdk.Address) MsgDelegate { + return MsgDelegate{ + MsgAddr: NewMsgAddr(address), + Bond: coin.Coin{"fermion", amt}, + } } -func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond { - return TxUnbond{ +func newMsgUnbond(shares string, pubKey crypto.PubKey) MsgUnbond { + return MsgUnbond{ PubKey: pubKey, Shares: shares, } @@ -50,14 +49,12 @@ func newTxUnbond(shares string, pubKey crypto.PubKey) TxUnbond { func paramsNoInflation() Params { return Params{ - HoldBonded: sdk.NewActor(stakingModuleName, []byte("77777777777777777777777777777777")), - HoldUnbonded: sdk.NewActor(stakingModuleName, []byte("88888888888888888888888888888888")), InflationRateChange: sdk.Zero, InflationMax: sdk.Zero, InflationMin: sdk.Zero, GoalBonded: sdk.New(67, 100), MaxVals: 100, - AllowedBondDenom: "fermion", + BondDenom: "fermion", GasDeclareCandidacy: 20, GasEditCandidacy: 20, GasDelegate: 20, @@ -72,7 +69,7 @@ func newTestTransact(t, sender sdk.Address, isCheckTx bool) transact { newTransact(ctx, sender, mapper, coinKeeper) } -func TestDuplicatesTxDeclareCandidacy(t *testing.T) { +func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { senders, accStore := initAccounts(2, 1000) // for accounts deliverer := newDeliver(t, senders[0], accStore) @@ -81,9 +78,9 @@ func TestDuplicatesTxDeclareCandidacy(t *testing.T) { sender: senders[0], } - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(t, got, "expected no error on runTxDeclareCandidacy") + assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // one sender can bond to two different pubKeys txDeclareCandidacy.PubKey = pks[1] @@ -97,21 +94,21 @@ func TestDuplicatesTxDeclareCandidacy(t *testing.T) { assert.NotNil(t, err, "expected error on checkTx") } -func TestIncrementsTxDelegate(t *testing.T) { +func TestIncrementsMsgDelegate(t *testing.T) { initSender := int64(1000) senders, accStore := initAccounts(1, initSender) // for accounts deliverer := newDeliver(t, senders[0], accStore) // first declare candidacy bondAmount := int64(10) - txDeclareCandidacy := newTxDeclareCandidacy(bondAmount, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(bondAmount, pks[0]) got := deliverer.declareCandidacy(txDeclareCandidacy) assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got) expectedBond := bondAmount // 1 since we send 1 at the start of loop, // just send the same txbond multiple times holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates - txDelegate := newTxDelegate(bondAmount, pks[0]) + txDelegate := newTestMsgDelegate(bondAmount, pks[0]) for i := 0; i < 5; i++ { got := deliverer.delegate(txDelegate) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) @@ -129,7 +126,7 @@ func TestIncrementsTxDelegate(t *testing.T) { } } -func TestIncrementsTxUnbond(t *testing.T) { +func TestIncrementsMsgUnbond(t *testing.T) { initSender := int64(0) senders, accStore := initAccounts(1, initSender) // for accounts deliverer := newDeliver(t, senders[0], accStore) @@ -137,7 +134,7 @@ func TestIncrementsTxUnbond(t *testing.T) { // set initial bond initBond := int64(1000) accStore[string(deliverer.sender.Address)] = initBond - got := deliverer.declareCandidacy(newTxDeclareCandidacy(initBond, pks[0])) + got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(initBond, pks[0])) assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got) // just send the same txunbond multiple times @@ -145,7 +142,7 @@ func TestIncrementsTxUnbond(t *testing.T) { // XXX use decimals here unbondShares, unbondSharesStr := int64(10), "10" - txUndelegate := newTxUnbond(unbondSharesStr, pks[0]) + txUndelegate := newMsgUnbond(unbondSharesStr, pks[0]) nUnbonds := 5 for i := 0; i < nUnbonds; i++ { got := deliverer.unbond(txUndelegate) @@ -174,7 +171,7 @@ func TestIncrementsTxUnbond(t *testing.T) { } for _, c := range errorCases { unbondShares := strconv.Itoa(int(c)) - txUndelegate := newTxUnbond(unbondShares, pks[0]) + txUndelegate := newMsgUnbond(unbondShares, pks[0]) got = deliverer.unbond(txUndelegate) assert.Error(t, got, "expected unbond tx to fail") } @@ -182,17 +179,17 @@ func TestIncrementsTxUnbond(t *testing.T) { leftBonded := initBond - unbondShares*int64(nUnbonds) // should be unable to unbond one more than we have - txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) + txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) got = deliverer.unbond(txUndelegate) assert.Error(t, got, "expected unbond tx to fail") // should be able to unbond just what we have - txUndelegate = newTxUnbond(strconv.Itoa(int(leftBonded)), pks[0]) + txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)), pks[0]) got = deliverer.unbond(txUndelegate) assert.NoError(t, got, "expected unbond tx to pass") } -func TestMultipleTxDeclareCandidacy(t *testing.T) { +func TestMultipleMsgDeclareCandidacy(t *testing.T) { initSender := int64(1000) senders, accStore := initAccounts(3, initSender) pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]} @@ -200,7 +197,7 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) { // bond them all for i, sender := range senders { - txDeclareCandidacy := newTxDeclareCandidacy(10, pubKeys[i]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pubKeys[i]) deliverer.sender = sender got := deliverer.declareCandidacy(txDeclareCandidacy) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) @@ -217,7 +214,7 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) { // unbond them all for i, sender := range senders { candidatePre := loadCandidate(deliverer.store, pubKeys[i]) - txUndelegate := newTxUnbond("10", pubKeys[i]) + txUndelegate := newMsgUnbond("10", pubKeys[i]) deliverer.sender = sender got := deliverer.unbond(txUndelegate) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) @@ -233,19 +230,19 @@ func TestMultipleTxDeclareCandidacy(t *testing.T) { } } -func TestMultipleTxDelegate(t *testing.T) { +func TestMultipleMsgDelegate(t *testing.T) { accounts, accStore := initAccounts(3, 1000) sender, delegators := accounts[0], accounts[1:] deliverer := newDeliver(t, sender, accStore) //first make a candidate - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) got := deliverer.declareCandidacy(txDeclareCandidacy) require.NoError(t, got, "expected tx to be ok, got %v", got) // delegate multiple parties for i, delegator := range delegators { - txDelegate := newTxDelegate(10, pks[0]) + txDelegate := newTestMsgDelegate(10, pks[0]) deliverer.sender = delegator got := deliverer.delegate(txDelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) @@ -257,7 +254,7 @@ func TestMultipleTxDelegate(t *testing.T) { // unbond them all for i, delegator := range delegators { - txUndelegate := newTxUnbond("10", pks[0]) + txUndelegate := newMsgUnbond("10", pks[0]) deliverer.sender = delegator got := deliverer.unbond(txUndelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) @@ -274,21 +271,21 @@ func TestVoidCandidacy(t *testing.T) { deliverer := newDeliver(t, sender, accStore) // create the candidate - txDeclareCandidacy := newTxDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) got := deliverer.declareCandidacy(txDeclareCandidacy) - require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // bond a delegator - txDelegate := newTxDelegate(10, pks[0]) + txDelegate := newTestMsgDelegate(10, pks[0]) deliverer.sender = delegator got = deliverer.delegate(txDelegate) require.NoError(t, got, "expected ok, got %v", got) // unbond the candidates bond portion - txUndelegate := newTxUnbond("10", pks[0]) + txUndelegate := newMsgUnbond("10", pks[0]) deliverer.sender = sender got = deliverer.unbond(txUndelegate) - require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // test that this pubkey cannot yet be bonded too deliverer.sender = delegator @@ -297,7 +294,7 @@ func TestVoidCandidacy(t *testing.T) { // test that the delegator can still withdraw their bonds got = deliverer.unbond(txUndelegate) - require.NoError(t, got, "expected no error on runTxDeclareCandidacy") + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // verify that the pubkey can now be reused got = deliverer.declareCandidacy(txDeclareCandidacy) diff --git a/x/stake/mapper.go b/x/stake/mapper.go index 296fe0aa32..22a9f96e53 100644 --- a/x/stake/mapper.go +++ b/x/stake/mapper.go @@ -1,8 +1,6 @@ package stake import ( - crypto "github.com/tendermint/go-crypto" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -10,9 +8,9 @@ import ( //nolint var ( // Keys for store prefixes - CandidatesPubKeysKey = []byte{0x01} // key for all candidates' pubkeys - ParamKey = []byte{0x02} // key for global parameters relating to staking - GlobalStateKey = []byte{0x03} // key for global parameters relating to staking + CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses + ParamKey = []byte{0x02} // key for global parameters relating to staking + GlobalStateKey = []byte{0x03} // key for global parameters relating to staking // Key prefixes CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate @@ -22,29 +20,29 @@ var ( DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond ) -// GetCandidateKey - get the key for the candidate with pubKey -func GetCandidateKey(pubKey crypto.PubKey) []byte { - return append(CandidateKeyPrefix, pubKey.Bytes()...) +// GetCandidateKey - get the key for the candidate with address +func GetCandidateKey(address sdk.Address) []byte { + return append(CandidateKeyPrefix, address.Bytes()...) } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(pubKey crypto.PubKey, power sdk.Rational) []byte { - b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? - return append(ValidatorKeyPrefix, append(b, pubKey.Bytes()...)...) // TODO does this need prefix if its in its own store +func GetValidatorKey(address sdk.Address, power sdk.Rational) []byte { + b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? + return append(ValidatorKeyPrefix, append(b, address.Bytes()...)...) // TODO does this need prefix if its in its own store } // GetValidatorUpdatesKey - get the key for the validator used in the power-store -func GetValidatorUpdatesKey(pubKey crypto.PubKey) []byte { - return append(ValidatorUpdatesKeyPrefix, pubKey.Bytes()...) // TODO does this need prefix if its in its own store +func GetValidatorUpdatesKey(address sdk.Address) []byte { + return append(ValidatorUpdatesKeyPrefix, address.Bytes()...) // TODO does this need prefix if its in its own store } // GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegator crypto.Address, candidate crypto.PubKey) []byte { +func GetDelegatorBondKey(delegator, candidate sdk.Address) []byte { return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) } // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { +func GetDelegatorBondKeyPrefix(delegator sdk.Address) []byte { res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) @@ -53,7 +51,7 @@ func GetDelegatorBondKeyPrefix(delegator crypto.Address) []byte { } // GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegator crypto.Address) []byte { +func GetDelegatorBondsKey(delegator sdk.Address) []byte { res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) @@ -69,20 +67,15 @@ type Mapper struct { cdc *wire.Codec } -func NewMapper(ctx sdk.Context, key sdk.StoreKey) Mapper { - cdc := wire.NewCodec() - cdc.RegisterInterface((*sdk.Rational)(nil), nil) // XXX make like crypto.RegisterWire() - cdc.RegisterConcrete(sdk.Rat{}, "rat", nil) - crypto.RegisterWire(cdc) - +func NewMapper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey) Mapper { return StakeMapper{ store: ctx.KVStore(m.key), cdc: cdc, } } -func (m Mapper) loadCandidate(pubKey crypto.PubKey) *Candidate { - b := m.store.Get(GetCandidateKey(pubKey)) +func (m Mapper) loadCandidate(address sdk.Address) *Candidate { + b := m.store.Get(GetCandidateKey(address)) if b == nil { return nil } @@ -97,28 +90,28 @@ func (m Mapper) loadCandidate(pubKey crypto.PubKey) *Candidate { func (m Mapper) saveCandidate(candidate *Candidate) { // XXX should only remove validator if we know candidate is a validator - removeValidator(m.store, candidate.PubKey) - validator := &Validator{candidate.PubKey, candidate.VotingPower} + removeValidator(m.store, candidate.Address) + validator := &Validator{candidate.Address, candidate.VotingPower} updateValidator(m.store, validator) b, err := cdc.MarshalJSON(*candidate) if err != nil { panic(err) } - m.store.Set(GetCandidateKey(candidate.PubKey), b) + m.store.Set(GetCandidateKey(candidate.Address), b) } -func (m Mapper) removeCandidate(pubKey crypto.PubKey) { +func (m Mapper) removeCandidate(address sdk.Address) { // XXX should only remove validator if we know candidate is a validator - removeValidator(m.store, pubKey) - m.store.Delete(GetCandidateKey(pubKey)) + removeValidator(m.store, address) + m.store.Delete(GetCandidateKey(address)) } //___________________________________________________________________________ -//func loadValidator(m.store sdk.KVStore, pubKey crypto.PubKey, votingPower sdk.Rational) *Validator { -//b := m.store.Get(GetValidatorKey(pubKey, votingPower)) +//func loadValidator(m.store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator { +//b := m.store.Get(GetValidatorKey(address, votingPower)) //if b == nil { //return nil //} @@ -140,25 +133,25 @@ func (m Mapper) updateValidator(validator *Validator) { } // add to the validators to update list if necessary - m.store.Set(GetValidatorUpdatesKey(validator.PubKey), b) + m.store.Set(GetValidatorUpdatesKey(validator.Address), b) // update the list ordered by voting power - m.store.Set(GetValidatorKey(validator.PubKey, validator.VotingPower), b) + m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower), b) } -func (m Mapper) removeValidator(pubKey crypto.PubKey) { +func (m Mapper) removeValidator(address sdk.Address) { //add validator with zero power to the validator updates - b, err := cdc.MarshalJSON(Validator{pubKey, sdk.ZeroRat}) + b, err := cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) if err != nil { panic(err) } - m.store.Set(GetValidatorUpdatesKey(pubKey), b) + m.store.Set(GetValidatorUpdatesKey(address), b) // now actually delete from the validator set - candidate := loadCandidate(m.store, pubKey) + candidate := loadCandidate(m.store, address) if candidate != nil { - m.store.Delete(GetValidatorKey(pubKey, candidate.VotingPower)) + m.store.Delete(GetValidatorKey(address, candidate.VotingPower)) } } @@ -243,14 +236,14 @@ func (m Mapper) loadCandidates() (candidates Candidates) { //_____________________________________________________________________ // load the pubkeys of all candidates a delegator is delegated too -func (m Mapper) loadDelegatorCandidates(delegator crypto.Address) (candidates []crypto.PubKey) { +func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs []sdk.Address) { candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator)) if candidateBytes == nil { return nil } - err := cdc.UnmarshalJSON(candidateBytes, &candidates) + err := cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) if err != nil { panic(err) } @@ -259,8 +252,7 @@ func (m Mapper) loadDelegatorCandidates(delegator crypto.Address) (candidates [] //_____________________________________________________________________ -func (m Mapper) loadDelegatorBond(delegator crypto.Address, - candidate crypto.PubKey) *DelegatorBond { +func (m Mapper) loadDelegatorBond(delegator, candidate sdk.Address) *DelegatorBond { delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate)) if delegatorBytes == nil { @@ -275,13 +267,13 @@ func (m Mapper) loadDelegatorBond(delegator crypto.Address, return bond } -func (m Mapper) saveDelegatorBond(delegator crypto.Address, +func (m Mapper) saveDelegatorBond(delegator sdk.Address, bond *DelegatorBond) { // if a new bond add to the list of bonds - if loadDelegatorBond(m.store, delegator, bond.PubKey) == nil { + if loadDelegatorBond(m.store, delegator, bond.Address) == nil { pks := loadDelegatorCandidates(m.store, delegator) - pks = append(pks, (*bond).PubKey) + pks = append(pks, (*bond).Address) b, err := cdc.MarshalJSON(pks) if err != nil { panic(err) @@ -294,11 +286,11 @@ func (m Mapper) saveDelegatorBond(delegator crypto.Address, if err != nil { panic(err) } - m.store.Set(GetDelegatorBondKey(delegator, bond.PubKey), b) + m.store.Set(GetDelegatorBondKey(delegator, bond.Address), b) //updateDelegatorBonds(store, delegator) } -func (m Mapper) removeDelegatorBond(delegator crypto.Address, candidate crypto.PubKey) { +func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidate sdk.Address) { // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds pks := loadDelegatorCandidates(m.store, delegator) diff --git a/x/stake/tx.go b/x/stake/tx.go index 8ed6089870..3bc58b0dd8 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -1,52 +1,204 @@ package stake import ( + "encoding/json" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" ) -// Tx -//-------------------------------------------------------------------------------- - -// register the tx type with its validation logic -// make sure to use the name of the handler as the prefix in the tx type, -// so it gets routed properly -const ( - ByteTxDeclareCandidacy = 0x55 - ByteTxEditCandidacy = 0x56 - ByteTxDelegate = 0x57 - ByteTxUnbond = 0x58 - - TypeTxDeclareCandidacy = "staking/declareCandidacy" - TypeTxEditCandidacy = "staking/editCandidacy" - TypeTxDelegate = "staking/delegate" - TypeTxUnbond = "staking/unbond" -) - -//func init() { -//sdk.TxMapper.RegisterImplementation(TxDeclareCandidacy{}, TypeTxDeclareCandidacy, ByteTxDeclareCandidacy) -//sdk.TxMapper.RegisterImplementation(TxEditCandidacy{}, TypeTxEditCandidacy, ByteTxEditCandidacy) -//sdk.TxMapper.RegisterImplementation(TxDelegate{}, TypeTxDelegate, ByteTxDelegate) -//sdk.TxMapper.RegisterImplementation(TxUnbond{}, TypeTxUnbond, ByteTxUnbond) -//} - //Verify interface at compile time -//var _, _, _, _ sdk.TxInner = &TxDeclareCandidacy{}, &TxEditCandidacy{}, &TxDelegate{}, &TxUnbond{} +var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} -// BondUpdate - struct for bonding or unbonding transactions -type BondUpdate struct { - PubKey crypto.PubKey `json:"pub_key"` - Bond sdk.Coin `json:"amount"` +//______________________________________________________________________ + +// MsgAddr - struct for bonding or unbonding transactions +type MsgAddr struct { + Address sdk.Address `json:"address"` +} + +func NewMsgAddr(address sdk.Address) MsgAddr { + return MsgAddr{ + Address: address, + } +} + +// nolint +func (msg MsgAddr) Type() string { return "stake" } +func (msg MsgAddr) Get(key interface{}) (value interface{}) { return nil } +func (msg MsgAddr) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } +func (msg MsgAddr) String() string { + return fmt.Sprintf("MsgAddr{Address: %v}", msg.Address) } // ValidateBasic - Check for non-empty candidate, and valid coins -func (tx BondUpdate) ValidateBasic() error { - if tx.PubKey.Empty() { +func (msg MsgAddr) ValidateBasic() sdk.Error { + if msg.Address.Empty() { return errCandidateEmpty } - coins := sdk.Coins{tx.Bond} +} + +//______________________________________________________________________ + +// MsgDeclareCandidacy - struct for unbonding transactions +type MsgDeclareCandidacy struct { + MsgAddr + Description + Bond sdk.Coin `json:"bond"` + PubKey crypto.PubKey `json:"pubkey"` +} + +func NewMsgDeclareCandidacy(bond sdk.Coin, address sdk.Address, pubkey crypto.PubKey, description Description) MsgDeclareCandidacy { + return MsgDeclareCandidacy{ + MsgAddr: NewMsgAddr(address), + Description: description, + Bond: bond, + PubKey: PubKey, + } +} + +// get the bytes for the message signer to sign on +func (msg MsgDeclareCandidacy) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { + err := MsgAddr.ValidateBasic() + if err != nil { + return err + } + err := validateCoin(msg.Bond) + if err != nil { + return err + } + empty := Description{} + if msg.Description == empty { + return fmt.Errorf("description must be included") + } + return nil +} + +//______________________________________________________________________ + +// MsgEditCandidacy - struct for editing a candidate +type MsgEditCandidacy struct { + MsgAddr + Description +} + +func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy { + return MsgEditCandidacy{ + MsgAddr: NewMsgAddr(address), + Description: description, + } +} + +// get the bytes for the message signer to sign on +func (msg MsgEditCandidacy) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { + err := MsgAddr.ValidateBasic() + if err != nil { + return err + } + empty := Description{} + if msg.Description == empty { + return fmt.Errorf("Transaction must include some information to modify") + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgDelegate struct { + MsgAddr + Bond sdk.Coin `json:"bond"` +} + +func NewMsgDelegate(address sdk.Address, bond sdk.Coin) MsgDelegate { + return MsgDelegate{ + MsgAddr: NewMsgAddr(address), + Bond: bond, + } +} + +// get the bytes for the message signer to sign on +func (msg MsgDelegate) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDelegate) ValidateBasic() sdk.Error { + err := MsgAddr.ValidateBasic() + if err != nil { + return err + } + err := validateCoin(msg.Bond) + if err != nil { + return err + } + return nil +} + +//______________________________________________________________________ + +// MsgUnbond - struct for unbonding transactions +type MsgUnbond struct { + MsgAddr + Shares string `json:"shares"` +} + +func NewMsgUnbond(shares string, address sdk.Address) MsgDelegate { + return MsgUnbond{ + MsgAddr: NewMsgAddr(address), + Shares: shares, + } +} + +// get the bytes for the message signer to sign on +func (msg MsgUnbond) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgUnbond) ValidateBasic() sdk.Error { + err := MsgAddr.ValidateBasic() + if err != nil { + return err + } + if msg.Shares { + return ErrCandidateEmpty() + } + return nil +} + +//______________________________________________________________________ +// helper + +func validateCoin(coin coin.Coin) sdk.Error { + coins := sdk.Coins{bond} if !sdk.IsValid() { return sdk.ErrInvalidCoins() } @@ -55,92 +207,3 @@ func (tx BondUpdate) ValidateBasic() error { } return nil } - -// TxDeclareCandidacy - struct for unbonding transactions -type TxDeclareCandidacy struct { - BondUpdate - Description -} - -// NewTxDeclareCandidacy - new TxDeclareCandidacy -func NewTxDeclareCandidacy(bond sdk.Coin, pubKey crypto.PubKey, description Description) sdk.Tx { - return TxDeclareCandidacy{ - BondUpdate{ - PubKey: pubKey, - Bond: bond, - }, - description, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxDeclareCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// TxEditCandidacy - struct for editing a candidate -type TxEditCandidacy struct { - PubKey crypto.PubKey `json:"pub_key"` - Description -} - -// NewTxEditCandidacy - new TxEditCandidacy -func NewTxEditCandidacy(pubKey crypto.PubKey, description Description) sdk.Tx { - return TxEditCandidacy{ - PubKey: pubKey, - Description: description, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxEditCandidacy) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// ValidateBasic - Check for non-empty candidate, -func (tx TxEditCandidacy) ValidateBasic() error { - if tx.PubKey.Empty() { - return errCandidateEmpty - } - - empty := Description{} - if tx.Description == empty { - return fmt.Errorf("Transaction must include some information to modify") - } - return nil -} - -// TxDelegate - struct for bonding transactions -type TxDelegate struct{ BondUpdate } - -// NewTxDelegate - new TxDelegate -func NewTxDelegate(bond sdk.Coin, pubKey crypto.PubKey) sdk.Tx { - return TxDelegate{BondUpdate{ - PubKey: pubKey, - Bond: bond, - }}.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxDelegate) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// TxUnbond - struct for unbonding transactions -type TxUnbond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares string `json:"amount"` -} - -// NewTxUnbond - new TxUnbond -func NewTxUnbond(shares string, pubKey crypto.PubKey) sdk.Tx { - return TxUnbond{ - PubKey: pubKey, - Shares: shares, - }.Wrap() -} - -// Wrap - Wrap a Tx as a Basecoin Tx -func (tx TxUnbond) Wrap() sdk.Tx { return sdk.Tx{tx} } - -// ValidateBasic - Check for non-empty candidate, positive shares -func (tx TxUnbond) ValidateBasic() error { - if tx.PubKey.Empty() { - return errCandidateEmpty - } - return nil -} diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go index f828782cda..156a8f120e 100644 --- a/x/stake/tx_test.go +++ b/x/stake/tx_test.go @@ -24,29 +24,40 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) -func TestBondUpdateValidateBasic(t *testing.T) { +func TestMsgAddrValidateBasic(t *testing.T) { tests := []struct { name string - PubKey crypto.PubKey - Bond sdk.Coin + address sdk.Address wantErr bool }{ - {"basic good", pks[0], coinPos, false}, - {"empty delegator", crypto.PubKey{}, coinPos, true}, - {"zero coin", pks[0], coinZero, true}, - {"neg coin", pks[0], coinNeg, true}, + {"basic good", pks[0], false}, + {"empty delegator", crypto.PubKey{}, true}, } for _, tc := range tests { - tx := TxDelegate{BondUpdate{ - PubKey: tc.PubKey, - Bond: tc.Bond, - }} + tx := NewMsgAddr(tc.address) assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) } } +func TestValidateCoin(t *testing.T) { + tests := []struct { + name string + coin sdk.Coin + wantErr bool + }{ + {"basic good", coinPos, false}, + {"zero coin", coinZero, true}, + {"neg coin", coinNeg, true}, + } + + for _, tc := range tests { + assert.Equal(t, tc.wantErr, tx.validateCoin(tc.coin) != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) + } +} + func TestAllAreTx(t *testing.T) { // make sure all types construct properly @@ -54,23 +65,20 @@ func TestAllAreTx(t *testing.T) { bondAmt := 1234321 bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)} - // Note that Wrap is only defined on BondUpdate, so when you call it, - // you lose all info on the embedding type. Please add Wrap() - // method to all the parents - txDelegate := NewTxDelegate(bond, pubKey) - _, ok := txDelegate.Unwrap().(TxDelegate) + txDelegate := NewMsgDelegate(bond, pubKey) + _, ok := txDelegate.(MsgDelegate) assert.True(t, ok, "%#v", txDelegate) - txUnbond := NewTxUnbond(strconv.Itoa(bondAmt), pubKey) - _, ok = txUnbond.Unwrap().(TxUnbond) + txUnbond := NewMsgUnbond(strconv.Itoa(bondAmt), pubKey) + _, ok = txUnbond.(MsgUnbond) assert.True(t, ok, "%#v", txUnbond) - txDecl := NewTxDeclareCandidacy(bond, pubKey, Description{}) - _, ok = txDecl.Unwrap().(TxDeclareCandidacy) + txDecl := NewMsgDeclareCandidacy(bond, pubKey, Description{}) + _, ok = txDecl.(MsgDeclareCandidacy) assert.True(t, ok, "%#v", txDecl) - txEditCan := NewTxEditCandidacy(pubKey, Description{}) - _, ok = txEditCan.Unwrap().(TxEditCandidacy) + txEditCan := NewMsgEditCandidacy(pubKey, Description{}) + _, ok = txEditCan.(MsgEditCandidacy) assert.True(t, ok, "%#v", txEditCan) } @@ -84,9 +92,9 @@ func TestSerializeTx(t *testing.T) { tests := []struct { tx sdk.Tx }{ - {NewTxUnbond(strconv.Itoa(bondAmt), pubKey)}, - {NewTxDeclareCandidacy(bond, pubKey, Description{})}, - {NewTxDeclareCandidacy(bond, pubKey, Description{})}, + {NewMsgUnbond(strconv.Itoa(bondAmt), pubKey)}, + {NewMsgDeclareCandidacy(bond, pubKey, Description{})}, + {NewMsgDeclareCandidacy(bond, pubKey, Description{})}, // {NewTxRevokeCandidacy(pubKey)}, } diff --git a/x/stake/types.go b/x/stake/types.go index 7bf2546ff3..fe7a4ebf0a 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -13,8 +13,8 @@ type Params struct { InflationMin sdk.Rational `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Rational `json:"goal_bonded"` // Goal of percent bonded atoms - MaxVals uint16 `json:"max_vals"` // maximum number of validators - AllowedBondDenom string `json:"allowed_bond_denom"` // bondable coin denomination + MaxVals uint16 `json:"max_vals"` // maximum number of validators + BondDenom string `json:"bond_denom"` // bondable coin denomination // gas costs for txs GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` @@ -30,7 +30,7 @@ func defaultParams() Params { InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), MaxVals: 100, - AllowedBondDenom: "fermion", + BondDenom: "fermion", GasDeclareCandidacy: 20, GasEditCandidacy: 20, GasDelegate: 20, @@ -92,6 +92,7 @@ func (gs *GlobalState) unbondedShareExRate() sdk.Rational { // XXX XXX XXX // expand to include the function of actually transfering the tokens +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) { issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.BondedPool += amount @@ -99,6 +100,7 @@ func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) return } +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens int64) { removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.BondedShares = gs.BondedShares.Sub(shares) @@ -106,6 +108,7 @@ func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens in return } +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rational) { issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) @@ -113,6 +116,7 @@ func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rationa return } +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rational) (removedTokens int64) { removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.UnbondedShares = gs.UnbondedShares.Sub(shares) @@ -144,7 +148,7 @@ const ( type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Owner crypto.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here Assets sdk.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares Liabilities sdk.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator @@ -160,11 +164,11 @@ type Description struct { } // NewCandidate - initialize a new candidate -func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Description) *Candidate { +func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate { return &Candidate{ Status: Unbonded, - PubKey: pubKey, - Owner: owner, + PubKey: pubKet, + Address: address, Assets: sdk.ZeroRat, Liabilities: sdk.ZeroRat, VotingPower: sdk.ZeroRat, @@ -172,8 +176,6 @@ func NewCandidate(pubKey crypto.PubKey, owner crypto.Address, description Descri } } -// XXX define candidate interface? - // get the exchange rate of global pool shares over delegator shares func (c *Candidate) delegatorShareExRate() sdk.Rational { if c.Liabilities.IsZero() { @@ -254,6 +256,37 @@ type Candidates []*Candidate // owned by one delegator, and is associated with the voting power of one // pubKey. type DelegatorBond struct { - PubKey crypto.PubKey `json:"pub_key"` - Shares sdk.Rational `json:"shares"` + Address sdk.Address `json:"pub_key"` + Shares sdk.Rational `json:"shares"` +} + +// Perform all the actions required to bond tokens to a delegator bond from their account +func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error { + + _, err := tr.coinKeeper.SubtractCoins(tr.ctx, d.Address, sdk.Coins{tokens}) + if err != nil { + return err + } + newShares = candidate.addTokens(tokens.Amount, tr.gs) + bond.Shares = bond.Shares.Add(newShares) + return nil +} + +// Perform all the actions required to bond tokens to a delegator bond from their account +func (bond *DelegatorBond) UnbondCoins(candidate *Candidate, shares int64, tr transact) sdk.Error { + + // subtract bond tokens from delegator bond + if bond.Shares.LT(shares) { + return ErrInsufficientFunds() + } + bond.Shares = bond.Shares.Sub(shares) + + returnAmount := candidate.removeShares(shares, tr.gs) + returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} + + _, err := tr.coinKeeper.AddCoins(tr.ctx, d.Address, returnCoins) + if err != nil { + return err + } + return nil } From 8e3f8319af5d6cf5df28a395a5fd4ff158ae3ef8 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Mar 2018 20:47:17 +0100 Subject: [PATCH 25/54] refactoring staking... --- x/stake/errors.go | 5 +++ x/stake/handler.go | 61 ++++++++++-------------------- x/stake/mapper.go | 94 +++++++++++++++++++++++----------------------- x/stake/tick.go | 20 +++++----- x/stake/tx.go | 34 +++++++++-------- x/stake/types.go | 62 +++++++++++++++--------------- 6 files changed, 133 insertions(+), 143 deletions(-) diff --git a/x/stake/errors.go b/x/stake/errors.go index fae872f1c1..68b6bd9a6e 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -2,6 +2,8 @@ package stake import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -43,6 +45,9 @@ func codeToDefaultMsg(code CodeType) string { //---------------------------------------- // Error constructors +func ErrNotEnoughBondShares(shares string) sdk.Error { + return newError(CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares)) +} func ErrCandidateEmpty() sdk.Error { return newError(CodeInvalidValidator, "Cannot bond to an empty candidate") } diff --git a/x/stake/handler.go b/x/stake/handler.go index ae5dc42836..d13881864f 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -1,10 +1,10 @@ package stake import ( + "bytes" "fmt" "strconv" - "github.com/spf13/viper" crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" @@ -214,7 +214,7 @@ func (tr transact) editCandidacy(tx MsgEditCandidacy) sdk.Error { func (tr transact) delegate(tx MsgDelegate) sdk.Error { - if tr.mapper.loadCandidate(tx.Address) == nil { // does PubKey exist + if tr.mapper.loadCandidate(tx.Address) == nil { return ErrBadCandidateAddr() } err := checkDenom(tr.mapper, tx.Bond) @@ -239,24 +239,17 @@ func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) s return ErrBondNotNominated() } - var poolAccount crypto.Address - if candidate.Status == Bonded { - poolAccount = tr.params.HoldBonded - } else { - poolAccount = tr.params.HoldUnbonded - } - // Get or create the delegator bond bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) if bond == nil { bond = &DelegatorBond{ - PubKey: tx.Address, - Shares: sdk.ZeroRat, + Address: tx.Address, + Shares: sdk.ZeroRat, } } // Account new shares, save - err := bond.BondTokens(candidate, tx.Bond, tr) + err := bond.BondCoins(candidate, tx.Bond, tr) if err != nil { return err } @@ -269,38 +262,34 @@ func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) s func (tr transact) unbond(tx MsgUnbond) sdk.Error { // check if bond has any shares in it unbond - existingBond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) - sharesStr := viper.GetString(tx.Shares) - if existingBond.Shares.LT(sdk.ZeroRat) { // bond shares < tx shares + bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) + if bond == nil { + return ErrNoDelegatorForAddress() + } + if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < tx shares return ErrInsufficientFunds() } // if shares set to special case Max then we're good - if sharesStr != "MAX" { + if tx.Shares != "MAX" { // test getting rational number from decimal provided - shares, err := sdk.NewRatFromDecimal(sharesStr) + shares, err := sdk.NewRatFromDecimal(tx.Shares) if err != nil { return err } // test that there are enough shares to unbond - if bond.Shares.LT(shares) { - return fmt.Errorf("not enough bond shares to unbond, have %v, trying to unbond %v", - bond.Shares, tx.Shares) + if !bond.Shares.GT(shares) { + return ErrNotEnoughBondShares(tx.Shares) } } if tr.ctx.IsCheckTx() { return nil } - // get delegator bond - bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) - if bond == nil { - return ErrNoDelegatorForAddress() - } - // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) var shares sdk.Rat + var err sdk.Error if tx.Shares == "MAX" { shares = bond.Shares } else { @@ -327,7 +316,7 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error { // if the bond is the owner of the candidate then // trigger a revoke candidacy - if tr.sender.Equals(candidate.Owner) && + if bytes.Equal(tr.sender, candidate.Address) && candidate.Status != Revoked { revokeCandidacy = true } @@ -338,20 +327,10 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error { tr.mapper.saveDelegatorBond(tr.sender, bond) } - // transfer coins back to account - var poolAccount crypto.Address - if candidate.Status == Bonded { - poolAccount = tr.params.HoldBonded - } else { - poolAccount = tr.params.HoldUnbonded - } - - returnCoins := candidate.removeShares(shares, tr.gs) - err := tr.transfer(poolAccount, tr.sender, - sdk.Coins{{tr.params.BondDenom, returnCoins}}) - if err != nil { - return err - } + // Add the coins + returnAmount := candidate.removeShares(shares, tr.gs) + returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} + tr.coinKeeper.AddCoins(tr.ctx, tr.sender, returnCoins) // lastly if an revoke candidate if necessary if revokeCandidacy { diff --git a/x/stake/mapper.go b/x/stake/mapper.go index 22a9f96e53..b763a26828 100644 --- a/x/stake/mapper.go +++ b/x/stake/mapper.go @@ -1,6 +1,8 @@ package stake import ( + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -26,7 +28,7 @@ func GetCandidateKey(address sdk.Address) []byte { } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(address sdk.Address, power sdk.Rational) []byte { +func GetValidatorKey(address sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte { b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, address.Bytes()...)...) // TODO does this need prefix if its in its own store } @@ -37,12 +39,12 @@ func GetValidatorUpdatesKey(address sdk.Address) []byte { } // GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegator, candidate sdk.Address) []byte { - return append(GetDelegatorBondKeyPrefix(delegator), candidate.Bytes()...) +func GetDelegatorBondKey(delegator, candidate sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegatorBondKeyPrefix(delegator, cdc), candidate.Bytes()...) } // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegator sdk.Address) []byte { +func GetDelegatorBondKeyPrefix(delegator sdk.Address, cdc *wire.Codec) []byte { res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) @@ -51,7 +53,7 @@ func GetDelegatorBondKeyPrefix(delegator sdk.Address) []byte { } // GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegator sdk.Address) []byte { +func GetDelegatorBondsKey(delegator sdk.Address, cdc *wire.Codec) []byte { res, err := cdc.MarshalJSON(&delegator) if err != nil { panic(err) @@ -68,8 +70,8 @@ type Mapper struct { } func NewMapper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey) Mapper { - return StakeMapper{ - store: ctx.KVStore(m.key), + return Mapper{ + store: ctx.KVStore(key), cdc: cdc, } } @@ -80,7 +82,7 @@ func (m Mapper) loadCandidate(address sdk.Address) *Candidate { return nil } candidate := new(Candidate) - err := cdc.UnmarshalJSON(b, candidate) + err := m.cdc.UnmarshalJSON(b, candidate) if err != nil { panic(err) // This error should never occur big problem if does } @@ -90,11 +92,11 @@ func (m Mapper) loadCandidate(address sdk.Address) *Candidate { func (m Mapper) saveCandidate(candidate *Candidate) { // XXX should only remove validator if we know candidate is a validator - removeValidator(m.store, candidate.Address) + m.removeValidator(candidate.Address) validator := &Validator{candidate.Address, candidate.VotingPower} - updateValidator(m.store, validator) + m.updateValidator(validator) - b, err := cdc.MarshalJSON(*candidate) + b, err := m.cdc.MarshalJSON(*candidate) if err != nil { panic(err) } @@ -104,7 +106,7 @@ func (m Mapper) saveCandidate(candidate *Candidate) { func (m Mapper) removeCandidate(address sdk.Address) { // XXX should only remove validator if we know candidate is a validator - removeValidator(m.store, address) + m.removeValidator(address) m.store.Delete(GetCandidateKey(address)) } @@ -127,7 +129,7 @@ func (m Mapper) removeCandidate(address sdk.Address) { // in the changed validator substore func (m Mapper) updateValidator(validator *Validator) { - b, err := cdc.MarshalJSON(*validator) + b, err := m.cdc.MarshalJSON(*validator) if err != nil { panic(err) } @@ -136,41 +138,41 @@ func (m Mapper) updateValidator(validator *Validator) { m.store.Set(GetValidatorUpdatesKey(validator.Address), b) // update the list ordered by voting power - m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower), b) + m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b) } func (m Mapper) removeValidator(address sdk.Address) { //add validator with zero power to the validator updates - b, err := cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) + b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) if err != nil { panic(err) } m.store.Set(GetValidatorUpdatesKey(address), b) // now actually delete from the validator set - candidate := loadCandidate(m.store, address) + candidate := m.loadCandidate(address) if candidate != nil { - m.store.Delete(GetValidatorKey(address, candidate.VotingPower)) + m.store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc)) } } // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower -func (m Mapper) getValidators(maxVal int) (validators []Validator) { +func (m Mapper) getValidators(maxVal uint16) (validators []Validator) { iterator := m.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators = make([]Validator, maxVal) for i := 0; ; i++ { - if !iterator.Valid() || i > maxVal { + if !iterator.Valid() || i > int(maxVal) { iterator.Close() break } valBytes := iterator.Value() var val Validator - err := cdc.UnmarshalJSON(valBytes, &val) + err := m.cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -191,7 +193,7 @@ func (m Mapper) getValidatorUpdates() (updates []Validator) { for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val Validator - err := cdc.UnmarshalJSON(valBytes, &val) + err := m.cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -223,7 +225,7 @@ func (m Mapper) loadCandidates() (candidates Candidates) { for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() var candidate Candidate - err := cdc.UnmarshalJSON(candidateBytes, &candidate) + err := m.cdc.UnmarshalJSON(candidateBytes, &candidate) if err != nil { panic(err) } @@ -238,12 +240,12 @@ func (m Mapper) loadCandidates() (candidates Candidates) { // load the pubkeys of all candidates a delegator is delegated too func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs []sdk.Address) { - candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator)) + candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator, m.cdc)) if candidateBytes == nil { return nil } - err := cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) + err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) if err != nil { panic(err) } @@ -254,13 +256,13 @@ func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs [ func (m Mapper) loadDelegatorBond(delegator, candidate sdk.Address) *DelegatorBond { - delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate)) + delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc)) if delegatorBytes == nil { return nil } bond := new(DelegatorBond) - err := cdc.UnmarshalJSON(delegatorBytes, bond) + err := m.cdc.UnmarshalJSON(delegatorBytes, bond) if err != nil { panic(err) } @@ -271,43 +273,43 @@ func (m Mapper) saveDelegatorBond(delegator sdk.Address, bond *DelegatorBond) { // if a new bond add to the list of bonds - if loadDelegatorBond(m.store, delegator, bond.Address) == nil { - pks := loadDelegatorCandidates(m.store, delegator) + if m.loadDelegatorBond(delegator, bond.Address) == nil { + pks := m.loadDelegatorCandidates(delegator) pks = append(pks, (*bond).Address) - b, err := cdc.MarshalJSON(pks) + b, err := m.cdc.MarshalJSON(pks) if err != nil { panic(err) } - m.store.Set(GetDelegatorBondsKey(delegator), b) + m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) } // now actually save the bond - b, err := cdc.MarshalJSON(*bond) + b, err := m.cdc.MarshalJSON(*bond) if err != nil { panic(err) } - m.store.Set(GetDelegatorBondKey(delegator, bond.Address), b) - //updateDelegatorBonds(store, delegator) + m.store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b) + //updateDelegatorBonds(store, delegator) //XXX remove? } -func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidate sdk.Address) { +func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidateAddr sdk.Address) { // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds - pks := loadDelegatorCandidates(m.store, delegator) - for i, pk := range pks { - if candidate.Equals(pk) { - pks = append(pks[:i], pks[i+1:]...) + addrs := m.loadDelegatorCandidates(delegator) + for i, addr := range addrs { + if bytes.Equal(candidateAddr, addr) { + addrs = append(addrs[:i], addrs[i+1:]...) } } - b, err := cdc.MarshalJSON(pks) + b, err := m.cdc.MarshalJSON(pks) if err != nil { panic(err) } - m.store.Set(GetDelegatorBondsKey(delegator), b) + m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) // now remove the actual bond - m.store.Delete(GetDelegatorBondKey(delegator, candidate)) - //updateDelegatorBonds(store, delegator) + m.store.Delete(GetDelegatorBondKey(delegator, candidateAddr, m.cdc)) + //updateDelegatorBonds(store, delegator) //XXX remove? } //_______________________________________________________________________ @@ -319,14 +321,14 @@ func (m Mapper) loadParams() (params Params) { return defaultParams() } - err := cdc.UnmarshalJSON(b, ¶ms) + err := m.cdc.UnmarshalJSON(b, ¶ms) if err != nil { panic(err) // This error should never occur big problem if does } return } func (m Mapper) saveParams(params Params) { - b, err := cdc.MarshalJSON(params) + b, err := m.cdc.MarshalJSON(params) if err != nil { panic(err) } @@ -342,7 +344,7 @@ func (m Mapper) loadGlobalState() (gs *GlobalState) { return initialGlobalState() } gs = new(GlobalState) - err := cdc.UnmarshalJSON(b, gs) + err := m.cdc.UnmarshalJSON(b, gs) if err != nil { panic(err) // This error should never occur big problem if does } @@ -350,7 +352,7 @@ func (m Mapper) loadGlobalState() (gs *GlobalState) { } func (m Mapper) saveGlobalState(gs *GlobalState) { - b, err := cdc.MarshalJSON(*gs) + b, err := m.cdc.MarshalJSON(*gs) if err != nil { panic(err) } diff --git a/x/stake/tick.go b/x/stake/tick.go index 3da581ae3f..3f0d38bcd7 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -6,27 +6,29 @@ import ( ) // Tick - called at the end of every block -func Tick(ctx sdk.Context, store sdk.KVStore) (change []*abci.Validator, err error) { +func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) { // retrieve params - params := loadParams(store) - gs := loadGlobalState(store) + params := m.loadParams() + gs := m.loadGlobalState() height := ctx.BlockHeight() // Process Validator Provisions // XXX right now just process every 5 blocks, in new SDK make hourly if gs.InflationLastTime+5 <= height { gs.InflationLastTime = height - processProvisions(store, gs, params) + processProvisions(m, gs, params) } - return UpdateValidatorSet(store, gs, params) + newVals := m.getValidators(params.MaxVals) + // XXX determine change from old validators, set to change + return change, nil } var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func processProvisions(store sdk.KVStore, gs *GlobalState, params Params) { +func processProvisions(m Mapper, gs *GlobalState, params Params) { gs.Inflation = nextInflation(gs, params).Round(1000000000) @@ -34,7 +36,7 @@ func processProvisions(store sdk.KVStore, gs *GlobalState, params Params) { // more bonded tokens are added proportionally to all validators the only term // which needs to be updated is the `BondedPool`. So for each previsions cycle: - provisions := gs.Inflation.Mul(sdk.New(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() + provisions := gs.Inflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() gs.BondedPool += provisions gs.TotalSupply += provisions @@ -43,7 +45,7 @@ func processProvisions(store sdk.KVStore, gs *GlobalState, params Params) { // XXX XXX XXX XXX XXX XXX XXX XXX XXX // save the params - saveGlobalState(store, gs) + m.saveGlobalState(gs) } // get the next inflation rate for the hour @@ -56,7 +58,7 @@ func nextInflation(gs *GlobalState, params Params) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.One.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) // increase the new annual inflation for this next cycle diff --git a/x/stake/tx.go b/x/stake/tx.go index 3bc58b0dd8..6cae6746f9 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -34,9 +34,10 @@ func (msg MsgAddr) String() string { // ValidateBasic - Check for non-empty candidate, and valid coins func (msg MsgAddr) ValidateBasic() sdk.Error { - if msg.Address.Empty() { - return errCandidateEmpty + if msg.Address == nil { + return ErrCandidateEmpty() } + return nil } //______________________________________________________________________ @@ -54,7 +55,7 @@ func NewMsgDeclareCandidacy(bond sdk.Coin, address sdk.Address, pubkey crypto.Pu MsgAddr: NewMsgAddr(address), Description: description, Bond: bond, - PubKey: PubKey, + PubKey: pubkey, } } @@ -69,17 +70,17 @@ func (msg MsgDeclareCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { - err := MsgAddr.ValidateBasic() + err := msg.MsgAddr.ValidateBasic() if err != nil { return err } - err := validateCoin(msg.Bond) + err = validateCoin(msg.Bond) if err != nil { return err } empty := Description{} if msg.Description == empty { - return fmt.Errorf("description must be included") + return newError(CodeInvalidInput, "description must be included") } return nil } @@ -110,13 +111,13 @@ func (msg MsgEditCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { - err := MsgAddr.ValidateBasic() + err := msg.MsgAddr.ValidateBasic() if err != nil { return err } empty := Description{} if msg.Description == empty { - return fmt.Errorf("Transaction must include some information to modify") + return newError(CodeInvalidInput, "Transaction must include some information to modify") } return nil } @@ -147,11 +148,11 @@ func (msg MsgDelegate) GetSignBytes() []byte { // quick validity check func (msg MsgDelegate) ValidateBasic() sdk.Error { - err := MsgAddr.ValidateBasic() + err := msg.MsgAddr.ValidateBasic() if err != nil { return err } - err := validateCoin(msg.Bond) + err = validateCoin(msg.Bond) if err != nil { return err } @@ -167,7 +168,7 @@ type MsgUnbond struct { } func NewMsgUnbond(shares string, address sdk.Address) MsgDelegate { - return MsgUnbond{ + return MsgDelegate{ MsgAddr: NewMsgAddr(address), Shares: shares, } @@ -184,11 +185,12 @@ func (msg MsgUnbond) GetSignBytes() []byte { // quick validity check func (msg MsgUnbond) ValidateBasic() sdk.Error { - err := MsgAddr.ValidateBasic() + err := msg.MsgAddr.ValidateBasic() if err != nil { return err } - if msg.Shares { + + if msg.Shares == "MAX" { return ErrCandidateEmpty() } return nil @@ -197,9 +199,9 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { //______________________________________________________________________ // helper -func validateCoin(coin coin.Coin) sdk.Error { - coins := sdk.Coins{bond} - if !sdk.IsValid() { +func validateCoin(coin sdk.Coin) sdk.Error { + coins := sdk.Coins{coin} + if !coins.IsValid() { return sdk.ErrInvalidCoins() } if !coins.IsPositive() { diff --git a/x/stake/types.go b/x/stake/types.go index fe7a4ebf0a..dd5272e0df 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -8,10 +8,10 @@ import ( // Params defines the high level settings for staking type Params struct { - InflationRateChange sdk.Rational `json:"inflation_rate_change"` // maximum annual change in inflation rate - InflationMax sdk.Rational `json:"inflation_max"` // maximum inflation rate - InflationMin sdk.Rational `json:"inflation_min"` // minimum inflation rate - GoalBonded sdk.Rational `json:"goal_bonded"` // Goal of percent bonded atoms + InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate + InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate + InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate + GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms MaxVals uint16 `json:"max_vals"` // maximum number of validators BondDenom string `json:"bond_denom"` // bondable coin denomination @@ -42,13 +42,13 @@ func defaultParams() Params { // GlobalState - dynamic parameters of the current state type GlobalState struct { - TotalSupply int64 `json:"total_supply"` // total supply of all tokens - BondedShares sdk.Rational `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool - UnbondedShares sdk.Rational `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool - BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens - UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates - InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time - Inflation sdk.Rational `json:"inflation"` // current annual inflation rate + TotalSupply int64 `json:"total_supply"` // total supply of all tokens + BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool + UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool + BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens + UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates + InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time + Inflation sdk.Rat `json:"inflation"` // current annual inflation rate } // XXX define globalstate interface? @@ -66,7 +66,7 @@ func initialGlobalState() *GlobalState { } // get the bond ratio of the global state -func (gs *GlobalState) bondedRatio() sdk.Rational { +func (gs *GlobalState) bondedRatio() sdk.Rat { if gs.TotalSupply > 0 { return sdk.NewRat(gs.BondedPool, gs.TotalSupply) } @@ -74,7 +74,7 @@ func (gs *GlobalState) bondedRatio() sdk.Rational { } // get the exchange rate of bonded token per issued share -func (gs *GlobalState) bondedShareExRate() sdk.Rational { +func (gs *GlobalState) bondedShareExRate() sdk.Rat { if gs.BondedShares.IsZero() { return sdk.OneRat } @@ -82,7 +82,7 @@ func (gs *GlobalState) bondedShareExRate() sdk.Rational { } // get the exchange rate of unbonded tokens held in candidates per issued share -func (gs *GlobalState) unbondedShareExRate() sdk.Rational { +func (gs *GlobalState) unbondedShareExRate() sdk.Rat { if gs.UnbondedShares.IsZero() { return sdk.OneRat } @@ -93,7 +93,7 @@ func (gs *GlobalState) unbondedShareExRate() sdk.Rational { // expand to include the function of actually transfering the tokens //XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) { +func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rat) { issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.BondedPool += amount gs.BondedShares = gs.BondedShares.Add(issuedShares) @@ -101,7 +101,7 @@ func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rational) } //XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens int64) { +func (gs *GlobalState) removeSharesBonded(shares sdk.Rat) (removedTokens int64) { removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.BondedShares = gs.BondedShares.Sub(shares) gs.BondedPool -= removedTokens @@ -109,7 +109,7 @@ func (gs *GlobalState) removeSharesBonded(shares sdk.Rational) (removedTokens in } //XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rational) { +func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rat) { issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) gs.UnbondedPool += amount @@ -117,7 +117,7 @@ func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rationa } //XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rational) (removedTokens int64) { +func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rat) (removedTokens int64) { removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares gs.UnbondedShares = gs.UnbondedShares.Sub(shares) gs.UnbondedPool -= removedTokens @@ -149,9 +149,9 @@ type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - Assets sdk.Rational `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities sdk.Rational `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares - VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator + Assets sdk.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares + Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator Description Description `json:"description"` // Description terms for the candidate } @@ -177,7 +177,7 @@ func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Descrip } // get the exchange rate of global pool shares over delegator shares -func (c *Candidate) delegatorShareExRate() sdk.Rational { +func (c *Candidate) delegatorShareExRate() sdk.Rat { if c.Liabilities.IsZero() { return sdk.OneRat } @@ -185,11 +185,11 @@ func (c *Candidate) delegatorShareExRate() sdk.Rational { } // add tokens to a candidate -func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares sdk.Rational) { +func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares sdk.Rat) { exRate := c.delegatorShareExRate() - var receivedGlobalShares sdk.Rational + var receivedGlobalShares sdk.Rat if c.Status == Bonded { receivedGlobalShares = gs.addTokensBonded(amount) } else { @@ -203,14 +203,14 @@ func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorSha } // remove shares from a candidate -func (c *Candidate) removeShares(shares sdk.Rational, gs *GlobalState) (removedTokens int64) { +func (c *Candidate) removeShares(shares sdk.Rat, gs *GlobalState) (createdCoins int64) { globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) if c.Status == Bonded { - removedTokens = gs.removeSharesBonded(globalPoolSharesToRemove) + createdCoins = gs.removeSharesBonded(globalPoolSharesToRemove) } else { - removedTokens = gs.removeSharesUnbonded(globalPoolSharesToRemove) + createdCoins = gs.removeSharesUnbonded(globalPoolSharesToRemove) } c.Assets = c.Assets.Sub(globalPoolSharesToRemove) @@ -229,8 +229,8 @@ func (c *Candidate) validator() Validator { // Validator is one of the top Candidates type Validator struct { - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - VotingPower sdk.Rational `json:"voting_power"` // Voting power if considered a validator + Address sdk.Address `json:"address"` // Address of validator + VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator } // ABCIValidator - Get the validator from a bond value @@ -256,8 +256,8 @@ type Candidates []*Candidate // owned by one delegator, and is associated with the voting power of one // pubKey. type DelegatorBond struct { - Address sdk.Address `json:"pub_key"` - Shares sdk.Rational `json:"shares"` + Address sdk.Address `json:"pub_key"` + Shares sdk.Rat `json:"shares"` } // Perform all the actions required to bond tokens to a delegator bond from their account From af6c1a3f02e73e97a8513f012b3186c3f8c4f66a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 16 Mar 2018 20:52:39 +0100 Subject: [PATCH 26/54] types/rational: use encoding/json minor fixes working on compiling --- types/errors.go | 4 +++ types/rational.go | 53 +++++++++++++++++++++++++++++---- types/rational_test.go | 66 +++++++++++++++--------------------------- x/stake/mapper.go | 2 +- x/stake/test_common.go | 19 +++++++----- x/stake/tick.go | 1 + x/stake/tx.go | 8 ++--- x/stake/types.go | 17 ++++++----- 8 files changed, 102 insertions(+), 68 deletions(-) diff --git a/types/errors.go b/types/errors.go index 1115d39376..48ca8287a0 100644 --- a/types/errors.go +++ b/types/errors.go @@ -109,6 +109,10 @@ func ErrInvalidCoins(msg string) Error { return newError(CodeInvalidCoins, msg) } +func ErrInvalidCoins(coins Coins) Error { + return newError(CodeInvalidCoins, coins.String()) +} + //---------------------------------------- // Error & sdkError diff --git a/types/rational.go b/types/rational.go index 907ff2f319..3fe1a31742 100644 --- a/types/rational.go +++ b/types/rational.go @@ -1,6 +1,9 @@ package types import ( + "bytes" + "encoding/json" + "fmt" "math/big" "strconv" "strings" @@ -13,12 +16,21 @@ import ( // __| |_ |_ // Rat - extend big.Rat +// NOTE: never use new(Rat) or else +// we will panic unmarshalling into the +// nil embedded big.Rat type Rat struct { *big.Rat `json:"rat"` } -// Rational - big Rat with additional functionality -type Rational interface { +type Rational = Rat + +// RationalInterface - big Rat with additional functionality +// NOTE: we only have one implementation of this interface +// and don't use it anywhere, but it might come in handy +// if we want to provide Rational types that include +// the units of the value in the type system. +type RationalInterface interface { GetRat() *big.Rat Num() int64 Denom() int64 @@ -200,6 +212,37 @@ func (r Rat) Round(precisionFactor int64) Rational { //return nil //} -//nolint -func (r Rat) MarshalJSON() ([]byte, error) { return r.MarshalText() } -func (r *Rat) UnmarshalJSON(data []byte) (err error) { return r.UnmarshalText(data) } +var ratCdc JSONCodec // TODO wire.Codec + +// Hack to just use json.Marshal for everything until +// we update for amino +type JSONCodec struct{} + +func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) { + return json.Marshal(o) +} + +func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error { + return json.Unmarshal(bz, o) +} + +// Wraps r.MarshalText() in quotes to make it a valid JSON string. +func (r Rat) MarshalJSON() ([]byte, error) { + bz, err := r.MarshalText() + if err != nil { + return bz, err + } + return []byte(fmt.Sprintf(`"%s"`, bz)), nil +} + +// Requires a valid JSON string - strings quotes and calls UnmarshalText +func (r *Rat) UnmarshalJSON(data []byte) (err error) { + quote := []byte(`"`) + if len(data) < 2 || + !bytes.HasPrefix(data, quote) || + !bytes.HasSuffix(data, quote) { + return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string") + } + data = bytes.Trim(data, `"`) + return r.UnmarshalText(data) +} diff --git a/types/rational_test.go b/types/rational_test.go index 9376a8e463..3794627789 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -1,7 +1,6 @@ package types import ( - "encoding/json" "math/big" "testing" @@ -193,41 +192,44 @@ func TestRound(t *testing.T) { } func TestZeroSerializationJSON(t *testing.T) { - var r Rat - err := json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":1}"), &r) + r := NewRat(0, 1) + err := r.UnmarshalJSON([]byte(`"0/1"`)) assert.Nil(t, err) - err = json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":0}"), &r) + err = r.UnmarshalJSON([]byte(`"0/0"`)) assert.NotNil(t, err) - err = json.Unmarshal([]byte("{\"numerator\":1,\"denominator\":0}"), &r) + err = r.UnmarshalJSON([]byte(`"1/0"`)) assert.NotNil(t, err) - err = json.Unmarshal([]byte("{}"), &r) + err = r.UnmarshalJSON([]byte(`"{}"`)) assert.NotNil(t, err) } func TestSerializationJSON(t *testing.T) { r := NewRat(1, 3) - rMarshal, err := json.Marshal(r) + bz, err := r.MarshalText() require.Nil(t, err) - var rUnmarshal Rat - err = json.Unmarshal(rMarshal, &rUnmarshal) + r2 := NewRat(0, 1) + err = r2.UnmarshalText(bz) require.Nil(t, err) - assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } func TestSerializationGoWire(t *testing.T) { r := NewRat(1, 3) - rMarshal, err := ratCdc.MarshalJSON(r) + bz, err := ratCdc.MarshalJSON(r) require.Nil(t, err) - var rUnmarshal Rat - err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) + bz, err = r.MarshalJSON() require.Nil(t, err) - assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal) + r2 := NewRat(0, 1) + err = ratCdc.UnmarshalJSON(bz, &r2) + require.Nil(t, err) + + assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) } type testEmbedStruct struct { @@ -237,38 +239,18 @@ type testEmbedStruct struct { } func TestEmbeddedStructSerializationGoWire(t *testing.T) { - r := testEmbedStruct{"foo", 10, NewRat(1, 3)} + obj := testEmbedStruct{"foo", 10, NewRat(1, 3)} - rMarshal, err := ratCdc.MarshalJSON(r) + bz, err := ratCdc.MarshalJSON(obj) require.Nil(t, err) - var rUnmarshal testEmbedStruct - err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) + var obj2 testEmbedStruct + obj2.Field3 = NewRat(0, 1) // ... needs to be initialized + err = ratCdc.UnmarshalJSON(bz, &obj2) require.Nil(t, err) - assert.Equal(t, r.Field1, rUnmarshal.Field1) - assert.Equal(t, r.Field2, rUnmarshal.Field2) - assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) + assert.Equal(t, obj.Field1, obj2.Field1) + assert.Equal(t, obj.Field2, obj2.Field2) + assert.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2) } - -type testEmbedInterface struct { - Field1 string `json:"f1"` - Field2 int `json:"f2"` - Field3 Rational `json:"f3"` -} - -func TestEmbeddedInterfaceSerializationGoWire(t *testing.T) { - r := testEmbedInterface{"foo", 10, NewRat(1, 3)} - - rMarshal, err := ratCdc.MarshalJSON(r) - require.Nil(t, err) - - var rUnmarshal testEmbedInterface - err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal) - require.Nil(t, err) - - assert.Equal(t, r.Field1, rUnmarshal.Field1) - assert.Equal(t, r.Field2, rUnmarshal.Field2) - assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal) -} diff --git a/x/stake/mapper.go b/x/stake/mapper.go index b763a26828..caebe84999 100644 --- a/x/stake/mapper.go +++ b/x/stake/mapper.go @@ -301,7 +301,7 @@ func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidateAddr sdk.Add addrs = append(addrs[:i], addrs[i+1:]...) } } - b, err := m.cdc.MarshalJSON(pks) + b, err := m.cdc.MarshalJSON(addrs) if err != nil { panic(err) } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 997b55102a..e04151046d 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -11,6 +11,7 @@ import ( crypto "github.com/tendermint/go-crypto" dbm "github.com/tendermint/tmlibs/db" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -21,18 +22,18 @@ func subspace(prefix []byte) (start, end []byte) { return prefix, end } -func createTestInput(t *testing.T, isCheckTx bool) (store sdk.KVStore, ctx sdk.Context, key sdk.StoreKey) { +func createTestInput(t *testing.T, isCheckTx bool) (sdk.KVStore, sdk.Context, sdk.StoreKey) { db := dbm.NewMemDB() - key = sdk.NewKVStoreKey("stake") + key := sdk.NewKVStoreKey("stake") ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) - ctx = sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) - store = ms.GetKVStore(key) - return + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) + store := ms.GetKVStore(key) + return store, ctx, key } func newAddrs(n int) (addrs []crypto.Address) { @@ -50,7 +51,7 @@ func newPubKey(pk string) (res crypto.PubKey) { //res, err = crypto.PubKeyFromBytes(pkBytes) var pkEd crypto.PubKeyEd25519 copy(pkEd[:], pkBytes[:]) - return pkEd + return pkEd.Wrap() } // dummy pubkeys used for testing @@ -74,7 +75,7 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6 c := &Candidate{ Status: Unbonded, PubKey: pks[i], - Owner: addrs[i], + Address: addrs[i], Assets: sdk.NewRat(amts[i]), Liabilities: sdk.NewRat(amts[i]), VotingPower: sdk.NewRat(amts[i]), @@ -83,12 +84,14 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6 } } +func saveCandidate(store sdk.KVStore, c *Candidate) {} // TODO + func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { for i := 0; i < len(addrs); i++ { c := &Candidate{ Status: Unbonded, PubKey: pks[i], - Owner: addrs[i], + Address: addrs[i], Assets: sdk.ZeroRat, Liabilities: sdk.ZeroRat, VotingPower: sdk.ZeroRat, diff --git a/x/stake/tick.go b/x/stake/tick.go index 3f0d38bcd7..4b206f4dcc 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -22,6 +22,7 @@ func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) { newVals := m.getValidators(params.MaxVals) // XXX determine change from old validators, set to change + _ = newVals return change, nil } diff --git a/x/stake/tx.go b/x/stake/tx.go index 6cae6746f9..5cc3de6d39 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -167,10 +167,10 @@ type MsgUnbond struct { Shares string `json:"shares"` } -func NewMsgUnbond(shares string, address sdk.Address) MsgDelegate { +func NewMsgUnbond(bond sdk.Coin, address sdk.Address) MsgDelegate { return MsgDelegate{ MsgAddr: NewMsgAddr(address), - Shares: shares, + Bond: bond, // Shares: shares, } } @@ -202,10 +202,10 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { func validateCoin(coin sdk.Coin) sdk.Error { coins := sdk.Coins{coin} if !coins.IsValid() { - return sdk.ErrInvalidCoins() + return sdk.ErrInvalidCoins(coins) } if !coins.IsPositive() { - return fmt.Errorf("Amount must be > 0") + return sdk.ErrInvalidCoins(coins) // XXX: add "Amount must be > 0" ? } return nil } diff --git a/x/stake/types.go b/x/stake/types.go index dd5272e0df..698c7813d8 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -2,7 +2,6 @@ package stake import ( sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" ) @@ -167,7 +166,7 @@ type Description struct { func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate { return &Candidate{ Status: Unbonded, - PubKey: pubKet, + PubKey: pubKey, Address: address, Assets: sdk.ZeroRat, Liabilities: sdk.ZeroRat, @@ -222,7 +221,7 @@ func (c *Candidate) removeShares(shares sdk.Rat, gs *GlobalState) (createdCoins // Should only be called when the Candidate qualifies as a validator. func (c *Candidate) validator() Validator { return Validator{ - PubKey: c.PubKey, + Address: c.Address, // XXX !!! VotingPower: c.VotingPower, } } @@ -234,8 +233,9 @@ type Validator struct { } // ABCIValidator - Get the validator from a bond value +/* TODO func (v Validator) ABCIValidator() (*abci.Validator, error) { - pkBytes, err := cdc.MarshalBinary(v.PubKey) + pkBytes, err := wire.MarshalBinary(v.PubKey) if err != nil { return nil, err } @@ -244,6 +244,7 @@ func (v Validator) ABCIValidator() (*abci.Validator, error) { Power: v.VotingPower.Evaluate(), }, nil } +*/ //_________________________________________________________________________ @@ -263,11 +264,11 @@ type DelegatorBond struct { // Perform all the actions required to bond tokens to a delegator bond from their account func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error { - _, err := tr.coinKeeper.SubtractCoins(tr.ctx, d.Address, sdk.Coins{tokens}) + _, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens}) if err != nil { return err } - newShares = candidate.addTokens(tokens.Amount, tr.gs) + newShares := candidate.addTokens(tokens.Amount, tr.gs) bond.Shares = bond.Shares.Add(newShares) return nil } @@ -277,14 +278,14 @@ func (bond *DelegatorBond) UnbondCoins(candidate *Candidate, shares int64, tr tr // subtract bond tokens from delegator bond if bond.Shares.LT(shares) { - return ErrInsufficientFunds() + return sdk.ErrInsufficientFunds("") // TODO } bond.Shares = bond.Shares.Sub(shares) returnAmount := candidate.removeShares(shares, tr.gs) returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - _, err := tr.coinKeeper.AddCoins(tr.ctx, d.Address, returnCoins) + _, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins) if err != nil { return err } From 865b1168379b1d03e6377009174e0708183c83da Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 16 Mar 2018 21:36:16 +0100 Subject: [PATCH 27/54] move Bond/UnbondCoins to transact working ... --- x/stake/commands/query.go | 313 +++++++++++++++++++------------------- x/stake/commands/tx.go | 42 +++-- x/stake/handler.go | 33 +++- x/stake/handler_test.go | 8 +- x/stake/tx.go | 13 +- x/stake/types.go | 31 ---- 6 files changed, 228 insertions(+), 212 deletions(-) diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index 46fc029526..6523cc75b8 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" @@ -13,8 +12,8 @@ import ( crypto "github.com/tendermint/go-crypto" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix + "github.com/cosmos/cosmos-sdk/client/builder" + "github.com/cosmos/cosmos-sdk/wire" // XXX fix "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -26,189 +25,183 @@ func PrefixedKey(app string, key []byte) []byte { //nolint var ( - CmdQueryCandidates = &cobra.Command{ - Use: "candidates", - Short: "Query for the set of validator-candidates pubkeys", - RunE: cmdQueryCandidates, - } - - CmdQueryCandidate = &cobra.Command{ - Use: "candidate", - Short: "Query a validator-candidate account", - RunE: cmdQueryCandidate, - } - - CmdQueryDelegatorBond = &cobra.Command{ - Use: "delegator-bond", - Short: "Query a delegators bond based on address and candidate pubkey", - RunE: cmdQueryDelegatorBond, - } - - CmdQueryDelegatorCandidates = &cobra.Command{ - Use: "delegator-candidates", - RunE: cmdQueryDelegatorCandidates, - Short: "Query all delegators candidates' pubkeys based on address", - } - - FlagDelegatorAddress = "delegator-address" + fsValAddr = flag.NewFlagSet("", flag.ContinueOnError) + fsDelAddr = flag.NewFlagSet("", flag.ContinueOnError) + FlagValidatorAddr = "address" + FlagDelegatorAddr = "delegator-address" ) func init() { //Add Flags - fsPk := flag.NewFlagSet("", flag.ContinueOnError) - fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") - fsAddr := flag.NewFlagSet("", flag.ContinueOnError) - fsAddr.String(FlagDelegatorAddress, "", "Delegator Hex Address") + fsValAddr.String(FlagValidatorAddr, "", "Address of the validator/candidate") + fsDelAddr.String(FlagDelegatorAddr, "", "Delegator hex address") - CmdQueryCandidate.Flags().AddFlagSet(fsPk) - CmdQueryDelegatorBond.Flags().AddFlagSet(fsPk) - CmdQueryDelegatorBond.Flags().AddFlagSet(fsAddr) - CmdQueryDelegatorCandidates.Flags().AddFlagSet(fsAddr) } -// XXX move to common directory in client helpers -func makeQuery(key, storeName string) (res []byte, err error) { +// create command to query for all candidates +func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command { + cmd := &cobra.Command{ + Use: "candidates", + Short: "Query for the set of validator-candidates pubkeys", + RunE: func(cmd *cobra.Command, args []string) error { - path := fmt.Sprintf("/%s/key", a.storeName) + var pks []crypto.PubKey - uri := viper.GetString(client.FlagNode) - if uri == "" { - return res, errors.New("Must define which node to query with --node") - } - node := client.GetNode(uri) + prove := !viper.GetBool(client.FlagTrustNode) + key := PrefixedKey(stake.Name, stake.CandidatesAddrKey) - opts := rpcclient.ABCIQueryOptions{ - Height: viper.GetInt64(client.FlagHeight), - Trusted: viper.GetBool(client.FlagTrustNode), + res, err := builder.Query(key, storeName) + if err != nil { + return err + } + + // parse out the candidates + candidates := new(stake.Candidates) + err = cdc.UnmarshalJSON(res, candidates) + if err != nil { + return err + } + output, err := json.MarshalIndent(candidates, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, } - result, err := node.ABCIQueryWithOptions(path, key, opts) - if err != nil { - return res, err - } - resp := result.Response - if resp.Code != uint32(0) { - return res, errors.Errorf("Query failed: (%d) %s", resp.Code, resp.Log) - } - return resp.val, nil + + cmd.Flags().AddFlagSet(fsDelAddr) + return cmd } -func cmdQueryCandidates(cmd *cobra.Command, args []string) error { +// get the command to query a candidate +func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command { + cmd := &cobra.Command{ + Use: "candidate", + Short: "Query a validator-candidate account", + RunE: func(cmd *cobra.Command, args []string) error { - var pks []crypto.PubKey + addr, err := GetAddress(viper.GetString(FlagValidatorAddr)) + if err != nil { + return err + } - prove := !viper.GetBool(client.FlagTrustNode) - key := PrefixedKey(stake.Name(), stake.CandidatesPubKeysKey) + prove := !viper.GetBool(client.FlagTrustNode) + key := PrefixedKey(stake.Name, stake.GetCandidateKey(addr)) - res, err := makeQuery(key, "gaia-store-name") // XXX move gaia store name out of here - if err != nil { - return err + res, err := builder.Query(key, storeName) + if err != nil { + return err + } + + // parse out the candidate + candidate := new(stake.Candidate) + err = cdc.UnmarshalBinary(res, candidate) + if err != nil { + return err + } + output, err := json.MarshalIndent(candidate, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, } - // parse out the candidates - candidates := new(stake.Candidates) - cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module - err = cdc.UnmarshalBinary(res, candidates) - if err != nil { - return err - } - output, err := json.MarshalIndent(candidates, "", " ") - if err != nil { - return err - } - fmt.Println(string(output)) - return nil - - // TODO output with proofs / machine parseable etc. + cmd.Flags().AddFlagSet(fsValAddr) + return cmd } -func cmdQueryCandidate(cmd *cobra.Command, args []string) error { +// get the command to query a single delegator bond +func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegator-bond", + Short: "Query a delegators bond based on address and candidate pubkey", + RunE: func(cmd *cobra.Command, args []string) error { - var candidate stake.Candidate + addr, err := GetAddress(viper.GetString(FlagValidatorAddr)) + if err != nil { + return err + } - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err + bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddr)) + if err != nil { + return err + } + delegator := crypto.Address(bz) + + prove := !viper.GetBool(client.FlagTrustNode) + key := PrefixedKey(stake.Name, stake.GetDelegatorBondKey(delegator, addr, cdc)) + + res, err := builder.Query(key, storeName) + if err != nil { + return err + } + + // parse out the bond + var bond stake.DelegatorBond + err = cdc.UnmarshalBinary(res, bond) + if err != nil { + return err + } + output, err := json.MarshalIndent(bond, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, } - prove := !viper.GetBool(client.FlagTrustNode) - key := PrefixedKey(stake.Name(), stake.GetCandidateKey(pk)) - - // parse out the candidate - candidate := new(stake.Candidate) - cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module - err = cdc.UnmarshalBinary(res, candidate) - if err != nil { - return err - } - output, err := json.MarshalIndent(candidate, "", " ") - if err != nil { - return err - } - fmt.Println(string(output)) - return nil - - // TODO output with proofs / machine parseable etc. + cmd.Flags().AddFlagSet(fsValAddr) + cmd.Flags().AddFlagSet(fsDelAddr) + return cmd } -func cmdQueryDelegatorBond(cmd *cobra.Command, args []string) error { +// get the command to query all the candidates bonded to a delegator +func GetCmdQueryDelegatorBonds(cdc *wire.Codec, storeName string) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegator-candidates", + Short: "Query all delegators candidates' pubkeys based on address", + RunE: func(cmd *cobra.Command, args []string) error { - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err + bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddr)) + if err != nil { + return err + } + delegator := crypto.Address(bz) + + prove := !viper.GetBool(client.FlagTrustNode) + key := PrefixedKey(stake.Name, stake.GetDelegatorBondsKey(delegator, cdc)) + + res, err := builder.Query(key, storeName) + if err != nil { + return err + } + + // parse out the candidates list + var candidates []crypto.PubKey + err = cdc.UnmarshalBinary(res, candidates) + if err != nil { + return err + } + output, err := json.MarshalIndent(candidates, "", " ") + if err != nil { + return err + } + fmt.Println(string(output)) + return nil + + // TODO output with proofs / machine parseable etc. + }, } - - bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddress)) - if err != nil { - return err - } - delegator := crypto.Address(bz) - delegator = coin.ChainAddr(delegator) - - prove := !viper.GetBool(client.FlagTrustNode) - key := PrefixedKey(stake.Name(), stake.GetDelegatorBondKey(delegator, pk)) - - // parse out the bond - var bond stake.DelegatorBond - cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module - err = cdc.UnmarshalBinary(res, bond) - if err != nil { - return err - } - output, err := json.MarshalIndent(bond, "", " ") - if err != nil { - return err - } - fmt.Println(string(output)) - return nil - - // TODO output with proofs / machine parseable etc. -} - -func cmdQueryDelegatorCandidates(cmd *cobra.Command, args []string) error { - - bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddress)) - if err != nil { - return err - } - delegator := crypto.Address(bz) - delegator = coin.ChainAddr(delegator) - - prove := !viper.GetBool(client.FlagTrustNode) - key := PrefixedKey(stake.Name(), stake.GetDelegatorBondsKey(delegator)) - - // parse out the candidates list - var candidates []crypto.PubKey - cdc := app.MakeTxCodec() // XXX create custom Tx for Staking Module - err = cdc.UnmarshalBinary(res, candidates) - if err != nil { - return err - } - output, err := json.MarshalIndent(candidates, "", " ") - if err != nil { - return err - } - fmt.Println(string(output)) - return nil - - // TODO output with proofs / machine parseable etc. + cmd.Flags().AddFlagSet(fsDelAddr) + return cmd } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index c264dfd80a..de3e4c848c 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -18,9 +18,10 @@ import ( // nolint const ( - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" + FlagAddress = "address" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagShares = "shares" FlagMoniker = "moniker" FlagIdentity = "keybase-sig" @@ -91,6 +92,11 @@ func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { return err } + addr, err := GetAddress(viper.GetString(FlagAddress)) + if err != nil { + return err + } + pk, err := GetPubKey(viper.GetString(FlagPubKey)) if err != nil { return err @@ -107,13 +113,13 @@ func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { Details: viper.GetString(FlagDetails), } - tx := stake.NewMsgDeclareCandidacy(amount, pk, description) + tx := stake.NewMsgDeclareCandidacy(addr, pk, amount, description) return doTx(tx) } func cmdEditCandidacy(cmd *cobra.Command, args []string) error { - pk, err := GetPubKey(viper.GetString(FlagPubKey)) + addr, err := GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } @@ -125,7 +131,7 @@ func cmdEditCandidacy(cmd *cobra.Command, args []string) error { Details: viper.GetString(FlagDetails), } - tx := stake.NewMsgEditCandidacy(pk, description) + tx := stake.NewMsgEditCandidacy(addr, description) return doTx(tx) } @@ -135,12 +141,12 @@ func cmdDelegate(cmd *cobra.Command, args []string) error { return err } - pk, err := GetPubKey(viper.GetString(FlagPubKey)) + addr, err := GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } - tx := stake.NewMsgDelegate(amount, pk) + tx := stake.NewMsgDelegate(addr, amount) return doTx(tx) } @@ -162,15 +168,17 @@ func cmdUnbond(cmd *cobra.Command, args []string) error { } } - pk, err := GetPubKey(viper.GetString(FlagPubKey)) + addr, err := GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } - tx := stake.NewMsgUnbond(sharesStr, pk) + tx := stake.NewMsgUnbond(addr, sharesStr) return doTx(tx) } +//______________________________________________________________________________________ + // GetPubKey - create the pubkey from a pubkey string func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { @@ -193,7 +201,19 @@ func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { return } -//-------------------------------------------------------------------- +// GetPubKey - create an Address from a pubkey string +func GetAddress(Address string) (addr sdk.Address, err error) { + if len(Address) == 0 { + return addr, errors.New("must use provide address") + } + bz, err := hex.DecodeString(addr) + if err != nil { + return nil, err + } + return sdk.Address(bz), nil +} + +//______________________________________________________________________________________ // XXX consolidate to client func doTx(tx []byte) { diff --git a/x/stake/handler.go b/x/stake/handler.go index d13881864f..402d3e2f18 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -249,7 +249,7 @@ func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) s } // Account new shares, save - err := bond.BondCoins(candidate, tx.Bond, tr) + err := tr.BondCoins(bond, candidate, tx.Bond) if err != nil { return err } @@ -259,6 +259,37 @@ func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) s return nil } +// Perform all the actions required to bond tokens to a delegator bond from their account +func (tr *transact) BondCoins(bond *DelegatorBond, candidate *Candidate, tokens sdk.Coin) sdk.Error { + + _, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens}) + if err != nil { + return err + } + newShares := candidate.addTokens(tokens.Amount, tr.gs) + bond.Shares = bond.Shares.Add(newShares) + return nil +} + +// Perform all the actions required to bond tokens to a delegator bond from their account +func (tr *transact) UnbondCoins(bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error { + + // subtract bond tokens from delegator bond + if bond.Shares.LT(shares) { + return sdk.ErrInsufficientFunds("") // TODO + } + bond.Shares = bond.Shares.Sub(shares) + + returnAmount := candidate.removeShares(shares, tr.gs) + returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} + + _, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins) + if err != nil { + return err + } + return nil +} + func (tr transact) unbond(tx MsgUnbond) sdk.Error { // check if bond has any shares in it unbond diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 4e5a1ee4ca..0c26f8a242 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -17,7 +17,7 @@ import ( func initAccounts(n int, amount int64) ([]sdk.Address, map[string]int64) { accStore := map[string]int64{} - senders := newActors(n) + senders := newAddrs(n) for _, sender := range senders { accStore[string(sender.Address)] = amount } @@ -49,9 +49,9 @@ func newMsgUnbond(shares string, pubKey crypto.PubKey) MsgUnbond { func paramsNoInflation() Params { return Params{ - InflationRateChange: sdk.Zero, - InflationMax: sdk.Zero, - InflationMin: sdk.Zero, + InflationRateChange: sdk.ZeroRat, + InflationMax: sdk.ZeroRat, + InflationMin: sdk.ZeroRat, GoalBonded: sdk.New(67, 100), MaxVals: 100, BondDenom: "fermion", diff --git a/x/stake/tx.go b/x/stake/tx.go index 5cc3de6d39..7104b92a8b 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -8,6 +8,9 @@ import ( crypto "github.com/tendermint/go-crypto" ) +// name to idetify transaction types +var Name = "stake" + //Verify interface at compile time var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} @@ -25,7 +28,7 @@ func NewMsgAddr(address sdk.Address) MsgAddr { } // nolint -func (msg MsgAddr) Type() string { return "stake" } +func (msg MsgAddr) Type() string { return Name } func (msg MsgAddr) Get(key interface{}) (value interface{}) { return nil } func (msg MsgAddr) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } func (msg MsgAddr) String() string { @@ -50,7 +53,7 @@ type MsgDeclareCandidacy struct { PubKey crypto.PubKey `json:"pubkey"` } -func NewMsgDeclareCandidacy(bond sdk.Coin, address sdk.Address, pubkey crypto.PubKey, description Description) MsgDeclareCandidacy { +func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy { return MsgDeclareCandidacy{ MsgAddr: NewMsgAddr(address), Description: description, @@ -167,10 +170,10 @@ type MsgUnbond struct { Shares string `json:"shares"` } -func NewMsgUnbond(bond sdk.Coin, address sdk.Address) MsgDelegate { - return MsgDelegate{ +func NewMsgUnbond(address sdk.Address, shares string) MsgUnbond { + return MsgUnbond{ MsgAddr: NewMsgAddr(address), - Bond: bond, // Shares: shares, + Shares: shares, } } diff --git a/x/stake/types.go b/x/stake/types.go index 698c7813d8..2ffc58ede1 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -260,34 +260,3 @@ type DelegatorBond struct { Address sdk.Address `json:"pub_key"` Shares sdk.Rat `json:"shares"` } - -// Perform all the actions required to bond tokens to a delegator bond from their account -func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error { - - _, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens}) - if err != nil { - return err - } - newShares := candidate.addTokens(tokens.Amount, tr.gs) - bond.Shares = bond.Shares.Add(newShares) - return nil -} - -// Perform all the actions required to bond tokens to a delegator bond from their account -func (bond *DelegatorBond) UnbondCoins(candidate *Candidate, shares int64, tr transact) sdk.Error { - - // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { - return sdk.ErrInsufficientFunds("") // TODO - } - bond.Shares = bond.Shares.Sub(shares) - - returnAmount := candidate.removeShares(shares, tr.gs) - returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - - _, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins) - if err != nil { - return err - } - return nil -} From 3cb5bdb166d152faacac38be32f2359287858c5d Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 17 Mar 2018 17:22:11 +0100 Subject: [PATCH 28/54] staking non-tests go files compile --- x/stake/commands/query.go | 7 - x/stake/commands/tx.go | 303 +++++++++++++++++++------------------- 2 files changed, 152 insertions(+), 158 deletions(-) diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index 6523cc75b8..6740ea812d 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -11,7 +11,6 @@ import ( crypto "github.com/tendermint/go-crypto" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/wire" // XXX fix "github.com/cosmos/cosmos-sdk/x/stake" @@ -45,9 +44,6 @@ func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command { Short: "Query for the set of validator-candidates pubkeys", RunE: func(cmd *cobra.Command, args []string) error { - var pks []crypto.PubKey - - prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name, stake.CandidatesAddrKey) res, err := builder.Query(key, storeName) @@ -88,7 +84,6 @@ func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command { return err } - prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name, stake.GetCandidateKey(addr)) res, err := builder.Query(key, storeName) @@ -135,7 +130,6 @@ func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command } delegator := crypto.Address(bz) - prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name, stake.GetDelegatorBondKey(delegator, addr, cdc)) res, err := builder.Query(key, storeName) @@ -178,7 +172,6 @@ func GetCmdQueryDelegatorBonds(cdc *wire.Codec, storeName string) *cobra.Command } delegator := crypto.Address(bz) - prove := !viper.GetBool(client.FlagTrustNode) key := PrefixedKey(stake.Name, stake.GetDelegatorBondsKey(delegator, cdc)) res, err := builder.Query(key, storeName) diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index de3e4c848c..571a346175 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -11,8 +11,9 @@ import ( crypto "github.com/tendermint/go-crypto" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -29,152 +30,183 @@ const ( FlagDetails = "details" ) -// nolint +// common flagsets to add to various functions var ( - CmdDeclareCandidacy = &cobra.Command{ - Use: "declare-candidacy", - Short: "create new validator-candidate account and delegate some coins to it", - RunE: cmdDeclareCandidacy, - } - CmdEditCandidacy = &cobra.Command{ - Use: "edit-candidacy", - Short: "edit and existing validator-candidate account", - RunE: cmdEditCandidacy, - } - CmdDelegate = &cobra.Command{ - Use: "delegate", - Short: "delegate coins to an existing validator/candidate", - RunE: cmdDelegate, - } - CmdUnbond = &cobra.Command{ - Use: "unbond", - Short: "unbond coins from a validator/candidate", - RunE: cmdUnbond, - } + fsPk = flag.NewFlagSet("", flag.ContinueOnError) + fsAmount = flag.NewFlagSet("", flag.ContinueOnError) + fsShares = flag.NewFlagSet("", flag.ContinueOnError) + fsCandidate = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { - - // define the flags - fsPk := flag.NewFlagSet("", flag.ContinueOnError) fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate") - - fsAmount := flag.NewFlagSet("", flag.ContinueOnError) fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond") - - fsShares := flag.NewFlagSet("", flag.ContinueOnError) fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)") - - fsCandidate := flag.NewFlagSet("", flag.ContinueOnError) fsCandidate.String(FlagMoniker, "", "validator-candidate name") fsCandidate.String(FlagIdentity, "", "optional keybase signature") fsCandidate.String(FlagWebsite, "", "optional website") fsCandidate.String(FlagDetails, "", "optional detailed description space") - - // add the flags - CmdDelegate.Flags().AddFlagSet(fsPk) - CmdDelegate.Flags().AddFlagSet(fsAmount) - - CmdUnbond.Flags().AddFlagSet(fsPk) - CmdUnbond.Flags().AddFlagSet(fsShares) - - CmdDeclareCandidacy.Flags().AddFlagSet(fsPk) - CmdDeclareCandidacy.Flags().AddFlagSet(fsAmount) - CmdDeclareCandidacy.Flags().AddFlagSet(fsCandidate) - - CmdEditCandidacy.Flags().AddFlagSet(fsPk) - CmdEditCandidacy.Flags().AddFlagSet(fsCandidate) } -func cmdDeclareCandidacy(cmd *cobra.Command, args []string) error { - amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) - if err != nil { - return err +//_________________________________________________________________________________________ + +// create declare candidacy command +func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "declare-candidacy", + Short: "create new validator-candidate account and delegate some coins to it", + RunE: func(cmd *cobra.Command, args []string) error { + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + addr, err := GetAddress(viper.GetString(FlagAddress)) + if err != nil { + return err + } + pk, err := GetPubKey(viper.GetString(FlagPubKey)) + if err != nil { + return err + } + if viper.GetString(FlagMoniker) == "" { + return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") + } + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + msg := stake.NewMsgDeclareCandidacy(addr, pk, amount, description) + + // build and sign the transaction, then broadcast to Tendermint + res, err := builder.SignBuildBroadcast(msg, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, } - addr, err := GetAddress(viper.GetString(FlagAddress)) - if err != nil { - return err - } - - pk, err := GetPubKey(viper.GetString(FlagPubKey)) - if err != nil { - return err - } - - if viper.GetString(FlagMoniker) == "" { - return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker") - } - - description := stake.Description{ - Moniker: viper.GetString(FlagMoniker), - Identity: viper.GetString(FlagIdentity), - Website: viper.GetString(FlagWebsite), - Details: viper.GetString(FlagDetails), - } - - tx := stake.NewMsgDeclareCandidacy(addr, pk, amount, description) - return doTx(tx) + cmd.Flags().AddFlagSet(fsPk) + cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(fsCandidate) + return cmd } -func cmdEditCandidacy(cmd *cobra.Command, args []string) error { +// create edit candidacy command +func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "edit-candidacy", + Short: "edit and existing validator-candidate account", + RunE: func(cmd *cobra.Command, args []string) error { - addr, err := GetAddress(viper.GetString(FlagAddress)) - if err != nil { - return err + addr, err := GetAddress(viper.GetString(FlagAddress)) + if err != nil { + return err + } + description := stake.Description{ + Moniker: viper.GetString(FlagMoniker), + Identity: viper.GetString(FlagIdentity), + Website: viper.GetString(FlagWebsite), + Details: viper.GetString(FlagDetails), + } + msg := stake.NewMsgEditCandidacy(addr, description) + + // build and sign the transaction, then broadcast to Tendermint + res, err := builder.SignBuildBroadcast(msg, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, } - description := stake.Description{ - Moniker: viper.GetString(FlagMoniker), - Identity: viper.GetString(FlagIdentity), - Website: viper.GetString(FlagWebsite), - Details: viper.GetString(FlagDetails), - } - - tx := stake.NewMsgEditCandidacy(addr, description) - return doTx(tx) + cmd.Flags().AddFlagSet(fsPk) + cmd.Flags().AddFlagSet(fsCandidate) + return cmd } -func cmdDelegate(cmd *cobra.Command, args []string) error { - amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) - if err != nil { - return err +// create edit candidacy command +func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "delegate", + Short: "delegate coins to an existing validator/candidate", + RunE: func(cmd *cobra.Command, args []string) error { + amount, err := sdk.ParseCoin(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + addr, err := GetAddress(viper.GetString(FlagAddress)) + if err != nil { + return err + } + + msg := stake.NewMsgDelegate(addr, amount) + + // build and sign the transaction, then broadcast to Tendermint + res, err := builder.SignBuildBroadcast(msg, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, } - addr, err := GetAddress(viper.GetString(FlagAddress)) - if err != nil { - return err - } - - tx := stake.NewMsgDelegate(addr, amount) - return doTx(tx) + cmd.Flags().AddFlagSet(fsPk) + cmd.Flags().AddFlagSet(fsAmount) + return cmd } -func cmdUnbond(cmd *cobra.Command, args []string) error { +// create edit candidacy command +func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "unbond", + Short: "unbond coins from a validator/candidate", + RunE: func(cmd *cobra.Command, args []string) error { - // TODO once go-wire refactored the shares can be broadcast as a Rat instead of a string + // check the shares before broadcasting + sharesStr := viper.GetString(FlagShares) + var shares sdk.Rat + if sharesStr != "MAX" { + var err error + shares, err = sdk.NewRatFromDecimal(sharesStr) + if err != nil { + return err + } + if !shares.GT(sdk.ZeroRat) { + return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") + } + } - // check the shares before broadcasting - sharesStr := viper.GetString(FlagShares) - var shares sdk.Rat - if sharesStr != "MAX" { - var err error - shares, err = sdk.NewRatFromDecimal(sharesStr) - if err != nil { - return err - } - if !shares.GT(sdk.ZeroRat) { - return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)") - } + addr, err := GetAddress(viper.GetString(FlagAddress)) + if err != nil { + return err + } + + msg := stake.NewMsgUnbond(addr, sharesStr) + + // build and sign the transaction, then broadcast to Tendermint + res, err := builder.SignBuildBroadcast(msg, cdc) + if err != nil { + return err + } + + fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String()) + return nil + }, } - addr, err := GetAddress(viper.GetString(FlagAddress)) - if err != nil { - return err - } - - tx := stake.NewMsgUnbond(addr, sharesStr) - return doTx(tx) + cmd.Flags().AddFlagSet(fsPk) + cmd.Flags().AddFlagSet(fsShares) + return cmd } //______________________________________________________________________________________ @@ -202,44 +234,13 @@ func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { } // GetPubKey - create an Address from a pubkey string -func GetAddress(Address string) (addr sdk.Address, err error) { - if len(Address) == 0 { +func GetAddress(address string) (addr sdk.Address, err error) { + if len(address) == 0 { return addr, errors.New("must use provide address") } - bz, err := hex.DecodeString(addr) + bz, err := hex.DecodeString(address) if err != nil { return nil, err } return sdk.Address(bz), nil } - -//______________________________________________________________________________________ -// XXX consolidate to client - -func doTx(tx []byte) { - - uri := viper.GetString(client.FlagNode) - if uri == "" { - return errors.New("Must define which node to query with --node") - } - node := client.GetNode(uri) - - result, err := node.BroadcastTxCommit(tx) - if err != nil { - return err - } - - if result.CheckTx.Code != uint32(0) { - fmt.Printf("CheckTx failed: (%d) %s\n", - result.CheckTx.Code, - result.CheckTx.Log) - } - if result.DeliverTx.Code != uint32(0) { - fmt.Printf("DeliverTx failed: (%d) %s\n", - result.DeliverTx.Code, - result.DeliverTx.Log) - } - - fmt.Printf("Committed at block %d. Hash: %s\n", result.Height, result.Hash.String()) - return nil -} From 3f0d7edb06989ceaf412bea6c713b638b09a3ff5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 17 Mar 2018 19:18:04 +0100 Subject: [PATCH 29/54] gettin' stakin' tests up to snuff --- types/account.go | 15 ++++ x/stake/commands/query.go | 5 +- x/stake/commands/tx.go | 24 ++---- x/stake/handler_test.go | 171 +++++++++++++++----------------------- x/stake/mapper_test.go | 108 ++++++++++++------------ x/stake/test_common.go | 118 ++++++++++++++++++++++---- x/stake/tx.go | 6 +- 7 files changed, 246 insertions(+), 201 deletions(-) diff --git a/types/account.go b/types/account.go index c0fadcd3cf..91ad499795 100644 --- a/types/account.go +++ b/types/account.go @@ -1,6 +1,9 @@ package types import ( + "encoding/hex" + "errors" + crypto "github.com/tendermint/go-crypto" cmn "github.com/tendermint/tmlibs/common" ) @@ -8,6 +11,18 @@ import ( // Address in go-crypto style type Address = cmn.HexBytes +// create an Address from a string +func GetAddress(address string) (addr Address, err error) { + if len(address) == 0 { + return addr, errors.New("must use provide address") + } + bz, err := hex.DecodeString(address) + if err != nil { + return nil, err + } + return Address(bz), nil +} + // Account is a standard account using a sequence number for replay protection // and a pubkey for authentication. type Account interface { diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index 6740ea812d..2a72d569ac 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -12,6 +12,7 @@ import ( crypto "github.com/tendermint/go-crypto" "github.com/cosmos/cosmos-sdk/client/builder" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" // XXX fix "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -79,7 +80,7 @@ func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command { Short: "Query a validator-candidate account", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := GetAddress(viper.GetString(FlagValidatorAddr)) + addr, err := sdk.GetAddress(viper.GetString(FlagValidatorAddr)) if err != nil { return err } @@ -119,7 +120,7 @@ func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command Short: "Query a delegators bond based on address and candidate pubkey", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := GetAddress(viper.GetString(FlagValidatorAddr)) + addr, err := sdk.GetAddress(viper.GetString(FlagValidatorAddr)) if err != nil { return err } diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 571a346175..99136e532c 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" flag "github.com/spf13/pflag" "github.com/spf13/viper" @@ -60,7 +59,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - addr, err := GetAddress(viper.GetString(FlagAddress)) + addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } @@ -103,7 +102,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { Short: "edit and existing validator-candidate account", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := GetAddress(viper.GetString(FlagAddress)) + addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } @@ -142,7 +141,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { return err } - addr, err := GetAddress(viper.GetString(FlagAddress)) + addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } @@ -186,7 +185,7 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { } } - addr, err := GetAddress(viper.GetString(FlagAddress)) + addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) if err != nil { return err } @@ -211,7 +210,8 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { //______________________________________________________________________________________ -// GetPubKey - create the pubkey from a pubkey string +// create the pubkey from a pubkey string +// TODO move to a better reusable place func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { if len(pubKeyStr) == 0 { @@ -232,15 +232,3 @@ func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) { pk = pkEd.Wrap() return } - -// GetPubKey - create an Address from a pubkey string -func GetAddress(address string) (addr sdk.Address, err error) { - if len(address) == 0 { - return addr, errors.New("must use provide address") - } - bz, err := hex.DecodeString(address) - if err != nil { - return nil, err - } - return sdk.Address(bz), nil -} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 0c26f8a242..c533bb8a4d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -10,154 +10,113 @@ import ( crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" - coin "github.com/cosmos/cosmos-sdk/x/bank" // XXX fix ) //______________________________________________________________________ -func initAccounts(n int, amount int64) ([]sdk.Address, map[string]int64) { +// XXX delete need to init accounts in the transact! +func initAccounts(amount int64) map[string]int64 { accStore := map[string]int64{} - senders := newAddrs(n) - for _, sender := range senders { - accStore[string(sender.Address)] = amount + for _, addr := range addrs { + accStore[string(addr)] = amount } - return senders, accStore + return accStore } -func newTestMsgDeclareCandidacy(amt int64, pubKey crypto.PubKey, address sdk.Address) MsgDeclareCandidacy { +func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { return MsgDeclareCandidacy{ - MsgAddr: NewMsgAddr(address), - PubKey: pubKey, - Bond: coin.Coin{"fermion", amt}, - Description{}, + MsgAddr: NewMsgAddr(address), + PubKey: pubKey, + Bond: sdk.Coin{"fermion", amt}, + Description: Description{}, } } func newTestMsgDelegate(amt int64, address sdk.Address) MsgDelegate { return MsgDelegate{ MsgAddr: NewMsgAddr(address), - Bond: coin.Coin{"fermion", amt}, + Bond: sdk.Coin{"fermion", amt}, } } -func newMsgUnbond(shares string, pubKey crypto.PubKey) MsgUnbond { - return MsgUnbond{ - PubKey: pubKey, - Shares: shares, - } -} - -func paramsNoInflation() Params { - return Params{ - InflationRateChange: sdk.ZeroRat, - InflationMax: sdk.ZeroRat, - InflationMin: sdk.ZeroRat, - GoalBonded: sdk.New(67, 100), - MaxVals: 100, - BondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, - } -} - -func newTestTransact(t, sender sdk.Address, isCheckTx bool) transact { - store, mapper, coinKeeper := createTestInput(t, isCheckTx) - params := paramsNoInflation() - mapper.saveParams(params) - newTransact(ctx, sender, mapper, coinKeeper) -} - func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { - senders, accStore := initAccounts(2, 1000) // for accounts + accStore := initAccounts(1000) // for accounts + _, deliverer := createTestInput(t, addrs[0], false) + _, checker := createTestInput(t, addrs[0], true) - deliverer := newDeliver(t, senders[0], accStore) - checker := check{ - store: deliverer.store, - sender: senders[0], - } - - txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) got := deliverer.declareCandidacy(txDeclareCandidacy) assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - // one sender can bond to two different pubKeys - txDeclareCandidacy.PubKey = pks[1] + // one sender can bond to two different addresses + txDeclareCandidacy.Address = addrs[1] err := checker.declareCandidacy(txDeclareCandidacy) assert.Nil(t, err, "didn't expected error on checkTx") - // two senders cant bond to the same pubkey - checker.sender = senders[1] - txDeclareCandidacy.PubKey = pks[0] + // two addrs cant bond to the same pubkey + checker.sender = addrs[1] + txDeclareCandidacy.Address = addrs[0] err = checker.declareCandidacy(txDeclareCandidacy) assert.NotNil(t, err, "expected error on checkTx") } func TestIncrementsMsgDelegate(t *testing.T) { initSender := int64(1000) - senders, accStore := initAccounts(1, initSender) // for accounts - deliverer := newDeliver(t, senders[0], accStore) + accStore := initAccounts(initSender) // for accounts + mapper, deliverer := createTestInput(t, addrs[0], false) // first declare candidacy bondAmount := int64(10) - txDeclareCandidacy := newTestMsgDeclareCandidacy(bondAmount, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) got := deliverer.declareCandidacy(txDeclareCandidacy) assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got) expectedBond := bondAmount // 1 since we send 1 at the start of loop, // just send the same txbond multiple times - holder := deliverer.params.HoldUnbonded // XXX this should be HoldBonded, new SDK updates - txDelegate := newTestMsgDelegate(bondAmount, pks[0]) + txDelegate := newTestMsgDelegate(bondAmount, addrs[0]) for i := 0; i < 5; i++ { got := deliverer.delegate(txDelegate) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values - candidates := loadCandidates(deliverer.store) + candidates := mapper.loadCandidates() expectedBond += bondAmount expectedSender := initSender - expectedBond gotBonded := candidates[0].Liabilities.Evaluate() - gotHolder := accStore[string(holder.Address)] - gotSender := accStore[string(deliverer.sender.Address)] + gotSender := accStore[string(deliverer.sender)] assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) - assert.Equal(t, expectedBond, gotHolder, "i: %v, %v, %v", i, expectedBond, gotHolder) assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) } } func TestIncrementsMsgUnbond(t *testing.T) { initSender := int64(0) - senders, accStore := initAccounts(1, initSender) // for accounts - deliverer := newDeliver(t, senders[0], accStore) + accStore := initAccounts(initSender) // for accounts + mapper, deliverer := createTestInput(t, addrs[0], false) // set initial bond initBond := int64(1000) - accStore[string(deliverer.sender.Address)] = initBond - got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(initBond, pks[0])) + accStore[string(deliverer.sender)] = initBond + got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got) // just send the same txunbond multiple times - holder := deliverer.params.HoldUnbonded // XXX new SDK, this should be HoldBonded - // XXX use decimals here unbondShares, unbondSharesStr := int64(10), "10" - txUndelegate := newMsgUnbond(unbondSharesStr, pks[0]) + txUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) nUnbonds := 5 for i := 0; i < nUnbonds; i++ { got := deliverer.unbond(txUndelegate) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the accounts and the bond account have the appropriate values - candidates := loadCandidates(deliverer.store) + candidates := mapper.loadCandidates() 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() - gotHolder := accStore[string(holder.Address)] - gotSender := accStore[string(deliverer.sender.Address)] + gotSender := accStore[string(deliverer.sender)] assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) - assert.Equal(t, expectedBond, gotHolder, "%v, %v", expectedBond, gotHolder) assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) } @@ -171,7 +130,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { } for _, c := range errorCases { unbondShares := strconv.Itoa(int(c)) - txUndelegate := newMsgUnbond(unbondShares, pks[0]) + txUndelegate := NewMsgUnbond(addrs[0], unbondShares) got = deliverer.unbond(txUndelegate) assert.Error(t, got, "expected unbond tx to fail") } @@ -179,51 +138,51 @@ func TestIncrementsMsgUnbond(t *testing.T) { leftBonded := initBond - unbondShares*int64(nUnbonds) // should be unable to unbond one more than we have - txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)+1), pks[0]) + txUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) got = deliverer.unbond(txUndelegate) assert.Error(t, got, "expected unbond tx to fail") // should be able to unbond just what we have - txUndelegate = newMsgUnbond(strconv.Itoa(int(leftBonded)), pks[0]) + txUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) got = deliverer.unbond(txUndelegate) assert.NoError(t, got, "expected unbond tx to pass") } func TestMultipleMsgDeclareCandidacy(t *testing.T) { initSender := int64(1000) - senders, accStore := initAccounts(3, initSender) - pubKeys := []crypto.PubKey{pks[0], pks[1], pks[2]} - deliverer := newDeliver(t, senders[0], accStore) + accStore := initAccounts(initSender) + addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + mapper, deliverer := createTestInput(t, addrs[0], false) // bond them all - for i, sender := range senders { - txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pubKeys[i]) - deliverer.sender = sender + for i, addr := range addrs { + txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) + deliverer.sender = addr got := deliverer.declareCandidacy(txDeclareCandidacy) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is bonded - candidates := loadCandidates(deliverer.store) + candidates := mapper.loadCandidates() val := candidates[i] - balanceGot, balanceExpd := accStore[string(val.Owner.Address)], initSender-10 + balanceGot, balanceExpd := accStore[string(val.Address)], initSender-10 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, sender := range senders { - candidatePre := loadCandidate(deliverer.store, pubKeys[i]) - txUndelegate := newMsgUnbond("10", pubKeys[i]) - deliverer.sender = sender + for i, addr := range addrs { + candidatePre := mapper.loadCandidate(addrs[i]) + txUndelegate := NewMsgUnbond(addrs[i], "10") + deliverer.sender = addr got := deliverer.unbond(txUndelegate) assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is unbonded - candidates := loadCandidates(deliverer.store) - assert.Equal(t, len(senders)-(i+1), len(candidates), "expected %d candidates got %d", len(senders)-(i+1), len(candidates)) + candidates := mapper.loadCandidates() + assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) - candidatePost := loadCandidate(deliverer.store, pubKeys[i]) + candidatePost := mapper.loadCandidate(addrs[i]) balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender 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) @@ -231,58 +190,58 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { } func TestMultipleMsgDelegate(t *testing.T) { - accounts, accStore := initAccounts(3, 1000) - sender, delegators := accounts[0], accounts[1:] - deliverer := newDeliver(t, sender, accStore) + accStore := initAccounts(1000) + sender, delegators := addrs[0], addrs[1:] + mapper, deliverer := createTestInput(t, addrs[0], false) //first make a candidate - txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) got := deliverer.declareCandidacy(txDeclareCandidacy) require.NoError(t, got, "expected tx to be ok, got %v", got) // delegate multiple parties for i, delegator := range delegators { - txDelegate := newTestMsgDelegate(10, pks[0]) + txDelegate := newTestMsgDelegate(10, addrs[0]) deliverer.sender = delegator got := deliverer.delegate(txDelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is bonded - bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + bond := mapper.loadDelegatorBond(delegator, addrs[0]) assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) } // unbond them all for i, delegator := range delegators { - txUndelegate := newMsgUnbond("10", pks[0]) + txUndelegate := NewMsgUnbond(addrs[0], "10") deliverer.sender = delegator got := deliverer.unbond(txUndelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is unbonded - bond := loadDelegatorBond(deliverer.store, delegator, pks[0]) + bond := mapper.loadDelegatorBond(delegator, addrs[0]) assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) } } func TestVoidCandidacy(t *testing.T) { - accounts, accStore := initAccounts(2, 1000) // for accounts - sender, delegator := accounts[0], accounts[1] - deliverer := newDeliver(t, sender, accStore) + accStore := initAccounts(1000) // for accounts + sender, delegator := addrs[0], addrs[1] + _, deliverer := createTestInput(t, addrs[0], false) // create the candidate - txDeclareCandidacy := newTestMsgDeclareCandidacy(10, pks[0]) + txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) got := deliverer.declareCandidacy(txDeclareCandidacy) require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // bond a delegator - txDelegate := newTestMsgDelegate(10, pks[0]) + txDelegate := newTestMsgDelegate(10, addrs[0]) deliverer.sender = delegator got = deliverer.delegate(txDelegate) require.NoError(t, got, "expected ok, got %v", got) // unbond the candidates bond portion - txUndelegate := newMsgUnbond("10", pks[0]) + txUndelegate := NewMsgUnbond(addrs[0], "10") deliverer.sender = sender got = deliverer.unbond(txUndelegate) require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") diff --git a/x/stake/mapper_test.go b/x/stake/mapper_test.go index 36c179788e..8afa1cc6c5 100644 --- a/x/stake/mapper_test.go +++ b/x/stake/mapper_test.go @@ -4,7 +4,7 @@ import ( "bytes" "testing" - "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" crypto "github.com/tendermint/go-crypto" "github.com/stretchr/testify/assert" @@ -26,12 +26,12 @@ import ( //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //// test a basic change in voting power -//candidates[0].Assets = types.NewRat(500) +//candidates[0].Assets = sdk.NewRat(500) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //// test a swap in voting power -//candidates[1].Assets = types.NewRat(600) +//candidates[1].Assets = sdk.NewRat(600) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) @@ -46,11 +46,11 @@ import ( //func TestValidatorsChanged(t *testing.T) { //require := require.New(t) -//v1 := (&Candidate{PubKey: pks[0], VotingPower: types.NewRat(10)}).validator() -//v2 := (&Candidate{PubKey: pks[1], VotingPower: types.NewRat(10)}).validator() -//v3 := (&Candidate{PubKey: pks[2], VotingPower: types.NewRat(10)}).validator() -//v4 := (&Candidate{PubKey: pks[3], VotingPower: types.NewRat(10)}).validator() -//v5 := (&Candidate{PubKey: pks[4], VotingPower: types.NewRat(10)}).validator() +//v1 := (&Candidate{PubKey: pks[0], VotingPower: sdk.NewRat(10)}).validator() +//v2 := (&Candidate{PubKey: pks[1], VotingPower: sdk.NewRat(10)}).validator() +//v3 := (&Candidate{PubKey: pks[2], VotingPower: sdk.NewRat(10)}).validator() +//v4 := (&Candidate{PubKey: pks[3], VotingPower: sdk.NewRat(10)}).validator() +//v5 := (&Candidate{PubKey: pks[4], VotingPower: sdk.NewRat(10)}).validator() //// test from nothing to something //vs1 := []Validator{} @@ -75,14 +75,14 @@ import ( //require.ZeroRat(len(changed)) //// test single value change -//vs2[2].VotingPower = types.OneRat +//vs2[2].VotingPower = sdk.OneRat //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testChange(t, vs2[2], changed[0]) //// test multiple value change -//vs2[0].VotingPower = types.NewRat(11) -//vs2[2].VotingPower = types.NewRat(5) +//vs2[0].VotingPower = sdk.NewRat(11) +//vs2[2].VotingPower = sdk.NewRat(5) //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[0], changed[0]) @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many types of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = types.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 +//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -141,11 +141,11 @@ import ( //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) //// mess with the power's of the candidates and test -//candidates[0].Assets = types.NewRat(10) -//candidates[1].Assets = types.NewRat(600) -//candidates[2].Assets = types.NewRat(1000) -//candidates[3].Assets = types.OneRat -//candidates[4].Assets = types.NewRat(10) +//candidates[0].Assets = sdk.NewRat(10) +//candidates[1].Assets = sdk.NewRat(600) +//candidates[2].Assets = sdk.NewRat(1000) +//candidates[3].Assets = sdk.OneRat +//candidates[4].Assets = sdk.NewRat(10) //for _, c := range candidates { //saveCandidate(store, c) //} @@ -161,12 +161,10 @@ import ( //} func TestState(t *testing.T) { - store := createTestInput(t) + mapper, _ := createTestInput(t, nil, false) - //delegator := crypto.Address{[]byte("addressdelegator")} - //validator := crypto.Address{[]byte("addressvalidator")} - delegator := []byte("addressdelegator") - validator := []byte("addressvalidator") + addrDel := sdk.Address([]byte("addressdelegator")) + addrVal := sdk.Address([]byte("addressvalidator")) //pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") pk := crypto.GenPrivKeyEd25519().PubKey() @@ -175,52 +173,52 @@ func TestState(t *testing.T) { // XXX expand to include both liabilities and assets use/test all candidate fields candidate := &Candidate{ - Owner: validator, + Address: addrVal, PubKey: pk, - Assets: types.NewRat(9), - Liabilities: types.NewRat(9), - VotingPower: types.ZeroRat, + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + VotingPower: sdk.ZeroRat, } candidatesEqual := func(c1, c2 *Candidate) bool { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && - bytes.Equal(c1.Owner, c2.Owner) && + bytes.Equal(c1.Address, c2.Address) && c1.Assets.Equal(c2.Assets) && c1.Liabilities.Equal(c2.Liabilities) && c1.VotingPower.Equal(c2.VotingPower) && c1.Description == c2.Description } - // check the empty store first - resCand := loadCandidate(store, pk) + // check the empty mapper first + resCand := mapper.loadCandidate(addrVal) assert.Nil(t, resCand) - resPks := loadCandidates(store) + resPks := mapper.loadCandidates() assert.Zero(t, len(resPks)) // set and retrieve a record - saveCandidate(store, candidate) - resCand = loadCandidate(store, pk) + mapper.saveCandidate(candidate) + resCand = mapper.loadCandidate(addrVal) //assert.Equal(candidate, resCand) assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve - candidate.Liabilities = types.NewRat(99) - saveCandidate(store, candidate) - resCand = loadCandidate(store, pk) + candidate.Liabilities = sdk.NewRat(99) + mapper.saveCandidate(candidate) + resCand = mapper.loadCandidate(addrVal) assert.True(t, candidatesEqual(candidate, resCand)) // also test that the pubkey has been added to pubkey list - resPks = loadCandidates(store) + resPks = mapper.loadCandidates() require.Equal(t, 1, len(resPks)) - assert.Equal(t, pk, resPks[0].PubKey) + assert.Equal(t, addrVal, resPks[0].PubKey) //---------------------------------------------------------------------- // Bond checks bond := &DelegatorBond{ - PubKey: pk, - Shares: types.NewRat(9), + Address: addrDel, + Shares: sdk.NewRat(9), } bondsEqual := func(b1, b2 *DelegatorBond) bool { @@ -228,19 +226,19 @@ func TestState(t *testing.T) { b1.Shares == b2.Shares } - //check the empty store first - resBond := loadDelegatorBond(store, delegator, pk) + //check the empty mapper first + resBond := mapper.loadDelegatorBond(addrDel, addrVal) assert.Nil(t, resBond) //Set and retrieve a record - saveDelegatorBond(store, delegator, bond) - resBond = loadDelegatorBond(store, delegator, pk) + mapper.saveDelegatorBond(addrDel, bond) + resBond = mapper.loadDelegatorBond(addrDel, addrVal) assert.True(t, bondsEqual(bond, resBond)) //modify a records, save, and retrieve - bond.Shares = types.NewRat(99) - saveDelegatorBond(store, delegator, bond) - resBond = loadDelegatorBond(store, delegator, pk) + bond.Shares = sdk.NewRat(99) + mapper.saveDelegatorBond(addrDel, bond) + resBond = mapper.loadDelegatorBond(addrDel, addrVal) assert.True(t, bondsEqual(bond, resBond)) //---------------------------------------------------------------------- @@ -248,25 +246,25 @@ func TestState(t *testing.T) { params := defaultParams() - //check that the empty store loads the default - resParams := loadParams(store) + //check that the empty mapper loads the default + resParams := mapper.loadParams() assert.Equal(t, params, resParams) //modify a params, save, and retrieve params.MaxVals = 777 - saveParams(store, params) - resParams = loadParams(store) + mapper.saveParams(params) + resParams = mapper.loadParams() assert.Equal(t, params, resParams) } func TestGetValidators(t *testing.T) { - store, ctx, key := createTestInput(t, false) + mapper, _ := createTestInput(t, nil, false) N := 5 addrs := newAddrs(N) - candidatesFromActors(store, addrs, []int64{400, 200, 0, 0, 0}) + candidatesFromAddrs(mapper, addrs, []int64{400, 200, 0, 0, 0}) - validators := getValidators(store, 5) + validators := mapper.getValidators(5) require.Equal(t, 2, len(validators)) - assert.Equal(t, pks[0], validators[0].PubKey) - assert.Equal(t, pks[1], validators[1].PubKey) + assert.Equal(t, addrs[0], validators[0].Address) + assert.Equal(t, addrs[1], validators[1].Address) } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index e04151046d..298b34eaf5 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -2,17 +2,21 @@ package stake import ( "encoding/hex" - "fmt" "testing" "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" + oldwire "github.com/tendermint/go-wire" dbm "github.com/tendermint/tmlibs/db" + "github.com/cosmos/cosmos-sdk/examples/basecoin/types" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" ) func subspace(prefix []byte) (start, end []byte) { @@ -22,25 +26,83 @@ func subspace(prefix []byte) (start, end []byte) { return prefix, end } -func createTestInput(t *testing.T, isCheckTx bool) (sdk.KVStore, sdk.Context, sdk.StoreKey) { +// custom tx codec +// TODO: use new go-wire +func makeTestCodec() *wire.Codec { + + const msgTypeSend = 0x1 + const msgTypeIssue = 0x2 + const msgTypeDeclareCandidacy = 0x3 + const msgTypeEditCandidacy = 0x4 + const msgTypeDelegate = 0x5 + const msgTypeUnbond = 0x6 + var _ = oldwire.RegisterInterface( + struct{ sdk.Msg }{}, + oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend}, + oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue}, + oldwire.ConcreteType{MsgDeclareCandidacy{}, msgTypeDeclareCandidacy}, + oldwire.ConcreteType{MsgEditCandidacy{}, msgTypeEditCandidacy}, + oldwire.ConcreteType{MsgDelegate{}, msgTypeDelegate}, + oldwire.ConcreteType{MsgUnbond{}, msgTypeUnbond}, + ) + + const accTypeApp = 0x1 + var _ = oldwire.RegisterInterface( + struct{ sdk.Account }{}, + oldwire.ConcreteType{&types.AppAccount{}, accTypeApp}, + ) + cdc := wire.NewCodec() + + // cdc.RegisterInterface((*sdk.Msg)(nil), nil) + // bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types. + // crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types. + return cdc +} + +func paramsNoInflation() Params { + return Params{ + InflationRateChange: sdk.ZeroRat, + InflationMax: sdk.ZeroRat, + InflationMin: sdk.ZeroRat, + GoalBonded: sdk.NewRat(67, 100), + MaxVals: 100, + BondDenom: "fermion", + GasDeclareCandidacy: 20, + GasEditCandidacy: 20, + GasDelegate: 20, + GasUnbond: 20, + } +} + +// hogpodge of all sorts of input required for testing +//func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (sdk.KVStore, sdk.Context, sdk.StoreKey, Mapper, bank.CoinKeeper, transact) { +func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (Mapper, transact) { db := dbm.NewMemDB() - key := sdk.NewKVStoreKey("stake") + keyMain := sdk.NewKVStoreKey("main") + keyStake := sdk.NewKVStoreKey("stake") ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) err := ms.LoadLatestVersion() require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) - store := ms.GetKVStore(key) - return store, ctx, key -} + store := ms.GetKVStore(keyStake) -func newAddrs(n int) (addrs []crypto.Address) { - for i := 0; i < n; i++ { - addrs = append(addrs, []byte(fmt.Sprintf("addr%d", i))) - } - return + cdc := makeTestCodec() + mapper := NewMapper(ctx, cdc, keyStake) + + accountMapper := auth.NewAccountMapperSealed( + keyMain, // target store + &auth.BaseAccount{}, // prototype + ) + ck := bank.NewCoinKeeper(accountMapper) + + params := paramsNoInflation() + mapper.saveParams(params) + tr := newTransact(ctx, sender, mapper, ck) + + return mapper, tr } func newPubKey(pk string) (res crypto.PubKey) { @@ -54,8 +116,8 @@ func newPubKey(pk string) (res crypto.PubKey) { return pkEd.Wrap() } -// dummy pubkeys used for testing var pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), @@ -65,12 +127,34 @@ var pks = []crypto.PubKey{ newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB60"), +} + +// for incode address generation +func testAddr(addr string) sdk.Address { + res, err := sdk.GetAddress("0XA58856F0FD53BF058B4909A21AEC019107BA6160") + if err != nil { + panic(err) + } + return res +} + +// dummy addresses used for testing +var addrs = []sdk.Address{ + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6160"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6161"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6162"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6163"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6164"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6165"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6166"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6167"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6168"), + testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6169"), } // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey // instead this is just being set the address here for testing purposes -func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int64) { +func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { for i := 0; i < len(addrs); i++ { c := &Candidate{ Status: Unbonded, @@ -80,12 +164,10 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6 Liabilities: sdk.NewRat(amts[i]), VotingPower: sdk.NewRat(amts[i]), } - saveCandidate(store, c) + mapper.saveCandidate(c) } } -func saveCandidate(store sdk.KVStore, c *Candidate) {} // TODO - func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { for i := 0; i < len(addrs); i++ { c := &Candidate{ diff --git a/x/stake/tx.go b/x/stake/tx.go index 7104b92a8b..82a8925d23 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -49,8 +49,9 @@ func (msg MsgAddr) ValidateBasic() sdk.Error { type MsgDeclareCandidacy struct { MsgAddr Description - Bond sdk.Coin `json:"bond"` - PubKey crypto.PubKey `json:"pubkey"` + Bond sdk.Coin `json:"bond"` + Address sdk.Address `json:"address"` + PubKey crypto.PubKey `json:"pubkey"` } func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy { @@ -58,6 +59,7 @@ func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk. MsgAddr: NewMsgAddr(address), Description: description, Bond: bond, + Address: address, PubKey: pubkey, } } From acc5bda225d9627fcb12b1674a0f5b64387b6006 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 17 Mar 2018 23:13:42 +0100 Subject: [PATCH 30/54] tests compile --- x/stake/handler_test.go | 45 +++++++++++++++++++++----------------- x/stake/mapper_test.go | 4 +--- x/stake/test_common.go | 5 ++--- x/stake/tick_test.go | 46 +++++++++++++++++++-------------------- x/stake/tx_test.go | 48 ++++++++++------------------------------- 5 files changed, 61 insertions(+), 87 deletions(-) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index c533bb8a4d..306ac1780e 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -40,7 +40,8 @@ func newTestMsgDelegate(amt int64, address sdk.Address) MsgDelegate { } func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { - accStore := initAccounts(1000) // for accounts + //accStore := initAccounts(1000) + // XXX initalize values in accounts to 1000 _, deliverer := createTestInput(t, addrs[0], false) _, checker := createTestInput(t, addrs[0], true) @@ -61,8 +62,9 @@ func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { } func TestIncrementsMsgDelegate(t *testing.T) { - initSender := int64(1000) - accStore := initAccounts(initSender) // for accounts + //initSender := int64(1000) + //accStore := initAccounts(initSender) // for accounts + // XXX initalize values in accounts to 1000 mapper, deliverer := createTestInput(t, addrs[0], false) // first declare candidacy @@ -81,22 +83,23 @@ func TestIncrementsMsgDelegate(t *testing.T) { //Check that the accounts and the bond account have the appropriate values candidates := mapper.loadCandidates() expectedBond += bondAmount - expectedSender := initSender - expectedBond + //expectedSender := initSender - expectedBond gotBonded := candidates[0].Liabilities.Evaluate() - gotSender := accStore[string(deliverer.sender)] + //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) + //assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix } } func TestIncrementsMsgUnbond(t *testing.T) { - initSender := int64(0) - accStore := initAccounts(initSender) // for accounts + //initSender := int64(0) + //accStore := initAccounts(initSender) // for accounts + // XXX initalize values in accounts to 0 mapper, deliverer := createTestInput(t, addrs[0], false) // set initial bond initBond := int64(1000) - accStore[string(deliverer.sender)] = initBond + //accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) assert.NoError(t, got, "expected initial bond tx to be ok, got %v", got) @@ -112,12 +115,12 @@ func TestIncrementsMsgUnbond(t *testing.T) { //Check that the accounts and the bond account have the appropriate values candidates := mapper.loadCandidates() expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop - expectedSender := initSender + (initBond - expectedBond) + //expectedSender := initSender + (initBond - expectedBond) gotBonded := candidates[0].Liabilities.Evaluate() - gotSender := accStore[string(deliverer.sender)] + //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) + //assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix } // these are more than we have bonded now @@ -183,49 +186,51 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) candidatePost := mapper.loadCandidate(addrs[i]) - balanceGot, balanceExpd := accStore[string(candidatePre.Owner.Address)], initSender + balanceGot, balanceExpd := accStore[string(candidatePre.Address)], initSender 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) { - accStore := initAccounts(1000) + //accStore := initAccounts(1000) + // XXX initalize values in accounts to 1000 sender, delegators := addrs[0], addrs[1:] mapper, deliverer := createTestInput(t, addrs[0], false) //first make a candidate - txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + txDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) got := deliverer.declareCandidacy(txDeclareCandidacy) require.NoError(t, got, "expected tx to be ok, got %v", got) // delegate multiple parties for i, delegator := range delegators { - txDelegate := newTestMsgDelegate(10, addrs[0]) + txDelegate := newTestMsgDelegate(10, sender) deliverer.sender = delegator got := deliverer.delegate(txDelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is bonded - bond := mapper.loadDelegatorBond(delegator, addrs[0]) + bond := mapper.loadDelegatorBond(delegator, sender) assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) } // unbond them all for i, delegator := range delegators { - txUndelegate := NewMsgUnbond(addrs[0], "10") + txUndelegate := NewMsgUnbond(sender, "10") deliverer.sender = delegator got := deliverer.unbond(txUndelegate) require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) //Check that the account is unbonded - bond := mapper.loadDelegatorBond(delegator, addrs[0]) + bond := mapper.loadDelegatorBond(delegator, sender) assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) } } func TestVoidCandidacy(t *testing.T) { - accStore := initAccounts(1000) // for accounts + // XXX use accountMapper to init all accounts to 1000 + //accStore := initAccounts(1000) sender, delegator := addrs[0], addrs[1] _, deliverer := createTestInput(t, addrs[0], false) diff --git a/x/stake/mapper_test.go b/x/stake/mapper_test.go index 8afa1cc6c5..cba56d6f55 100644 --- a/x/stake/mapper_test.go +++ b/x/stake/mapper_test.go @@ -222,7 +222,7 @@ func TestState(t *testing.T) { } bondsEqual := func(b1, b2 *DelegatorBond) bool { - return b1.PubKey.Equals(b2.PubKey) && + return bytes.Equal(b1.Address, b2.Address) && b1.Shares == b2.Shares } @@ -259,8 +259,6 @@ func TestState(t *testing.T) { func TestGetValidators(t *testing.T) { mapper, _ := createTestInput(t, nil, false) - N := 5 - addrs := newAddrs(N) candidatesFromAddrs(mapper, addrs, []int64{400, 200, 0, 0, 0}) validators := mapper.getValidators(5) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 298b34eaf5..f966af705d 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -87,7 +87,6 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (Mapper, require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) - store := ms.GetKVStore(keyStake) cdc := makeTestCodec() mapper := NewMapper(ctx, cdc, keyStake) @@ -155,7 +154,7 @@ var addrs = []sdk.Address{ // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey // instead this is just being set the address here for testing purposes func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { - for i := 0; i < len(addrs); i++ { + for i := 0; i < len(amts); i++ { c := &Candidate{ Status: Unbonded, PubKey: pks[i], @@ -168,7 +167,7 @@ func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { } } -func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) { +func candidatesFromAddrsEmpty(addrs []crypto.Address) (candidates Candidates) { for i := 0; i < len(addrs); i++ { c := &Candidate{ Status: Unbonded, diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 8489b7c517..e245f773bc 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -8,9 +8,9 @@ import ( ) func TestGetInflation(t *testing.T) { - store, ctx, key := createTestInput(t, false) - params := loadParams(store) - gs := loadGlobalState(store) + mapper, _ := createTestInput(t, nil, false) + params := mapper.loadParams() + gs := mapper.loadGlobalState() // Governing Mechanism: // bondedRatio = BondedPool / TotalSupply @@ -21,24 +21,24 @@ func TestGetInflation(t *testing.T) { setInflation, expectedChange sdk.Rat }{ // with 0% bonded atom supply the inflation should increase by InflationRateChange - {0, 0, sdk.New(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + {0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, // 100% bonded, starting at 20% inflation and being reduced - {1, 1, sdk.New(20, 100), sdk.One.Sub(sdk.One.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + {1, 1, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, // 50% bonded, starting at 10% inflation and being increased - {1, 2, sdk.New(10, 100), sdk.One.Sub(sdk.New(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + {1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, // test 7% minimum stop (testing with 100% bonded) - {1, 1, sdk.New(7, 100), sdk.Zero}, - {1, 1, sdk.New(70001, 1000000), sdk.New(-1, 1000000)}, + {1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, + {1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)}, // test 20% maximum stop (testing with 0% bonded) - {0, 0, sdk.New(20, 100), sdk.Zero}, - {0, 0, sdk.New(199999, 1000000), sdk.New(1, 1000000)}, + {0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, + {0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)}, // perfect balance shouldn't change inflation - {67, 100, sdk.New(15, 100), sdk.Zero}, + {67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, } for _, tc := range tests { gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply @@ -53,14 +53,12 @@ func TestGetInflation(t *testing.T) { } func TestProcessProvisions(t *testing.T) { - store, ctx, key := createTestInput(t, false) - params := loadParams(store) - gs := loadGlobalState(store) + mapper, _ := createTestInput(t, nil, false) + params := mapper.loadParams() + gs := mapper.loadGlobalState() // create some candidates some bonded, some unbonded - n := 10 - actors := newActors(n) - candidates := candidatesFromActorsEmpty(actors) + candidates := candidatesFromAddrsEmpty(addrs) for i, candidate := range candidates { if i < 5 { candidate.Status = Bonded @@ -68,14 +66,14 @@ func TestProcessProvisions(t *testing.T) { mintedTokens := int64((i + 1) * 10000000) gs.TotalSupply += mintedTokens candidate.addTokens(mintedTokens, gs) - saveCandidate(store, candidate) + mapper.saveCandidate(candidate) } var totalSupply int64 = 550000000 var bondedShares int64 = 150000000 var unbondedShares int64 = 400000000 // initial bonded ratio ~ 27% - assert.True(t, gs.bondedRatio().Equal(sdk.New(bondedShares, totalSupply)), "%v", gs.bondedRatio()) + assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", gs.bondedRatio()) // Supplies assert.Equal(t, totalSupply, gs.TotalSupply) @@ -83,7 +81,7 @@ func TestProcessProvisions(t *testing.T) { assert.Equal(t, unbondedShares, gs.UnbondedPool) // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Equal(sdk.One), "%v", gs.bondedShareExRate()) + assert.True(t, gs.bondedShareExRate().Equal(sdk.OneRat), "%v", gs.bondedShareExRate()) initialSupply := gs.TotalSupply initialUnbonded := gs.TotalSupply - gs.BondedPool @@ -91,10 +89,10 @@ func TestProcessProvisions(t *testing.T) { // process the provisions a year for hr := 0; hr < 8766; hr++ { expInflation := nextInflation(gs, params).Round(1000000000) - expProvisions := (expInflation.Mul(sdk.New(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() + expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() startBondedPool := gs.BondedPool startTotalSupply := gs.TotalSupply - processProvisions(store, gs, params) + processProvisions(mapper, gs, params) assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) } @@ -103,7 +101,7 @@ func TestProcessProvisions(t *testing.T) { //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) // initial bonded ratio ~ 35% ~ 30% increase for bonded holders - assert.True(t, gs.bondedRatio().Equal(sdk.New(105906511, 305906511)), "%v", gs.bondedRatio()) + assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", gs.bondedRatio()) // global supply assert.Equal(t, int64(611813022), gs.TotalSupply) @@ -111,6 +109,6 @@ func TestProcessProvisions(t *testing.T) { assert.Equal(t, unbondedShares, gs.UnbondedPool) // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Mul(sdk.New(bondedShares)).Equal(sdk.New(211813022)), "%v", gs.bondedShareExRate()) + assert.True(t, gs.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", gs.bondedShareExRate()) } diff --git a/x/stake/tx_test.go b/x/stake/tx_test.go index 156a8f120e..0af8b0db6b 100644 --- a/x/stake/tx_test.go +++ b/x/stake/tx_test.go @@ -6,7 +6,6 @@ import ( "github.com/stretchr/testify/assert" - crypto "github.com/tendermint/go-crypto" wire "github.com/tendermint/go-wire" sdk "github.com/cosmos/cosmos-sdk/types" @@ -30,8 +29,8 @@ func TestMsgAddrValidateBasic(t *testing.T) { address sdk.Address wantErr bool }{ - {"basic good", pks[0], false}, - {"empty delegator", crypto.PubKey{}, true}, + {"basic good", addrs[0], false}, + {"empty delegator", sdk.Address{}, true}, } for _, tc := range tests { @@ -53,49 +52,24 @@ func TestValidateCoin(t *testing.T) { } for _, tc := range tests { - assert.Equal(t, tc.wantErr, tx.validateCoin(tc.coin) != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) + assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) } } -func TestAllAreTx(t *testing.T) { +func TestSerializeMsg(t *testing.T) { // make sure all types construct properly - pubKey := newPubKey("1234567890") bondAmt := 1234321 - bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)} - - txDelegate := NewMsgDelegate(bond, pubKey) - _, ok := txDelegate.(MsgDelegate) - assert.True(t, ok, "%#v", txDelegate) - - txUnbond := NewMsgUnbond(strconv.Itoa(bondAmt), pubKey) - _, ok = txUnbond.(MsgUnbond) - assert.True(t, ok, "%#v", txUnbond) - - txDecl := NewMsgDeclareCandidacy(bond, pubKey, Description{}) - _, ok = txDecl.(MsgDeclareCandidacy) - assert.True(t, ok, "%#v", txDecl) - - txEditCan := NewMsgEditCandidacy(pubKey, Description{}) - _, ok = txEditCan.(MsgEditCandidacy) - assert.True(t, ok, "%#v", txEditCan) -} - -func TestSerializeTx(t *testing.T) { - - // make sure all types construct properly - pubKey := newPubKey("1234567890") - bondAmt := 1234321 - bond := sdk.Coin{Denom: "ATOM", Amount: int64(bondAmt)} + bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} tests := []struct { - tx sdk.Tx + tx sdk.Msg }{ - {NewMsgUnbond(strconv.Itoa(bondAmt), pubKey)}, - {NewMsgDeclareCandidacy(bond, pubKey, Description{})}, - {NewMsgDeclareCandidacy(bond, pubKey, Description{})}, - // {NewTxRevokeCandidacy(pubKey)}, + {NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})}, + {NewMsgEditCandidacy(addrs[0], Description{})}, + {NewMsgDelegate(addrs[0], bond)}, + {NewMsgUnbond(addrs[0], strconv.Itoa(bondAmt))}, } for i, tc := range tests { From d439f5c47f2730eabcfb1ae107a6050e8993deab Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 19 Mar 2018 14:13:30 +0100 Subject: [PATCH 31/54] name and pass rebase fixes --- x/stake/commands/tx.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 99136e532c..56b41a15f9 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -10,6 +10,7 @@ import ( crypto "github.com/tendermint/go-crypto" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -47,6 +48,15 @@ func init() { fsCandidate.String(FlagDetails, "", "optional detailed description space") } +//TODO refactor to common functionality +func getNamePassword() (name, passphrase string, err error) { + name = viper.GetString(client.FlagName) + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password to sign with '%s':", name) + passphrase, err = client.GetPassword(prompt, buf) + return +} + //_________________________________________________________________________________________ // create declare candidacy command @@ -78,8 +88,13 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { } msg := stake.NewMsgDeclareCandidacy(addr, pk, amount, description) + name, pass, err := getNamePassword() + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) if err != nil { return err } @@ -114,8 +129,13 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { } msg := stake.NewMsgEditCandidacy(addr, description) + name, pass, err := getNamePassword() + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) if err != nil { return err } @@ -148,8 +168,13 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(addr, amount) + name, pass, err := getNamePassword() + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) if err != nil { return err } @@ -192,8 +217,13 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgUnbond(addr, sharesStr) + name, pass, err := getNamePassword() + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) if err != nil { return err } From b8cdf059218b8513fcb793ea3fd1cfde82b345ab Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 19 Mar 2018 14:15:47 +0100 Subject: [PATCH 32/54] gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ed8f93c683..5f3c9db7f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store *.swp *.swo vendor @@ -10,8 +11,6 @@ examples/basecoin/glide.lock examples/basecoin/app/data baseapp/data/* docs/_build -<<<<<<< HEAD -<<<<<<< HEAD .DS_Store coverage.txt profile.out From c1d8aefecdf82c5d46cd8e76a21f8a7a0b108dd8 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 19 Mar 2018 16:53:20 +0100 Subject: [PATCH 33/54] testing staking ... ... --- x/auth/mapper.go | 1 - x/stake/handler_test.go | 149 ++++++++++++++++++---------------------- x/stake/mapper_test.go | 4 +- x/stake/test_common.go | 36 +++++----- x/stake/tick_test.go | 4 +- x/stake/tx.go | 6 +- 6 files changed, 91 insertions(+), 109 deletions(-) diff --git a/x/auth/mapper.go b/x/auth/mapper.go index 1176db3cbe..13fe9a8445 100644 --- a/x/auth/mapper.go +++ b/x/auth/mapper.go @@ -165,7 +165,6 @@ func (am accountMapper) decodeAccount(bz []byte) sdk.Account { accI := oldwire.ReadBinary(struct{ sdk.Account }{}, r, len(bz), n, err) if *err != nil { panic(*err) - } acc := accI.(struct{ sdk.Account }).Account diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 306ac1780e..824fe88e0a 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -14,21 +14,12 @@ import ( //______________________________________________________________________ -// XXX delete need to init accounts in the transact! -func initAccounts(amount int64) map[string]int64 { - accStore := map[string]int64{} - for _, addr := range addrs { - accStore[string(addr)] = amount - } - return accStore -} - func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { return MsgDeclareCandidacy{ MsgAddr: NewMsgAddr(address), - PubKey: pubKey, - Bond: sdk.Coin{"fermion", amt}, Description: Description{}, + Bond: sdk.Coin{"fermion", amt}, + PubKey: pubKey, } } @@ -40,45 +31,40 @@ func newTestMsgDelegate(amt int64, address sdk.Address) MsgDelegate { } func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { - //accStore := initAccounts(1000) - // XXX initalize values in accounts to 1000 - _, deliverer := createTestInput(t, addrs[0], false) - _, checker := createTestInput(t, addrs[0], true) + _, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) + _, _, _, checker := createTestInput(t, addrs[0], true, 1000) - txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) - got := deliverer.declareCandidacy(txDeclareCandidacy) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // one sender can bond to two different addresses - txDeclareCandidacy.Address = addrs[1] - err := checker.declareCandidacy(txDeclareCandidacy) + msgDeclareCandidacy.Address = addrs[1] + err := checker.declareCandidacy(msgDeclareCandidacy) assert.Nil(t, err, "didn't expected error on checkTx") // two addrs cant bond to the same pubkey checker.sender = addrs[1] - txDeclareCandidacy.Address = addrs[0] - err = checker.declareCandidacy(txDeclareCandidacy) + msgDeclareCandidacy.Address = addrs[0] + err = checker.declareCandidacy(msgDeclareCandidacy) assert.NotNil(t, err, "expected error on checkTx") } func TestIncrementsMsgDelegate(t *testing.T) { - //initSender := int64(1000) - //accStore := initAccounts(initSender) // for accounts - // XXX initalize values in accounts to 1000 - mapper, deliverer := createTestInput(t, addrs[0], false) + _, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) // first declare candidacy bondAmount := int64(10) - txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) - got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(t, got, "expected declare candidacy tx to be ok, got %v", got) + 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 txbond multiple times - txDelegate := newTestMsgDelegate(bondAmount, addrs[0]) + // just send the same msgbond multiple times + msgDelegate := newTestMsgDelegate(bondAmount, addrs[0]) for i := 0; i < 5; i++ { - got := deliverer.delegate(txDelegate) - assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadCandidates() @@ -92,25 +78,22 @@ func TestIncrementsMsgDelegate(t *testing.T) { } func TestIncrementsMsgUnbond(t *testing.T) { - //initSender := int64(0) - //accStore := initAccounts(initSender) // for accounts - // XXX initalize values in accounts to 0 - mapper, deliverer := createTestInput(t, addrs[0], false) + _, _, 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 tx to be ok, got %v", got) + assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got) - // just send the same txunbond multiple times + // just send the same msgunbond multiple times // XXX use decimals here unbondShares, unbondSharesStr := int64(10), "10" - txUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) + msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) nUnbonds := 5 for i := 0; i < nUnbonds; i++ { - got := deliverer.unbond(txUndelegate) - assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadCandidates() @@ -133,41 +116,42 @@ func TestIncrementsMsgUnbond(t *testing.T) { } for _, c := range errorCases { unbondShares := strconv.Itoa(int(c)) - txUndelegate := NewMsgUnbond(addrs[0], unbondShares) - got = deliverer.unbond(txUndelegate) - assert.Error(t, got, "expected unbond tx to fail") + msgUndelegate := NewMsgUnbond(addrs[0], unbondShares) + got = deliverer.unbond(msgUndelegate) + assert.Error(t, got, "expected unbond msg to fail") } leftBonded := initBond - unbondShares*int64(nUnbonds) // should be unable to unbond one more than we have - txUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) - got = deliverer.unbond(txUndelegate) - assert.Error(t, got, "expected unbond tx to fail") + 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 - txUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) - got = deliverer.unbond(txUndelegate) - assert.NoError(t, got, "expected unbond tx to pass") + 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) - accStore := initAccounts(initSender) + ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender) addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} - mapper, deliverer := createTestInput(t, addrs[0], false) // bond them all for i, addr := range addrs { - txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) deliverer.sender = addr - got := deliverer.declareCandidacy(txDeclareCandidacy) - assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadCandidates() + require.Equal(t, i, len(candidates)) val := candidates[i] - balanceGot, balanceExpd := accStore[string(val.Address)], initSender-10 + 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) @@ -176,39 +160,38 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { // unbond them all for i, addr := range addrs { candidatePre := mapper.loadCandidate(addrs[i]) - txUndelegate := NewMsgUnbond(addrs[i], "10") + msgUndelegate := NewMsgUnbond(addrs[i], "10") deliverer.sender = addr - got := deliverer.unbond(txUndelegate) - assert.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadCandidates() assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) candidatePost := mapper.loadCandidate(addrs[i]) - balanceGot, balanceExpd := accStore[string(candidatePre.Address)], initSender + 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) { - //accStore := initAccounts(1000) - // XXX initalize values in accounts to 1000 sender, delegators := addrs[0], addrs[1:] - mapper, deliverer := createTestInput(t, addrs[0], false) + _, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) //first make a candidate - txDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) - got := deliverer.declareCandidacy(txDeclareCandidacy) - require.NoError(t, got, "expected tx to be ok, got %v", got) + 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 { - txDelegate := newTestMsgDelegate(10, sender) + msgDelegate := newTestMsgDelegate(10, sender) deliverer.sender = delegator - got := deliverer.delegate(txDelegate) - require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadDelegatorBond(delegator, sender) @@ -217,10 +200,10 @@ func TestMultipleMsgDelegate(t *testing.T) { // unbond them all for i, delegator := range delegators { - txUndelegate := NewMsgUnbond(sender, "10") + msgUndelegate := NewMsgUnbond(sender, "10") deliverer.sender = delegator - got := deliverer.unbond(txUndelegate) - require.NoError(t, got, "expected tx %d to be ok, got %v", i, got) + 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.loadDelegatorBond(delegator, sender) @@ -229,38 +212,36 @@ func TestMultipleMsgDelegate(t *testing.T) { } func TestVoidCandidacy(t *testing.T) { - // XXX use accountMapper to init all accounts to 1000 - //accStore := initAccounts(1000) sender, delegator := addrs[0], addrs[1] - _, deliverer := createTestInput(t, addrs[0], false) + _, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) // create the candidate - txDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) - got := deliverer.declareCandidacy(txDeclareCandidacy) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // bond a delegator - txDelegate := newTestMsgDelegate(10, addrs[0]) + msgDelegate := newTestMsgDelegate(10, addrs[0]) deliverer.sender = delegator - got = deliverer.delegate(txDelegate) + got = deliverer.delegate(msgDelegate) require.NoError(t, got, "expected ok, got %v", got) // unbond the candidates bond portion - txUndelegate := NewMsgUnbond(addrs[0], "10") + msgUndelegate := NewMsgUnbond(addrs[0], "10") deliverer.sender = sender - got = deliverer.unbond(txUndelegate) + 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(txDelegate) + 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(txUndelegate) + got = deliverer.unbond(msgUndelegate) require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") // verify that the pubkey can now be reused - got = deliverer.declareCandidacy(txDeclareCandidacy) + got = deliverer.declareCandidacy(msgDeclareCandidacy) assert.NoError(t, got, "expected ok, got %v", got) } diff --git a/x/stake/mapper_test.go b/x/stake/mapper_test.go index cba56d6f55..5cf5b1820a 100644 --- a/x/stake/mapper_test.go +++ b/x/stake/mapper_test.go @@ -161,7 +161,7 @@ import ( //} func TestState(t *testing.T) { - mapper, _ := createTestInput(t, nil, false) + _, _, mapper, _ := createTestInput(t, nil, false, 0) addrDel := sdk.Address([]byte("addressdelegator")) addrVal := sdk.Address([]byte("addressvalidator")) @@ -258,7 +258,7 @@ func TestState(t *testing.T) { } func TestGetValidators(t *testing.T) { - mapper, _ := createTestInput(t, nil, false) + _, _, mapper, _ := createTestInput(t, nil, false, 0) candidatesFromAddrs(mapper, addrs, []int64{400, 200, 0, 0, 0}) validators := mapper.getValidators(5) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index f966af705d..11d0752a9b 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -75,11 +75,10 @@ func paramsNoInflation() Params { } // hogpodge of all sorts of input required for testing -//func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (sdk.KVStore, sdk.Context, sdk.StoreKey, Mapper, bank.CoinKeeper, transact) { -func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (Mapper, transact) { +func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Mapper, transact) { db := dbm.NewMemDB() - keyMain := sdk.NewKVStoreKey("main") keyStake := sdk.NewKVStoreKey("stake") + keyMain := keyStake //sdk.NewKVStoreKey("main") //XXX fix multistore ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) @@ -96,12 +95,17 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool) (Mapper, &auth.BaseAccount{}, // prototype ) ck := bank.NewCoinKeeper(accountMapper) - params := paramsNoInflation() mapper.saveParams(params) + + // fill all the addresses with some coins + for _, addr := range addrs { + ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}}) + } + tr := newTransact(ctx, sender, mapper, ck) - return mapper, tr + return ctx, accountMapper, mapper, tr } func newPubKey(pk string) (res crypto.PubKey) { @@ -130,7 +134,7 @@ var pks = []crypto.PubKey{ // for incode address generation func testAddr(addr string) sdk.Address { - res, err := sdk.GetAddress("0XA58856F0FD53BF058B4909A21AEC019107BA6160") + res, err := sdk.GetAddress(addr) if err != nil { panic(err) } @@ -139,16 +143,16 @@ func testAddr(addr string) sdk.Address { // dummy addresses used for testing var addrs = []sdk.Address{ - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6160"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6161"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6162"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6163"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6164"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6165"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6166"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6167"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6168"), - testAddr("0XA58856F0FD53BF058B4909A21AEC019107BA6169"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6163"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6164"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6165"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6166"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6167"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6168"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169"), } // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index e245f773bc..d04581a7c5 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -8,7 +8,7 @@ import ( ) func TestGetInflation(t *testing.T) { - mapper, _ := createTestInput(t, nil, false) + _, _, mapper, _ := createTestInput(t, nil, false, 0) params := mapper.loadParams() gs := mapper.loadGlobalState() @@ -53,7 +53,7 @@ func TestGetInflation(t *testing.T) { } func TestProcessProvisions(t *testing.T) { - mapper, _ := createTestInput(t, nil, false) + _, _, mapper, _ := createTestInput(t, nil, false, 0) params := mapper.loadParams() gs := mapper.loadGlobalState() diff --git a/x/stake/tx.go b/x/stake/tx.go index 82a8925d23..7104b92a8b 100644 --- a/x/stake/tx.go +++ b/x/stake/tx.go @@ -49,9 +49,8 @@ func (msg MsgAddr) ValidateBasic() sdk.Error { type MsgDeclareCandidacy struct { MsgAddr Description - Bond sdk.Coin `json:"bond"` - Address sdk.Address `json:"address"` - PubKey crypto.PubKey `json:"pubkey"` + Bond sdk.Coin `json:"bond"` + PubKey crypto.PubKey `json:"pubkey"` } func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy { @@ -59,7 +58,6 @@ func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk. MsgAddr: NewMsgAddr(address), Description: description, Bond: bond, - Address: address, PubKey: pubkey, } } From 93173e9fddc2a79c8549bfb549f09cd5c9ce3d33 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Mar 2018 14:56:07 +0100 Subject: [PATCH 34/54] working sunny staking refactor --- docs/spec/staking/spec-technical.md | 6 +- x/stake/errors.go | 3 + x/stake/handler.go | 326 ++++++++--------- x/stake/handler_test.go | 16 +- x/stake/keeper.go | 397 +++++++++++++++++++++ x/stake/{mapper_test.go => keeper_test.go} | 62 ++-- x/stake/mapper.go | 360 ------------------- x/stake/msg.go | 213 +++++++++++ x/stake/{tx_test.go => msg_test.go} | 0 x/stake/test_common.go | 4 +- x/stake/tick.go | 6 +- x/stake/tick_test.go | 10 +- x/stake/tx.go | 214 ----------- x/stake/types.go | 35 +- 14 files changed, 825 insertions(+), 827 deletions(-) create mode 100644 x/stake/keeper.go rename x/stake/{mapper_test.go => keeper_test.go} (86%) delete mode 100644 x/stake/mapper.go create mode 100644 x/stake/msg.go rename x/stake/{tx_test.go => msg_test.go} (100%) delete mode 100644 x/stake/tx.go diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index 8d9baa7967..5f340877e7 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -274,7 +274,7 @@ type TxDeclareCandidacy struct { } declareCandidacy(tx TxDeclareCandidacy): - candidate = loadCandidate(store, tx.PubKey) + candidate = getCandidate(store, msg.Address) if candidate != nil return // candidate with that public key already exists candidate = NewCandidate(tx.PubKey) @@ -285,7 +285,7 @@ declareCandidacy(tx TxDeclareCandidacy): candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description - saveCandidate(store, candidate) + setCandidate(store, candidate) txDelegate = TxDelegate(tx.PubKey, tx.Amount) return delegateWithCandidate(txDelegate, candidate) @@ -343,7 +343,7 @@ delegateWithCandidate(tx TxDelegate, candidate Candidate): poolAccount = params.HoldBonded else poolAccount = params.HoldUnbonded - + err = transfer(sender, poolAccount, tx.Amount) if err != nil return diff --git a/x/stake/errors.go b/x/stake/errors.go index 68b6bd9a6e..aae855f6e1 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -72,6 +72,9 @@ func ErrBadValidatorAddr() sdk.Error { func ErrBadCandidateAddr() sdk.Error { return newError(CodeInvalidValidator, "Candidate does not exist for that address") } +func ErrBadDelegatorAddr() sdk.Error { + return newError(CodeInvalidValidator, "Delegator does not exist for that address") +} func ErrCandidateExistsAddr() sdk.Error { return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy") } diff --git a/x/stake/handler.go b/x/stake/handler.go index 402d3e2f18..e96e8484c4 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -2,93 +2,67 @@ package stake import ( "bytes" - "fmt" - "strconv" - - crypto "github.com/tendermint/go-crypto" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/bank" ) +//nolint +const ( + GasDeclareCandidacy int64 = 20 + GasEditCandidacy int64 = 20 + GasDelegate int64 = 20 + GasUnbond int64 = 20 +) + +//XXX fix initstater // separated for testing -func InitState(ctx sdk.Context, mapper Mapper, key, value string) sdk.Error { +//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error { - params := mapper.loadParams() - switch key { - case "allowed_bond_denom": - params.BondDenom = value - case "max_vals", "gas_bond", "gas_unbond": +//params := k.getParams(ctx) +//switch key { +//case "allowed_bond_denom": +//params.BondDenom = value +//case "max_vals", "gas_bond", "gas_unbond": - i, err := strconv.Atoi(value) - if err != nil { - return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error())) - } +//i, err := strconv.Atoi(value) +//if err != nil { +//return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error())) +//} - switch key { - case "max_vals": - if i < 0 { - return sdk.ErrUnknownRequest("cannot designate negative max validators") - } - params.MaxVals = uint16(i) - case "gas_bond": - params.GasDelegate = int64(i) - case "gas_unbound": - params.GasUnbond = int64(i) - } - default: - return sdk.ErrUnknownRequest(key) - } +//switch key { +//case "max_vals": +//if i < 0 { +//return sdk.ErrUnknownRequest("cannot designate negative max validators") +//} +//params.MaxValidators = uint16(i) +//case "gas_bond": +//GasDelegate = int64(i) +//case "gas_unbound": +//GasUnbond = int64(i) +//} +//default: +//return sdk.ErrUnknownRequest(key) +//} - mapper.saveParams(params) - return nil -} +//k.setParams(params) +//return nil +//} //_______________________________________________________________________ -func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler { +func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { - - params := mapper.loadParams() - - err := msg.ValidateBasic() - if err != nil { - return err.Result() // TODO should also return gasUsed? - } - signers := msg.GetSigners() - if len(signers) != 1 { - return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result() - } - sender := signers[0] - - transact := newTransact(ctx, sender, mapper, ck) - - // Run the transaction + // NOTE msg already has validate basic run switch msg := msg.(type) { case MsgDeclareCandidacy: - res := transact.declareCandidacy(msg).Result() - if !ctx.IsCheckTx() { - res.GasUsed = params.GasDeclareCandidacy - } - return res + return k.handleMsgDeclareCandidacy(ctx, msg) case MsgEditCandidacy: - res := transact.editCandidacy(msg).Result() - if !ctx.IsCheckTx() { - res.GasUsed = params.GasEditCandidacy - } - return res + return k.handleMsgEditCandidacy(ctx, msg) case MsgDelegate: - res := transact.delegate(msg).Result() - if !ctx.IsCheckTx() { - res.GasUsed = params.GasDelegate - } - return res + return k.handleMsgDelegate(ctx, msg) case MsgUnbond: - res := transact.unbond(msg).Result() - if !ctx.IsCheckTx() { - res.GasUsed = params.GasUnbond - } - return res + return k.handleMsgUnbond(ctx, msg) default: return sdk.ErrTxParse("invalid message parse in staking module").Result() } @@ -97,249 +71,239 @@ func NewHandler(mapper Mapper, ck bank.CoinKeeper) sdk.Handler { //_____________________________________________________________________ -// common fields to all transactions -type transact struct { - ctx sdk.Context - sender crypto.Address - mapper Mapper - coinKeeper bank.CoinKeeper - params Params - gs *GlobalState -} - -func newTransact(ctx sdk.Context, sender sdk.Address, mapper Mapper, ck bank.CoinKeeper) transact { - return transact{ - ctx: ctx, - sender: sender, - mapper: mapper, - coinKeeper: ck, - params: mapper.loadParams(), - gs: mapper.loadGlobalState(), - } -} +// 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] +//} //_____________________________________________________________________ // helper functions // move a candidates asset pool from bonded to unbonded pool -func (tr transact) bondedToUnbondedPool(candidate *Candidate) { +func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate *Candidate) { // replace bonded shares with unbonded shares - tokens := tr.gs.removeSharesBonded(candidate.Assets) - candidate.Assets = tr.gs.addTokensUnbonded(tokens) + tokens := k.getGlobalState(ctx).removeSharesBonded(candidate.Assets) + candidate.Assets = k.getGlobalState(ctx).addTokensUnbonded(tokens) candidate.Status = Unbonded } // move a candidates asset pool from unbonded to bonded pool -func (tr transact) unbondedToBondedPool(candidate *Candidate) { +func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate *Candidate) { // replace unbonded shares with bonded shares - tokens := tr.gs.removeSharesUnbonded(candidate.Assets) - candidate.Assets = tr.gs.addTokensBonded(tokens) + tokens := k.getGlobalState(ctx).removeSharesUnbonded(candidate.Assets) + candidate.Assets = k.getGlobalState(ctx).addTokensBonded(tokens) candidate.Status = Bonded } -// return an error if the bonds coins are incorrect -func checkDenom(mapper Mapper, bond sdk.Coin) sdk.Error { - if bond.Denom != mapper.loadParams().BondDenom { - return ErrBadBondingDenom() - } - return nil -} - //_____________________________________________________________________ // These functions assume everything has been authenticated, // now we just perform action and save -func (tr transact) declareCandidacy(tx MsgDeclareCandidacy) sdk.Error { +func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) sdk.Result { // check to see if the pubkey or sender has been registered before - if tr.mapper.loadCandidate(tx.Address) != nil { + if k.getCandidate(msg.Address) != nil { return ErrCandidateExistsAddr() } - err := checkDenom(tr.mapper, tx.Bond) - if err != nil { - return err + if msg.bond.Denom != k.getParams().BondDenom { + return ErrBadBondingDenom() } - if tr.ctx.IsCheckTx() { - return nil + if ctx.IsCheckTx() { + return sdk.Result{ + GasUsed: GasDeclareCandidacy, + } } - candidate := NewCandidate(tx.PubKey, tr.sender, tx.Description) - tr.mapper.saveCandidate(candidate) + candidate := NewCandidate(msg.PubKey, msg.Address, msg.Description) + k.setCandidate(candidate) - // move coins from the tr.sender account to a (self-bond) delegator account + // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - txDelegate := NewMsgDelegate(tx.Address, tx.Bond) - return tr.delegateWithCandidate(txDelegate, candidate) + txDelegate := NewMsgDelegate(msg.Address, msg.Bond) + return delegateWithCandidate(txDelegate, candidate) } -func (tr transact) editCandidacy(tx MsgEditCandidacy) sdk.Error { +func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sdk.Result { // candidate must already be registered - if tr.mapper.loadCandidate(tx.Address) == nil { - return ErrBadCandidateAddr() + if k.getCandidate(msg.Address) == nil { + return ErrBadCandidateAddr().Result() } - if tr.ctx.IsCheckTx() { - return nil + if ctx.IsCheckTx() { + return sdk.Result{ + GasUsed: GasEditCandidacy, + } } // Get the pubKey bond account - candidate := tr.mapper.loadCandidate(tx.Address) + candidate := k.getCandidate(msg.Address) if candidate == nil { - return ErrBondNotNominated() + return ErrBondNotNominated().Result() } if candidate.Status == Unbonded { //candidate has been withdrawn - return ErrBondNotNominated() + return ErrBondNotNominated().Result() } //check and edit any of the editable terms - if tx.Description.Moniker != "" { - candidate.Description.Moniker = tx.Description.Moniker + if msg.Description.Moniker != "" { + candidate.Description.Moniker = msg.Description.Moniker } - if tx.Description.Identity != "" { - candidate.Description.Identity = tx.Description.Identity + if msg.Description.Identity != "" { + candidate.Description.Identity = msg.Description.Identity } - if tx.Description.Website != "" { - candidate.Description.Website = tx.Description.Website + if msg.Description.Website != "" { + candidate.Description.Website = msg.Description.Website } - if tx.Description.Details != "" { - candidate.Description.Details = tx.Description.Details + if msg.Description.Details != "" { + candidate.Description.Details = msg.Description.Details } - tr.mapper.saveCandidate(candidate) + k.setCandidate(candidate) return nil } -func (tr transact) delegate(tx MsgDelegate) sdk.Error { +func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { - if tr.mapper.loadCandidate(tx.Address) == nil { - return ErrBadCandidateAddr() + if k.getCandidate(msg.Address) == nil { + return ErrBadCandidateAddr().Result() } - err := checkDenom(tr.mapper, tx.Bond) - if err != nil { - return err + if msg.bond.Denom != k.getParams().BondDenom { + return ErrBadBondingDenom().Result() } - if tr.ctx.IsCheckTx() { - return nil + if ctx.IsCheckTx() { + return sdk.Result{ + GasUsed: GasDelegate, + } } // Get the pubKey bond account - candidate := tr.mapper.loadCandidate(tx.Address) + candidate := k.getCandidate(msg.Address) if candidate == nil { - return ErrBondNotNominated() + return ErrBondNotNominated().Result() } - return tr.delegateWithCandidate(tx, candidate) + + return tr.delegateWithCandidate(msg, candidate).Result() } -func (tr transact) delegateWithCandidate(tx MsgDelegate, candidate *Candidate) sdk.Error { +func (k Keeper) delegateWithCandidate(ctx sdk.Context, candidateAddr, delegatorAddr sdk.Address, + bondAmt sdk.Coin, candidate Candidate) sdk.Error { if candidate.Status == Revoked { //candidate has been withdrawn return ErrBondNotNominated() } // Get or create the delegator bond - bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) + bond := k.getDelegatorBond(tr.sender, canad) if bond == nil { bond = &DelegatorBond{ - Address: tx.Address, - Shares: sdk.ZeroRat, + CandidateAddr: delegatorAddr, + DelegatorAddr: candidateAddr, + Shares: sdk.ZeroRat, } } // Account new shares, save - err := tr.BondCoins(bond, candidate, tx.Bond) + err := BondCoins(bond, candidate, msg.Bond) if err != nil { - return err + return err.Result() } - tr.mapper.saveDelegatorBond(tr.sender, bond) - tr.mapper.saveCandidate(candidate) - tr.mapper.saveGlobalState(tr.gs) + k.setDelegatorBond(tr.sender, bond) + k.setCandidate(candidate) + k.setGlobalState(tr.gs) return nil } // Perform all the actions required to bond tokens to a delegator bond from their account -func (tr *transact) BondCoins(bond *DelegatorBond, candidate *Candidate, tokens sdk.Coin) sdk.Error { +func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, amount sdk.Coin) sdk.Error { - _, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens}) + _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount}) if err != nil { return err } newShares := candidate.addTokens(tokens.Amount, tr.gs) bond.Shares = bond.Shares.Add(newShares) + k.SetDelegatorBond() return nil } // Perform all the actions required to bond tokens to a delegator bond from their account -func (tr *transact) UnbondCoins(bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error { +func (k Keeper) UnbondCoins(ctx sdk.Context, bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error { // subtract bond tokens from delegator bond if bond.Shares.LT(shares) { - return sdk.ErrInsufficientFunds("") // TODO + return sdk.ErrInsufficientFunds("") //XXX variables inside } bond.Shares = bond.Shares.Sub(shares) returnAmount := candidate.removeShares(shares, tr.gs) returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - _, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins) + _, err := tr.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) if err != nil { return err } return nil } -func (tr transact) unbond(tx MsgUnbond) sdk.Error { +func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { // check if bond has any shares in it unbond - bond := tr.mapper.loadDelegatorBond(tr.sender, tx.Address) + bond := k.getDelegatorBond(sender, msg.Address) if bond == nil { - return ErrNoDelegatorForAddress() + return ErrNoDelegatorForAddress().Result() } - if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < tx shares - return ErrInsufficientFunds() + if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < msg shares + return ErrInsufficientFunds().Result() } // if shares set to special case Max then we're good - if tx.Shares != "MAX" { + if msg.Shares != "MAX" { // test getting rational number from decimal provided - shares, err := sdk.NewRatFromDecimal(tx.Shares) + shares, err := sdk.NewRatFromDecimal(msg.Shares) if err != nil { - return err + return err.Result() } // test that there are enough shares to unbond if !bond.Shares.GT(shares) { - return ErrNotEnoughBondShares(tx.Shares) + return ErrNotEnoughBondShares(msg.Shares).Result() } } - if tr.ctx.IsCheckTx() { - return nil + if ctx.IsCheckTx() { + return sdk.Result{ + GasUsed: GasUnbond, + } } // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) var shares sdk.Rat var err sdk.Error - if tx.Shares == "MAX" { + if msg.Shares == "MAX" { shares = bond.Shares } else { - shares, err = sdk.NewRatFromDecimal(tx.Shares) + shares, err = sdk.NewRatFromDecimal(msg.Shares) if err != nil { - return err + return err.Result() } } // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { // bond shares < tx shares - return ErrInsufficientFunds() + if bond.Shares.LT(shares) { // bond shares < msg shares + return ErrInsufficientFunds().Result() } bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate := tr.mapper.loadCandidate(tx.Address) + candidate := k.getCandidate(msg.Address) if candidate == nil { - return ErrNoCandidateForAddress() + return ErrNoCandidateForAddress().Result() } revokeCandidacy := false @@ -353,15 +317,15 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error { } // remove the bond - tr.mapper.removeDelegatorBond(tr.sender, tx.Address) + k.removeDelegatorBond(ctx, msg.Address) } else { - tr.mapper.saveDelegatorBond(tr.sender, bond) + k.setDelegatorBond(tr.sender, bond) } // Add the coins returnAmount := candidate.removeShares(shares, tr.gs) returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - tr.coinKeeper.AddCoins(tr.ctx, tr.sender, returnCoins) + tr.coinKeeper.AddCoins(ctx, tr.sender, returnCoins) // lastly if an revoke candidate if necessary if revokeCandidacy { @@ -377,11 +341,11 @@ func (tr transact) unbond(tx MsgUnbond) sdk.Error { // deduct shares from the candidate and save if candidate.Liabilities.IsZero() { - tr.mapper.removeCandidate(tx.Address) + k.removeCandidate(msg.Address) } else { - tr.mapper.saveCandidate(candidate) + k.setCandidate(candidate) } - tr.mapper.saveGlobalState(tr.gs) - return nil + k.setGlobalState(tr.gs) + return sdk.Result{} } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 824fe88e0a..214ee43503 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -67,7 +67,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { 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.loadCandidates() + candidates := mapper.getCandidates() expectedBond += bondAmount //expectedSender := initSender - expectedBond gotBonded := candidates[0].Liabilities.Evaluate() @@ -96,7 +96,7 @@ func TestIncrementsMsgUnbond(t *testing.T) { 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.loadCandidates() + 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() @@ -147,7 +147,7 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded - candidates := mapper.loadCandidates() + candidates := mapper.getCandidates() require.Equal(t, i, len(candidates)) val := candidates[i] balanceExpd := initSender - 10 @@ -159,17 +159,17 @@ func TestMultipleMsgDeclareCandidacy(t *testing.T) { // unbond them all for i, addr := range addrs { - candidatePre := mapper.loadCandidate(addrs[i]) + 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.loadCandidates() + 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.loadCandidate(addrs[i]) + 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) @@ -194,7 +194,7 @@ func TestMultipleMsgDelegate(t *testing.T) { require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) //Check that the account is bonded - bond := mapper.loadDelegatorBond(delegator, sender) + bond := mapper.getDelegatorBond(delegator, sender) assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) } @@ -206,7 +206,7 @@ func TestMultipleMsgDelegate(t *testing.T) { require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) //Check that the account is unbonded - bond := mapper.loadDelegatorBond(delegator, sender) + bond := mapper.getDelegatorBond(delegator, sender) assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) } } diff --git a/x/stake/keeper.go b/x/stake/keeper.go new file mode 100644 index 0000000000..2428aaeca7 --- /dev/null +++ b/x/stake/keeper.go @@ -0,0 +1,397 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +//nolint +var ( + // Keys for store prefixes + CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses + ParamKey = []byte{0x02} // key for global parameters relating to staking + GlobalStateKey = []byte{0x03} // key for global parameters relating to staking + + // Key prefixes + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond +) + +// XXX remove beggining word get from all these keys +// GetCandidateKey - get the key for the candidate with address +func GetCandidateKey(addr sdk.Address) []byte { + return append(CandidateKeyPrefix, addr.Bytes()...) +} + +// GetValidatorKey - get the key for the validator used in the power-store +func GetValidatorKey(addr sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte { + b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? + return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store +} + +// GetValidatorUpdatesKey - get the key for the validator used in the power-store +func GetValidatorUpdatesKey(addr sdk.Address) []byte { + return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store +} + +// GetDelegatorBondKey - get the key for delegator bond with candidate +func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { + return append(GetDelegatorBondKeyPrefix(delegatorAddr, cdc), candidateAddr.Bytes()...) +} + +// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates +func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res, err := cdc.MarshalJSON(&delegatorAddr) + if err != nil { + panic(err) + } + return append(DelegatorBondKeyPrefix, res...) +} + +// GetDelegatorBondsKey - get the key for list of all the delegator's bonds +func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res, err := cdc.MarshalJSON(&delegatorAddr) + if err != nil { + panic(err) + } + return append(DelegatorBondsKeyPrefix, res...) +} + +//___________________________________________________________________________ + +// keeper of the staking store +type Keeper struct { + storeKey sdk.StoreKey + cdc *wire.Codec + coinKeeper bank.CoinKeeper + + //just caches + gs GlobalState + params Params +} + +func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinKeeper) Keeper { + keeper := Keeper{ + storeKey: key, + cdc: cdc, + coinKeeper: ck, + } + return keeper +} + +//XXX load/save -> get/set +func (m Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate) { + store := ctx.KVStore(storeKey) + b := store.Get(GetCandidateKey(addr)) + if b == nil { + return nil + } + err := m.cdc.UnmarshalJSON(b, &candidate) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} + +func (m Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { + store := ctx.KVStore(storeKey) + + // XXX should only remove validator if we know candidate is a validator + m.removeValidator(candidate.Address) + validator := &Validator{candidate.Address, candidate.VotingPower} + m.updateValidator(validator) + + b, err := m.cdc.MarshalJSON(candidate) + if err != nil { + panic(err) + } + store.Set(GetCandidateKey(candidate.Address), b) +} + +func (m Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { + store := ctx.KVStore(storeKey) + + // XXX should only remove validator if we know candidate is a validator + m.removeValidator(candidateAddr) + store.Delete(GetCandidateKey(candidateAddr)) +} + +//___________________________________________________________________________ + +//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator { +//b := store.Get(GetValidatorKey(address, votingPower)) +//if b == nil { +//return nil +//} +//validator := new(Validator) +//err := cdc.UnmarshalJSON(b, validator) +//if err != nil { +//panic(err) // This error should never occur big problem if does +//} +//return validator +//} + +// updateValidator - update a validator and create accumulate any changes +// in the changed validator substore +func (m Keeper) updateValidator(ctx sdk.Context, validator Validator) { + store := ctx.KVStore(storeKey) + + b, err := m.cdc.MarshalJSON(validator) + if err != nil { + panic(err) + } + + // add to the validators to update list if necessary + store.Set(GetValidatorUpdatesKey(validator.Address), b) + + // update the list ordered by voting power + store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b) +} + +func (m Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { + store := ctx.KVStore(storeKey) + + //add validator with zero power to the validator updates + b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) + if err != nil { + panic(err) + } + store.Set(GetValidatorUpdatesKey(address), b) + + // now actually delete from the validator set + candidate := m.getCandidate(address) + if candidate != nil { + store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc)) + } +} + +// get the most recent updated validator set from the Candidates. These bonds +// are already sorted by VotingPower from the UpdateVotingPower function which +// is the only function which is to modify the VotingPower +func (m Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Validator) { + store := ctx.KVStore(storeKey) + + iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest + + validators = make([]Validator, maxVal) + for i := 0; ; i++ { + if !iterator.Valid() || i > int(maxVal) { + iterator.Close() + break + } + valBytes := iterator.Value() + var val Validator + err := m.cdc.UnmarshalJSON(valBytes, &val) + if err != nil { + panic(err) + } + validators[i] = val + iterator.Next() + } + + return +} + +//_________________________________________________________________________ + +// get the most updated validators +func (m Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { + store := ctx.KVStore(storeKey) + + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest + + for ; iterator.Valid(); iterator.Next() { + valBytes := iterator.Value() + var val Validator + err := m.cdc.UnmarshalJSON(valBytes, &val) + if err != nil { + panic(err) + } + updates = append(updates, val) + } + + iterator.Close() + return +} + +// remove all validator update entries +func (m Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { + store := ctx.KVStore(storeKey) + iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + } + iterator.Close() +} + +//--------------------------------------------------------------------- + +// getCandidates - get the active list of all candidates TODO replace with multistore +func (m Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { + store := ctx.KVStore(storeKey) + + iterator := store.Iterator(subspace(CandidateKeyPrefix)) + //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) + //iterator := store.Iterator([]byte{}, []byte(nil)) + + for ; iterator.Valid(); iterator.Next() { + candidateBytes := iterator.Value() + var candidate Candidate + err := m.cdc.UnmarshalJSON(candidateBytes, &candidate) + if err != nil { + panic(err) + } + candidates = append(candidates, &candidate) + } + iterator.Close() + return candidates +} + +//_____________________________________________________________________ + +// XXX use a store iterator to get +//// load the pubkeys of all candidates a delegator is delegated too +//func (m Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) { +//store := ctx.KVStore(storeKey) + +//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, m.cdc)) +//if candidateBytes == nil { +//return nil +//} + +//err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) +//if err != nil { +//panic(err) +//} +//return +//} + +//_____________________________________________________________________ + +func (m Keeper) getDelegatorBond(ctx sdk.Context, + delegator, candidate sdk.Address) (bond DelegatorBond) { + + store := ctx.KVStore(storeKey) + delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc)) + if delegatorBytes == nil { + return nil + } + + err := m.cdc.UnmarshalJSON(delegatorBytes, &bond) + if err != nil { + panic(err) + } + return bond +} + +func (m Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(storeKey) + + // XXX use store iterator + // if a new bond add to the list of bonds + //if m.getDelegatorBond(delegator, bond.Address) == nil { + //pks := m.getDelegatorCandidates(delegator) + //pks = append(pks, bond.Address) + //b, err := m.cdc.MarshalJSON(pks) + //if err != nil { + //panic(err) + //} + //store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) + //} + + // now actually save the bond + b, err := m.cdc.MarshalJSON(bond) + if err != nil { + panic(err) + } + store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b) +} + +func (m Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(storeKey) + + // XXX use store iterator + // TODO use list queries on multistore to remove iterations here! + // first remove from the list of bonds + //addrs := m.getDelegatorCandidates(delegator) + //for i, addr := range addrs { + //if bytes.Equal(candidateAddr, addr) { + //addrs = append(addrs[:i], addrs[i+1:]...) + //} + //} + //b, err := m.cdc.MarshalJSON(addrs) + //if err != nil { + //panic(err) + //} + //store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) + + // now remove the actual bond + store.Delete(GetDelegatorBondKey(bond.delegatorAddr, bond.candidateAddr, m.cdc)) + //updateDelegatorBonds(store, delegator) //XXX remove? +} + +//_______________________________________________________________________ + +// load/save the global staking params +func (m Keeper) getParams(ctx sdk.Context) (params Params) { + // check if cached before anything + if m.params != (Params{}) { + return m.params + } + store := ctx.KVStore(storeKey) + b := store.Get(ParamKey) + if b == nil { + return defaultParams() + } + + err := m.cdc.UnmarshalJSON(b, ¶ms) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} +func (m Keeper) setParams(ctx sdk.Context, params Params) { + store := ctx.KVStore(storeKey) + b, err := m.cdc.MarshalJSON(params) + if err != nil { + panic(err) + } + store.Set(ParamKey, b) + m.params = Params{} // clear the cache +} + +//_______________________________________________________________________ + +// XXX nothing is this Keeper should return a pointer...!!!!!! +// load/save the global staking state +func (m Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { + // check if cached before anything + if m.gs != nil { + return m.gs + } + store := ctx.KVStore(storeKey) + b := store.Get(GlobalStateKey) + if b == nil { + return initialGlobalState() + } + gs = new(GlobalState) + err := m.cdc.UnmarshalJSON(b, &gs) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} + +func (m Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { + store := ctx.KVStore(storeKey) + b, err := m.cdc.MarshalJSON(gs) + if err != nil { + panic(err) + } + store.Set(GlobalStateKey, b) + m.gs = GlobalState{} // clear the cache +} diff --git a/x/stake/mapper_test.go b/x/stake/keeper_test.go similarity index 86% rename from x/stake/mapper_test.go rename to x/stake/keeper_test.go index 5cf5b1820a..680b516429 100644 --- a/x/stake/mapper_test.go +++ b/x/stake/keeper_test.go @@ -18,8 +18,8 @@ import ( //func TestUpdateVotingPower(t *testing.T) { //assert := assert.New(t) //store := initTestStore(t) -//params := loadParams(store) -//gs := loadGlobalState(store) +//params := getParams(store) +//gs := getGlobalState(store) //N := 5 //actors := newAddrs(N) @@ -38,7 +38,7 @@ import ( //// test the max validators term //params.MaxVals = 4 -//saveParams(store, params) +//setParams(store, params) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) //} @@ -118,11 +118,11 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := loadParams(store) //gs := loadGlobalState(store) //N := 5 +//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getGlobalState(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { -//saveCandidate(store, c) +//setCandidate(store, c) //} //// they should all already be validators @@ -132,12 +132,12 @@ import ( //// test the max value and test again //params.MaxVals = 4 -//saveParams(store, params) +//setParams(store, params) //change, err = UpdateValidatorSet(store, gs, params) //require.Nil(err) //require.Equal(1, len(change), "%v", change) //testRemove(t, candidates[4].validator(), change[0]) -//candidates = loadCandidates(store) +//candidates = getCandidates(store) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) //// mess with the power's of the candidates and test @@ -147,12 +147,12 @@ import ( //candidates[3].Assets = sdk.OneRat //candidates[4].Assets = sdk.NewRat(10) //for _, c := range candidates { -//saveCandidate(store, c) +//setCandidate(store, c) //} //change, err = UpdateValidatorSet(store, gs, params) //require.Nil(err) //require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed -//candidates = loadCandidates(store) +//candidates = getCandidates(store) //testChange(t, candidates[0].validator(), change[0]) //testChange(t, candidates[1].validator(), change[1]) //testChange(t, candidates[2].validator(), change[2]) @@ -161,7 +161,7 @@ import ( //} func TestState(t *testing.T) { - _, _, mapper, _ := createTestInput(t, nil, false, 0) + _, _, keeper, _ := createTestInput(t, nil, false, 0) addrDel := sdk.Address([]byte("addressdelegator")) addrVal := sdk.Address([]byte("addressvalidator")) @@ -190,26 +190,26 @@ func TestState(t *testing.T) { c1.Description == c2.Description } - // check the empty mapper first - resCand := mapper.loadCandidate(addrVal) + // check the empty keeper first + resCand := keeper.getCandidate(addrVal) assert.Nil(t, resCand) - resPks := mapper.loadCandidates() + resPks := keeper.getCandidates() assert.Zero(t, len(resPks)) // set and retrieve a record - mapper.saveCandidate(candidate) - resCand = mapper.loadCandidate(addrVal) + keeper.setCandidate(candidate) + resCand = keeper.getCandidate(addrVal) //assert.Equal(candidate, resCand) assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve candidate.Liabilities = sdk.NewRat(99) - mapper.saveCandidate(candidate) - resCand = mapper.loadCandidate(addrVal) + keeper.setCandidate(candidate) + resCand = keeper.getCandidate(addrVal) assert.True(t, candidatesEqual(candidate, resCand)) // also test that the pubkey has been added to pubkey list - resPks = mapper.loadCandidates() + resPks = keeper.getCandidates() require.Equal(t, 1, len(resPks)) assert.Equal(t, addrVal, resPks[0].PubKey) @@ -226,19 +226,19 @@ func TestState(t *testing.T) { b1.Shares == b2.Shares } - //check the empty mapper first - resBond := mapper.loadDelegatorBond(addrDel, addrVal) + //check the empty keeper first + resBond := keeper.getDelegatorBond(addrDel, addrVal) assert.Nil(t, resBond) //Set and retrieve a record - mapper.saveDelegatorBond(addrDel, bond) - resBond = mapper.loadDelegatorBond(addrDel, addrVal) + keeper.setDelegatorBond(addrDel, bond) + resBond = keeper.getDelegatorBond(addrDel, addrVal) assert.True(t, bondsEqual(bond, resBond)) //modify a records, save, and retrieve bond.Shares = sdk.NewRat(99) - mapper.saveDelegatorBond(addrDel, bond) - resBond = mapper.loadDelegatorBond(addrDel, addrVal) + keeper.setDelegatorBond(addrDel, bond) + resBond = keeper.getDelegatorBond(addrDel, addrVal) assert.True(t, bondsEqual(bond, resBond)) //---------------------------------------------------------------------- @@ -246,22 +246,22 @@ func TestState(t *testing.T) { params := defaultParams() - //check that the empty mapper loads the default - resParams := mapper.loadParams() + //check that the empty keeper loads the default + resParams := keeper.getParams() assert.Equal(t, params, resParams) //modify a params, save, and retrieve params.MaxVals = 777 - mapper.saveParams(params) - resParams = mapper.loadParams() + keeper.setParams(params) + resParams = keeper.getParams() assert.Equal(t, params, resParams) } func TestGetValidators(t *testing.T) { - _, _, mapper, _ := createTestInput(t, nil, false, 0) - candidatesFromAddrs(mapper, addrs, []int64{400, 200, 0, 0, 0}) + _, _, keeper, _ := createTestInput(t, nil, false, 0) + candidatesFromAddrs(keeper, addrs, []int64{400, 200, 0, 0, 0}) - validators := mapper.getValidators(5) + validators := keeper.getValidators(5) require.Equal(t, 2, len(validators)) assert.Equal(t, addrs[0], validators[0].Address) assert.Equal(t, addrs[1], validators[1].Address) diff --git a/x/stake/mapper.go b/x/stake/mapper.go deleted file mode 100644 index caebe84999..0000000000 --- a/x/stake/mapper.go +++ /dev/null @@ -1,360 +0,0 @@ -package stake - -import ( - "bytes" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/wire" -) - -//nolint -var ( - // Keys for store prefixes - CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses - ParamKey = []byte{0x02} // key for global parameters relating to staking - GlobalStateKey = []byte{0x03} // key for global parameters relating to staking - - // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond -) - -// GetCandidateKey - get the key for the candidate with address -func GetCandidateKey(address sdk.Address) []byte { - return append(CandidateKeyPrefix, address.Bytes()...) -} - -// GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(address sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte { - b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? - return append(ValidatorKeyPrefix, append(b, address.Bytes()...)...) // TODO does this need prefix if its in its own store -} - -// GetValidatorUpdatesKey - get the key for the validator used in the power-store -func GetValidatorUpdatesKey(address sdk.Address) []byte { - return append(ValidatorUpdatesKeyPrefix, address.Bytes()...) // TODO does this need prefix if its in its own store -} - -// GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegator, candidate sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegatorBondKeyPrefix(delegator, cdc), candidate.Bytes()...) -} - -// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegator sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalJSON(&delegator) - if err != nil { - panic(err) - } - return append(DelegatorBondKeyPrefix, res...) -} - -// GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegator sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalJSON(&delegator) - if err != nil { - panic(err) - } - return append(DelegatorBondsKeyPrefix, res...) -} - -//___________________________________________________________________________ - -// mapper of the staking store -type Mapper struct { - store sdk.KVStore - cdc *wire.Codec -} - -func NewMapper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey) Mapper { - return Mapper{ - store: ctx.KVStore(key), - cdc: cdc, - } -} - -func (m Mapper) loadCandidate(address sdk.Address) *Candidate { - b := m.store.Get(GetCandidateKey(address)) - if b == nil { - return nil - } - candidate := new(Candidate) - err := m.cdc.UnmarshalJSON(b, candidate) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return candidate -} - -func (m Mapper) saveCandidate(candidate *Candidate) { - - // XXX should only remove validator if we know candidate is a validator - m.removeValidator(candidate.Address) - validator := &Validator{candidate.Address, candidate.VotingPower} - m.updateValidator(validator) - - b, err := m.cdc.MarshalJSON(*candidate) - if err != nil { - panic(err) - } - m.store.Set(GetCandidateKey(candidate.Address), b) -} - -func (m Mapper) removeCandidate(address sdk.Address) { - - // XXX should only remove validator if we know candidate is a validator - m.removeValidator(address) - m.store.Delete(GetCandidateKey(address)) -} - -//___________________________________________________________________________ - -//func loadValidator(m.store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator { -//b := m.store.Get(GetValidatorKey(address, votingPower)) -//if b == nil { -//return nil -//} -//validator := new(Validator) -//err := cdc.UnmarshalJSON(b, validator) -//if err != nil { -//panic(err) // This error should never occur big problem if does -//} -//return validator -//} - -// updateValidator - update a validator and create accumulate any changes -// in the changed validator substore -func (m Mapper) updateValidator(validator *Validator) { - - b, err := m.cdc.MarshalJSON(*validator) - if err != nil { - panic(err) - } - - // add to the validators to update list if necessary - m.store.Set(GetValidatorUpdatesKey(validator.Address), b) - - // update the list ordered by voting power - m.store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b) -} - -func (m Mapper) removeValidator(address sdk.Address) { - - //add validator with zero power to the validator updates - b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) - if err != nil { - panic(err) - } - m.store.Set(GetValidatorUpdatesKey(address), b) - - // now actually delete from the validator set - candidate := m.loadCandidate(address) - if candidate != nil { - m.store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc)) - } -} - -// get the most recent updated validator set from the Candidates. These bonds -// are already sorted by VotingPower from the UpdateVotingPower function which -// is the only function which is to modify the VotingPower -func (m Mapper) getValidators(maxVal uint16) (validators []Validator) { - - iterator := m.store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest - - validators = make([]Validator, maxVal) - for i := 0; ; i++ { - if !iterator.Valid() || i > int(maxVal) { - iterator.Close() - break - } - valBytes := iterator.Value() - var val Validator - err := m.cdc.UnmarshalJSON(valBytes, &val) - if err != nil { - panic(err) - } - validators[i] = val - iterator.Next() - } - - return -} - -//_________________________________________________________________________ - -// get the most updated validators -func (m Mapper) getValidatorUpdates() (updates []Validator) { - - iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest - - for ; iterator.Valid(); iterator.Next() { - valBytes := iterator.Value() - var val Validator - err := m.cdc.UnmarshalJSON(valBytes, &val) - if err != nil { - panic(err) - } - updates = append(updates, val) - } - - iterator.Close() - return -} - -// remove all validator update entries -func (m Mapper) clearValidatorUpdates(maxVal int) { - iterator := m.store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) - for ; iterator.Valid(); iterator.Next() { - m.store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop - } - iterator.Close() -} - -//--------------------------------------------------------------------- - -// loadCandidates - get the active list of all candidates TODO replace with multistore -func (m Mapper) loadCandidates() (candidates Candidates) { - - iterator := m.store.Iterator(subspace(CandidateKeyPrefix)) - //iterator := m.store.Iterator(CandidateKeyPrefix, []byte(nil)) - //iterator := m.store.Iterator([]byte{}, []byte(nil)) - - for ; iterator.Valid(); iterator.Next() { - candidateBytes := iterator.Value() - var candidate Candidate - err := m.cdc.UnmarshalJSON(candidateBytes, &candidate) - if err != nil { - panic(err) - } - candidates = append(candidates, &candidate) - } - iterator.Close() - return candidates -} - -//_____________________________________________________________________ - -// load the pubkeys of all candidates a delegator is delegated too -func (m Mapper) loadDelegatorCandidates(delegator sdk.Address) (candidateAddrs []sdk.Address) { - - candidateBytes := m.store.Get(GetDelegatorBondsKey(delegator, m.cdc)) - if candidateBytes == nil { - return nil - } - - err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) - if err != nil { - panic(err) - } - return -} - -//_____________________________________________________________________ - -func (m Mapper) loadDelegatorBond(delegator, candidate sdk.Address) *DelegatorBond { - - delegatorBytes := m.store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc)) - if delegatorBytes == nil { - return nil - } - - bond := new(DelegatorBond) - err := m.cdc.UnmarshalJSON(delegatorBytes, bond) - if err != nil { - panic(err) - } - return bond -} - -func (m Mapper) saveDelegatorBond(delegator sdk.Address, - bond *DelegatorBond) { - - // if a new bond add to the list of bonds - if m.loadDelegatorBond(delegator, bond.Address) == nil { - pks := m.loadDelegatorCandidates(delegator) - pks = append(pks, (*bond).Address) - b, err := m.cdc.MarshalJSON(pks) - if err != nil { - panic(err) - } - m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) - } - - // now actually save the bond - b, err := m.cdc.MarshalJSON(*bond) - if err != nil { - panic(err) - } - m.store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b) - //updateDelegatorBonds(store, delegator) //XXX remove? -} - -func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidateAddr sdk.Address) { - // TODO use list queries on multistore to remove iterations here! - // first remove from the list of bonds - addrs := m.loadDelegatorCandidates(delegator) - for i, addr := range addrs { - if bytes.Equal(candidateAddr, addr) { - addrs = append(addrs[:i], addrs[i+1:]...) - } - } - b, err := m.cdc.MarshalJSON(addrs) - if err != nil { - panic(err) - } - m.store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) - - // now remove the actual bond - m.store.Delete(GetDelegatorBondKey(delegator, candidateAddr, m.cdc)) - //updateDelegatorBonds(store, delegator) //XXX remove? -} - -//_______________________________________________________________________ - -// load/save the global staking params -func (m Mapper) loadParams() (params Params) { - b := m.store.Get(ParamKey) - if b == nil { - return defaultParams() - } - - err := m.cdc.UnmarshalJSON(b, ¶ms) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return -} -func (m Mapper) saveParams(params Params) { - b, err := m.cdc.MarshalJSON(params) - if err != nil { - panic(err) - } - m.store.Set(ParamKey, b) -} - -//_______________________________________________________________________ - -// load/save the global staking state -func (m Mapper) loadGlobalState() (gs *GlobalState) { - b := m.store.Get(GlobalStateKey) - if b == nil { - return initialGlobalState() - } - gs = new(GlobalState) - err := m.cdc.UnmarshalJSON(b, gs) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return -} - -func (m Mapper) saveGlobalState(gs *GlobalState) { - b, err := m.cdc.MarshalJSON(*gs) - if err != nil { - panic(err) - } - m.store.Set(GlobalStateKey, b) -} diff --git a/x/stake/msg.go b/x/stake/msg.go new file mode 100644 index 0000000000..ad9e7e2eb8 --- /dev/null +++ b/x/stake/msg.go @@ -0,0 +1,213 @@ +package stake + +import ( + "encoding/json" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + crypto "github.com/tendermint/go-crypto" +) + +// name to idetify transaction types +var MsgType = "stake" + +//Verify interface at compile time +var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} + +//______________________________________________________________________ + +// MsgDeclareCandidacy - struct for unbonding transactions +type MsgDeclareCandidacy struct { + Description + CandidateAddr sdk.Address `json:"address"` + Bond sdk.Coin `json:"bond"` + PubKey crypto.PubKey `json:"pubkey"` +} + +func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey, + bond sdk.Coin, description Description) MsgDeclareCandidacy { + return MsgDeclareCandidacy{ + Description: description, + CandidateAddr: candidateAddr, + Bond: bond, + PubKey: pubkey, + } +} + +//nolint +func (msg MsgDeclareCandidacy) Type() string { return MsgType } //TODO update "stake/declarecandidacy" +func (msg MsgDeclareCandidacy) Get(key interface{}) (value interface{}) { return nil } +func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } +func (msg MsgDeclareCandidacy) String() string { + return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix +} + +// get the bytes for the message signer to sign on +func (msg MsgDeclareCandidacy) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { + if msg.Address == nil { + return ErrCandidateEmpty() + } + if msg.Bond.Denom <= 0 { + return sdk.ErrInvalidCoins(coins) + } + empty := Description{} + if msg.Description == empty { + return newError(CodeInvalidInput, "description must be included") + } + return nil +} + +//______________________________________________________________________ + +// MsgEditCandidacy - struct for editing a candidate +type MsgEditCandidacy struct { + Description + CandidateAddr sdk.Address `json:"address"` +} + +func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy { + return MsgEditCandidacy{ + Description: description, + Address: address, + } +} + +//nolint +func (msg MsgEditCandidacy) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" +func (msg MsgEditCandidacy) Get(key interface{}) (value interface{}) { return nil } +func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } +func (msg MsgEditCandidacy) String() string { + return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix +} + +// get the bytes for the message signer to sign on +func (msg MsgEditCandidacy) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { + if msg.Address == nil { + return ErrCandidateEmpty() + } + if err != nil { + return err + } + empty := Description{} + if msg.Description == empty { + return newError(CodeInvalidInput, "Transaction must include some information to modify") + } + return nil +} + +//______________________________________________________________________ + +// MsgDelegate - struct for bonding transactions +type MsgDelegate struct { + DelegatorAddr sdk.Address `json:"address"` + CandidateAddr sdk.Address `json:"address"` + Bond sdk.Coin `json:"bond"` +} + +func NewMsgDelegate(delegatorAddr, candidateAddr sdk.Address, bond sdk.Coin) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidateAddr, + Bond: bond, + } +} + +//nolint +func (msg MsgDelegate) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" +func (msg MsgDelegate) Get(key interface{}) (value interface{}) { return nil } +func (msg MsgDelegate) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } +func (msg MsgDelegate) String() string { + return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix +} + +// get the bytes for the message signer to sign on +func (msg MsgDelegate) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgDelegate) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrBadDelegatorAddr() + } + if msg.CandidateAddr == nil { + return ErrBadCandidateAddr() + } + if msg.Bond.Denom <= 0 { + return sdk.ErrInvalidCoins(coins) + } + return nil +} + +//______________________________________________________________________ + +// MsgUnbond - struct for unbonding transactions +type MsgUnbond struct { + DelegatorAddr sdk.Address `json:"address"` + CandidateAddr sdk.Address `json:"address"` + Shares string `json:"shares"` +} + +func NewMsgUnbond(delegatorAddr, candidateAddr sdk.Address, shares string) MsgUnbond { + return MsgUnbond{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidateAddr, + Shares: shares, + } +} + +//nolint +func (msg MsgUnbond) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy" +func (msg MsgUnbond) Get(key interface{}) (value interface{}) { return nil } +func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} } +func (msg MsgUnbond) String() string { + return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix +} + +// get the bytes for the message signer to sign on +func (msg MsgUnbond) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +// quick validity check +func (msg MsgUnbond) ValidateBasic() sdk.Error { + if msg.DelegatorAddr == nil { + return ErrBadDelegatorAddr() + } + if msg.CandidateAddr == nil { + return ErrBadCandidateAddr() + } + if msg.Shares != "MAX" { + shares, err = sdk.NewRatFromDecimal(msg.Shares) + if err != nil { + return ErrBadShares() + } + } + return nil +} diff --git a/x/stake/tx_test.go b/x/stake/msg_test.go similarity index 100% rename from x/stake/tx_test.go rename to x/stake/msg_test.go diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 11d0752a9b..601ee50fdb 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -96,7 +96,7 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins ) ck := bank.NewCoinKeeper(accountMapper) params := paramsNoInflation() - mapper.saveParams(params) + mapper.setParams(params) // fill all the addresses with some coins for _, addr := range addrs { @@ -167,7 +167,7 @@ func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { Liabilities: sdk.NewRat(amts[i]), VotingPower: sdk.NewRat(amts[i]), } - mapper.saveCandidate(c) + mapper.setCandidate(c) } } diff --git a/x/stake/tick.go b/x/stake/tick.go index 4b206f4dcc..27ba44f6a0 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -9,8 +9,8 @@ import ( func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) { // retrieve params - params := m.loadParams() - gs := m.loadGlobalState() + params := m.getParams() + gs := m.getGlobalState() height := ctx.BlockHeight() // Process Validator Provisions @@ -46,7 +46,7 @@ func processProvisions(m Mapper, gs *GlobalState, params Params) { // XXX XXX XXX XXX XXX XXX XXX XXX XXX // save the params - m.saveGlobalState(gs) + m.setGlobalState(gs) } // get the next inflation rate for the hour diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index d04581a7c5..fcd67af9cd 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -9,8 +9,8 @@ import ( func TestGetInflation(t *testing.T) { _, _, mapper, _ := createTestInput(t, nil, false, 0) - params := mapper.loadParams() - gs := mapper.loadGlobalState() + params := mapper.getParams() + gs := mapper.getGlobalState() // Governing Mechanism: // bondedRatio = BondedPool / TotalSupply @@ -54,8 +54,8 @@ func TestGetInflation(t *testing.T) { func TestProcessProvisions(t *testing.T) { _, _, mapper, _ := createTestInput(t, nil, false, 0) - params := mapper.loadParams() - gs := mapper.loadGlobalState() + params := mapper.getParams() + gs := mapper.getGlobalState() // create some candidates some bonded, some unbonded candidates := candidatesFromAddrsEmpty(addrs) @@ -66,7 +66,7 @@ func TestProcessProvisions(t *testing.T) { mintedTokens := int64((i + 1) * 10000000) gs.TotalSupply += mintedTokens candidate.addTokens(mintedTokens, gs) - mapper.saveCandidate(candidate) + mapper.setCandidate(candidate) } var totalSupply int64 = 550000000 var bondedShares int64 = 150000000 diff --git a/x/stake/tx.go b/x/stake/tx.go deleted file mode 100644 index 7104b92a8b..0000000000 --- a/x/stake/tx.go +++ /dev/null @@ -1,214 +0,0 @@ -package stake - -import ( - "encoding/json" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - crypto "github.com/tendermint/go-crypto" -) - -// name to idetify transaction types -var Name = "stake" - -//Verify interface at compile time -var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} - -//______________________________________________________________________ - -// MsgAddr - struct for bonding or unbonding transactions -type MsgAddr struct { - Address sdk.Address `json:"address"` -} - -func NewMsgAddr(address sdk.Address) MsgAddr { - return MsgAddr{ - Address: address, - } -} - -// nolint -func (msg MsgAddr) Type() string { return Name } -func (msg MsgAddr) Get(key interface{}) (value interface{}) { return nil } -func (msg MsgAddr) GetSigners() []sdk.Address { return []sdk.Address{msg.Address} } -func (msg MsgAddr) String() string { - return fmt.Sprintf("MsgAddr{Address: %v}", msg.Address) -} - -// ValidateBasic - Check for non-empty candidate, and valid coins -func (msg MsgAddr) ValidateBasic() sdk.Error { - if msg.Address == nil { - return ErrCandidateEmpty() - } - return nil -} - -//______________________________________________________________________ - -// MsgDeclareCandidacy - struct for unbonding transactions -type MsgDeclareCandidacy struct { - MsgAddr - Description - Bond sdk.Coin `json:"bond"` - PubKey crypto.PubKey `json:"pubkey"` -} - -func NewMsgDeclareCandidacy(address sdk.Address, pubkey crypto.PubKey, bond sdk.Coin, description Description) MsgDeclareCandidacy { - return MsgDeclareCandidacy{ - MsgAddr: NewMsgAddr(address), - Description: description, - Bond: bond, - PubKey: pubkey, - } -} - -// get the bytes for the message signer to sign on -func (msg MsgDeclareCandidacy) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { - err := msg.MsgAddr.ValidateBasic() - if err != nil { - return err - } - err = validateCoin(msg.Bond) - if err != nil { - return err - } - empty := Description{} - if msg.Description == empty { - return newError(CodeInvalidInput, "description must be included") - } - return nil -} - -//______________________________________________________________________ - -// MsgEditCandidacy - struct for editing a candidate -type MsgEditCandidacy struct { - MsgAddr - Description -} - -func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy { - return MsgEditCandidacy{ - MsgAddr: NewMsgAddr(address), - Description: description, - } -} - -// get the bytes for the message signer to sign on -func (msg MsgEditCandidacy) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { - err := msg.MsgAddr.ValidateBasic() - if err != nil { - return err - } - empty := Description{} - if msg.Description == empty { - return newError(CodeInvalidInput, "Transaction must include some information to modify") - } - return nil -} - -//______________________________________________________________________ - -// MsgDelegate - struct for bonding transactions -type MsgDelegate struct { - MsgAddr - Bond sdk.Coin `json:"bond"` -} - -func NewMsgDelegate(address sdk.Address, bond sdk.Coin) MsgDelegate { - return MsgDelegate{ - MsgAddr: NewMsgAddr(address), - Bond: bond, - } -} - -// get the bytes for the message signer to sign on -func (msg MsgDelegate) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgDelegate) ValidateBasic() sdk.Error { - err := msg.MsgAddr.ValidateBasic() - if err != nil { - return err - } - err = validateCoin(msg.Bond) - if err != nil { - return err - } - return nil -} - -//______________________________________________________________________ - -// MsgUnbond - struct for unbonding transactions -type MsgUnbond struct { - MsgAddr - Shares string `json:"shares"` -} - -func NewMsgUnbond(address sdk.Address, shares string) MsgUnbond { - return MsgUnbond{ - MsgAddr: NewMsgAddr(address), - Shares: shares, - } -} - -// get the bytes for the message signer to sign on -func (msg MsgUnbond) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - -// quick validity check -func (msg MsgUnbond) ValidateBasic() sdk.Error { - err := msg.MsgAddr.ValidateBasic() - if err != nil { - return err - } - - if msg.Shares == "MAX" { - return ErrCandidateEmpty() - } - return nil -} - -//______________________________________________________________________ -// helper - -func validateCoin(coin sdk.Coin) sdk.Error { - coins := sdk.Coins{coin} - if !coins.IsValid() { - return sdk.ErrInvalidCoins(coins) - } - if !coins.IsPositive() { - return sdk.ErrInvalidCoins(coins) // XXX: add "Amount must be > 0" ? - } - return nil -} diff --git a/x/stake/types.go b/x/stake/types.go index 2ffc58ede1..d1cf6df5fa 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -12,14 +12,8 @@ type Params struct { InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms - MaxVals uint16 `json:"max_vals"` // maximum number of validators - BondDenom string `json:"bond_denom"` // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 `json:"gas_declare_candidacy"` - GasEditCandidacy int64 `json:"gas_edit_candidacy"` - GasDelegate int64 `json:"gas_delegate"` - GasUnbond int64 `json:"gas_unbond"` + MaxValidators uint16 `json:"max_validators"` // maximum number of validators + BondDenom string `json:"bond_denom"` // bondable coin denomination } func defaultParams() Params { @@ -28,12 +22,8 @@ func defaultParams() Params { InflationMax: sdk.NewRat(20, 100), InflationMin: sdk.NewRat(7, 100), GoalBonded: sdk.NewRat(67, 100), - MaxVals: 100, + MaxValidators: 100, BondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, } } @@ -142,12 +132,10 @@ const ( // bond shares is based on the amount of coins delegated divided by the current // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. - -// XXX update to use Address as the main key NOT the pubkey type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate Assets sdk.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator @@ -163,11 +151,11 @@ type Description struct { } // NewCandidate - initialize a new candidate -func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate { +func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) *Candidate { return &Candidate{ Status: Unbonded, - PubKey: pubKey, Address: address, + PubKey: pubKey, Assets: sdk.ZeroRat, Liabilities: sdk.ZeroRat, VotingPower: sdk.ZeroRat, @@ -226,6 +214,11 @@ func (c *Candidate) validator() Validator { } } +//XXX updateDescription function +//XXX enforce limit to number of description characters + +//______________________________________________________________________ + // Validator is one of the top Candidates type Validator struct { Address sdk.Address `json:"address"` // Address of validator @@ -256,7 +249,9 @@ type Candidates []*Candidate // DelegatorBond represents the bond with tokens held by an account. It is // owned by one delegator, and is associated with the voting power of one // pubKey. +// TODO better way of managing space type DelegatorBond struct { - Address sdk.Address `json:"pub_key"` - Shares sdk.Rat `json:"shares"` + Address sdk.Address `json:"address"` + CandidateAddr sdk.Address `json:"candidate_addr"` + Shares sdk.Rat `json:"shares"` } From 13835f084fd5015c4f708b8302eb68f44d794203 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Mar 2018 22:21:18 +0100 Subject: [PATCH 35/54] sunny refactor compiling --- docs/spec/staking/old/spec.md | 2 +- x/stake/commands/query.go | 8 +- x/stake/commands/tx.go | 33 +-- x/stake/errors.go | 5 +- x/stake/handler.go | 161 ++++++-------- x/stake/handler_test.go | 407 +++++++++++++++++----------------- x/stake/keeper.go | 272 ++++++++++++++++------- x/stake/keeper_test.go | 69 +++--- x/stake/msg.go | 31 ++- x/stake/msg_test.go | 65 +++--- x/stake/test_common.go | 29 +-- x/stake/tick.go | 16 +- x/stake/tick_test.go | 18 +- x/stake/types.go | 91 +------- 14 files changed, 606 insertions(+), 601 deletions(-) diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index 7010ee153d..dac69aa066 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -49,7 +49,7 @@ type Params struct { GoalBonded rational.Rational // Goal of percent bonded atoms ReserveTax rational.Rational // Tax collected on all fees - MaxVals uint16 // maximum number of validators + MaxValidators uint16 // maximum number of validators BondDenom string // bondable coin denomination // gas costs for txs diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index 2a72d569ac..b6ccb712e5 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -45,7 +45,7 @@ func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command { Short: "Query for the set of validator-candidates pubkeys", RunE: func(cmd *cobra.Command, args []string) error { - key := PrefixedKey(stake.Name, stake.CandidatesAddrKey) + key := PrefixedKey(stake.MsgType, stake.CandidatesAddrKey) res, err := builder.Query(key, storeName) if err != nil { @@ -85,7 +85,7 @@ func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command { return err } - key := PrefixedKey(stake.Name, stake.GetCandidateKey(addr)) + key := PrefixedKey(stake.MsgType, stake.GetCandidateKey(addr)) res, err := builder.Query(key, storeName) if err != nil { @@ -131,7 +131,7 @@ func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command } delegator := crypto.Address(bz) - key := PrefixedKey(stake.Name, stake.GetDelegatorBondKey(delegator, addr, cdc)) + key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondKey(delegator, addr, cdc)) res, err := builder.Query(key, storeName) if err != nil { @@ -173,7 +173,7 @@ func GetCmdQueryDelegatorBonds(cdc *wire.Codec, storeName string) *cobra.Command } delegator := crypto.Address(bz) - key := PrefixedKey(stake.Name, stake.GetDelegatorBondsKey(delegator, cdc)) + key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondsKey(delegator, cdc)) res, err := builder.Query(key, storeName) if err != nil { diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 56b41a15f9..4314832888 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -19,10 +19,11 @@ import ( // nolint const ( - FlagAddress = "address" - FlagPubKey = "pubkey" - FlagAmount = "amount" - FlagShares = "shares" + FlagAddressDelegator = "addressD" + FlagAddressCandidate = "addressC" + FlagPubKey = "pubkey" + FlagAmount = "amount" + FlagShares = "shares" FlagMoniker = "moniker" FlagIdentity = "keybase-sig" @@ -36,6 +37,7 @@ var ( fsAmount = flag.NewFlagSet("", flag.ContinueOnError) fsShares = flag.NewFlagSet("", flag.ContinueOnError) fsCandidate = flag.NewFlagSet("", flag.ContinueOnError) + fsDelegator = flag.NewFlagSet("", flag.ContinueOnError) ) func init() { @@ -45,7 +47,8 @@ func init() { fsCandidate.String(FlagMoniker, "", "validator-candidate name") fsCandidate.String(FlagIdentity, "", "optional keybase signature") fsCandidate.String(FlagWebsite, "", "optional website") - fsCandidate.String(FlagDetails, "", "optional detailed description space") + fsCandidate.String(FlagAddressCandidate, "", "hex address of the validator/candidate") + fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator") } //TODO refactor to common functionality @@ -69,7 +72,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { if err != nil { return err } - addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) + candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) if err != nil { return err } @@ -86,7 +89,7 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgDeclareCandidacy(addr, pk, amount, description) + msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) name, pass, err := getNamePassword() if err != nil { @@ -117,7 +120,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { Short: "edit and existing validator-candidate account", RunE: func(cmd *cobra.Command, args []string) error { - addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) + candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) if err != nil { return err } @@ -127,7 +130,7 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { Website: viper.GetString(FlagWebsite), Details: viper.GetString(FlagDetails), } - msg := stake.NewMsgEditCandidacy(addr, description) + msg := stake.NewMsgEditCandidacy(candidateAddr, description) name, pass, err := getNamePassword() if err != nil { @@ -161,12 +164,13 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { return err } - addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) + delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) + candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) if err != nil { return err } - msg := stake.NewMsgDelegate(addr, amount) + msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount) name, pass, err := getNamePassword() if err != nil { @@ -186,6 +190,7 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsAmount) + cmd.Flags().AddFlagSet(fsDelegator) return cmd } @@ -210,12 +215,13 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { } } - addr, err := sdk.GetAddress(viper.GetString(FlagAddress)) + delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator)) + candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate)) if err != nil { return err } - msg := stake.NewMsgUnbond(addr, sharesStr) + msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr) name, pass, err := getNamePassword() if err != nil { @@ -235,6 +241,7 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { cmd.Flags().AddFlagSet(fsPk) cmd.Flags().AddFlagSet(fsShares) + cmd.Flags().AddFlagSet(fsDelegator) return cmd } diff --git a/x/stake/errors.go b/x/stake/errors.go index aae855f6e1..a8038a3d81 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -91,7 +91,10 @@ func ErrNoDelegatorForAddress() sdk.Error { return newError(CodeInvalidValidator, "Delegator does not contain validator bond") } func ErrInsufficientFunds() sdk.Error { - return newError(CodeInvalidValidator, "Insufficient bond shares") + return newError(CodeInvalidInput, "Insufficient bond shares") +} +func ErrBadShares() sdk.Error { + return newError(CodeInvalidInput, "bad shares provided as input, must be MAX or decimal") } func ErrBadRemoveValidator() sdk.Error { return newError(CodeInvalidValidator, "Error removing validator") diff --git a/x/stake/handler.go b/x/stake/handler.go index e96e8484c4..8dda9e7f38 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -80,27 +80,6 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { //sender := signers[0] //} -//_____________________________________________________________________ -// helper functions - -// move a candidates asset pool from bonded to unbonded pool -func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate *Candidate) { - - // replace bonded shares with unbonded shares - tokens := k.getGlobalState(ctx).removeSharesBonded(candidate.Assets) - candidate.Assets = k.getGlobalState(ctx).addTokensUnbonded(tokens) - candidate.Status = Unbonded -} - -// move a candidates asset pool from unbonded to bonded pool -func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate *Candidate) { - - // replace unbonded shares with bonded shares - tokens := k.getGlobalState(ctx).removeSharesUnbonded(candidate.Assets) - candidate.Assets = k.getGlobalState(ctx).addTokensBonded(tokens) - candidate.Status = Bonded -} - //_____________________________________________________________________ // These functions assume everything has been authenticated, @@ -109,11 +88,12 @@ func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate *Candidate) { func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) sdk.Result { // check to see if the pubkey or sender has been registered before - if k.getCandidate(msg.Address) != nil { - return ErrCandidateExistsAddr() + _, found := k.getCandidate(ctx, msg.CandidateAddr) + if found { + return ErrCandidateExistsAddr().Result() } - if msg.bond.Denom != k.getParams().BondDenom { - return ErrBadBondingDenom() + if msg.Bond.Denom != k.getParams(ctx).BondDenom { + return ErrBadBondingDenom().Result() } if ctx.IsCheckTx() { return sdk.Result{ @@ -121,19 +101,19 @@ func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandida } } - candidate := NewCandidate(msg.PubKey, msg.Address, msg.Description) - k.setCandidate(candidate) + candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description) + k.setCandidate(ctx, candidate) // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - txDelegate := NewMsgDelegate(msg.Address, msg.Bond) - return delegateWithCandidate(txDelegate, candidate) + return k.delegateWithCandidate(ctx, msg.CandidateAddr, msg.Bond, candidate).Result() } func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sdk.Result { // candidate must already be registered - if k.getCandidate(msg.Address) == nil { + candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + if !found { return ErrBadCandidateAddr().Result() } if ctx.IsCheckTx() { @@ -141,16 +121,11 @@ func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sd GasUsed: GasEditCandidacy, } } - - // Get the pubKey bond account - candidate := k.getCandidate(msg.Address) - if candidate == nil { - return ErrBondNotNominated().Result() - } if candidate.Status == Unbonded { //candidate has been withdrawn return ErrBondNotNominated().Result() } + // XXX move to types //check and edit any of the editable terms if msg.Description.Moniker != "" { candidate.Description.Moniker = msg.Description.Moniker @@ -165,16 +140,17 @@ func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sd candidate.Description.Details = msg.Description.Details } - k.setCandidate(candidate) - return nil + k.setCandidate(ctx, candidate) + return sdk.Result{} } func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { - if k.getCandidate(msg.Address) == nil { + candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + if !found { return ErrBadCandidateAddr().Result() } - if msg.bond.Denom != k.getParams().BondDenom { + if msg.Bond.Denom != k.getParams(ctx).BondDenom { return ErrBadBondingDenom().Result() } if ctx.IsCheckTx() { @@ -182,17 +158,10 @@ func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { GasUsed: GasDelegate, } } - - // Get the pubKey bond account - candidate := k.getCandidate(msg.Address) - if candidate == nil { - return ErrBondNotNominated().Result() - } - - return tr.delegateWithCandidate(msg, candidate).Result() + return k.delegateWithCandidate(ctx, msg.DelegatorAddr, msg.Bond, candidate).Result() } -func (k Keeper) delegateWithCandidate(ctx sdk.Context, candidateAddr, delegatorAddr sdk.Address, +func (k Keeper) delegateWithCandidate(ctx sdk.Context, delegatorAddr sdk.Address, bondAmt sdk.Coin, candidate Candidate) sdk.Error { if candidate.Status == Revoked { //candidate has been withdrawn @@ -200,63 +169,43 @@ func (k Keeper) delegateWithCandidate(ctx sdk.Context, candidateAddr, delegatorA } // Get or create the delegator bond - bond := k.getDelegatorBond(tr.sender, canad) - if bond == nil { - bond = &DelegatorBond{ - CandidateAddr: delegatorAddr, - DelegatorAddr: candidateAddr, + existingBond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address) + if !found { + existingBond = DelegatorBond{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidate.Address, Shares: sdk.ZeroRat, } } // Account new shares, save - err := BondCoins(bond, candidate, msg.Bond) + err := k.BondCoins(ctx, existingBond, candidate, bondAmt) if err != nil { - return err.Result() + return err } - k.setDelegatorBond(tr.sender, bond) - k.setCandidate(candidate) - k.setGlobalState(tr.gs) + k.setDelegatorBond(ctx, existingBond) + k.setCandidate(ctx, candidate) return nil } // Perform all the actions required to bond tokens to a delegator bond from their account -func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, amount sdk.Coin) sdk.Error { +func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error { _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount}) if err != nil { return err } - newShares := candidate.addTokens(tokens.Amount, tr.gs) + newShares := k.candidateAddTokens(ctx, candidate, amount.Amount) bond.Shares = bond.Shares.Add(newShares) - k.SetDelegatorBond() - return nil -} - -// Perform all the actions required to bond tokens to a delegator bond from their account -func (k Keeper) UnbondCoins(ctx sdk.Context, bond *DelegatorBond, candidate *Candidate, shares sdk.Rat) sdk.Error { - - // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { - return sdk.ErrInsufficientFunds("") //XXX variables inside - } - bond.Shares = bond.Shares.Sub(shares) - - returnAmount := candidate.removeShares(shares, tr.gs) - returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - - _, err := tr.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) - if err != nil { - return err - } + k.setDelegatorBond(ctx, bond) return nil } func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { // check if bond has any shares in it unbond - bond := k.getDelegatorBond(sender, msg.Address) - if bond == nil { + bond, found := k.getDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr) + if !found { return ErrNoDelegatorForAddress().Result() } if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < msg shares @@ -301,8 +250,8 @@ func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate := k.getCandidate(msg.Address) - if candidate == nil { + candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + if !found { return ErrNoCandidateForAddress().Result() } @@ -311,41 +260,59 @@ func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { // if the bond is the owner of the candidate then // trigger a revoke candidacy - if bytes.Equal(tr.sender, candidate.Address) && + if bytes.Equal(bond.DelegatorAddr, candidate.Address) && candidate.Status != Revoked { revokeCandidacy = true } // remove the bond - k.removeDelegatorBond(ctx, msg.Address) + k.removeDelegatorBond(ctx, bond) } else { - k.setDelegatorBond(tr.sender, bond) + k.setDelegatorBond(ctx, bond) } // Add the coins - returnAmount := candidate.removeShares(shares, tr.gs) - returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}} - tr.coinKeeper.AddCoins(ctx, tr.sender, returnCoins) + returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + returnCoins := sdk.Coins{{k.getParams(ctx).BondDenom, returnAmount}} + k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) // lastly if an revoke candidate if necessary if revokeCandidacy { // change the share types to unbonded if they were not already if candidate.Status == Bonded { - tr.bondedToUnbondedPool(candidate) + k.bondedToUnbondedPool(ctx, candidate) } // lastly update the status candidate.Status = Revoked } - // deduct shares from the candidate and save + // deduct shares from the candidate if candidate.Liabilities.IsZero() { - k.removeCandidate(msg.Address) + k.removeCandidate(ctx, candidate.Address) } else { - k.setCandidate(candidate) + k.setCandidate(ctx, candidate) } - - k.setGlobalState(tr.gs) return sdk.Result{} } + +// XXX where this used +// Perform all the actions required to bond tokens to a delegator bond from their account +func (k Keeper) UnbondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error { + + // subtract bond tokens from delegator bond + if bond.Shares.LT(shares) { + return sdk.ErrInsufficientFunds("") //XXX variables inside + } + bond.Shares = bond.Shares.Sub(shares) + + returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + returnCoins := sdk.Coins{{k.getParams(ctx).BondDenom, returnAmount}} + + _, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) + if err != nil { + return err + } + return nil +} diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 214ee43503..665458c1aa 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,247 +1,248 @@ 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{ - MsgAddr: NewMsgAddr(address), - Description: Description{}, - 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, address sdk.Address) MsgDelegate { - return MsgDelegate{ - MsgAddr: NewMsgAddr(address), - 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) { - _, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) - _, _, _, checker := createTestInput(t, addrs[0], true, 1000) +//func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { +//ctxDeliver, _, keeper := createTestInput(t, addrs[0], false, 1000) +//ctxCheck, _, keeper := createTestInput(t, addrs[0], true, 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 := deliverer.declareCandidacy(msgDeclareCandidacy) +//assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - // 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 can bond to two different addresses +//msgDeclareCandidacy.Address = addrs[1] +//err := checker.declareCandidacy(msgDeclareCandidacy) +//assert.Nil(t, err, "didn't expected error on checkTx") - // 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") -} +//// 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) { - _, _, mapper, deliverer := 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) { - _, _, mapper, deliverer := 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) - 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) +//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 2428aaeca7..a54d006bc2 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -84,39 +84,39 @@ func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinK } //XXX load/save -> get/set -func (m Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate) { - store := ctx.KVStore(storeKey) +func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { + store := ctx.KVStore(k.storeKey) b := store.Get(GetCandidateKey(addr)) if b == nil { - return nil + return candidate, false } - err := m.cdc.UnmarshalJSON(b, &candidate) + err := k.cdc.UnmarshalJSON(b, &candidate) if err != nil { - panic(err) // This error should never occur big problem if does + panic(err) } - return + return candidate, true } -func (m Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { - store := ctx.KVStore(storeKey) +func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { + store := ctx.KVStore(k.storeKey) // XXX should only remove validator if we know candidate is a validator - m.removeValidator(candidate.Address) - validator := &Validator{candidate.Address, candidate.VotingPower} - m.updateValidator(validator) + k.removeValidator(ctx, candidate.Address) + validator := Validator{candidate.Address, candidate.VotingPower} + k.updateValidator(ctx, validator) - b, err := m.cdc.MarshalJSON(candidate) + b, err := k.cdc.MarshalJSON(candidate) if err != nil { panic(err) } store.Set(GetCandidateKey(candidate.Address), b) } -func (m Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { - store := ctx.KVStore(storeKey) +func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { + store := ctx.KVStore(k.storeKey) // XXX should only remove validator if we know candidate is a validator - m.removeValidator(candidateAddr) + k.removeValidator(ctx, candidateAddr) store.Delete(GetCandidateKey(candidateAddr)) } @@ -137,10 +137,10 @@ func (m Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { // updateValidator - update a validator and create accumulate any changes // in the changed validator substore -func (m Keeper) updateValidator(ctx sdk.Context, validator Validator) { - store := ctx.KVStore(storeKey) +func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) { + store := ctx.KVStore(k.storeKey) - b, err := m.cdc.MarshalJSON(validator) + b, err := k.cdc.MarshalJSON(validator) if err != nil { panic(err) } @@ -149,31 +149,31 @@ func (m Keeper) updateValidator(ctx sdk.Context, validator Validator) { store.Set(GetValidatorUpdatesKey(validator.Address), b) // update the list ordered by voting power - store.Set(GetValidatorKey(validator.Address, validator.VotingPower, m.cdc), b) + store.Set(GetValidatorKey(validator.Address, validator.VotingPower, k.cdc), b) } -func (m Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { - store := ctx.KVStore(storeKey) +func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { + store := ctx.KVStore(k.storeKey) //add validator with zero power to the validator updates - b, err := m.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) + b, err := k.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) if err != nil { panic(err) } store.Set(GetValidatorUpdatesKey(address), b) // now actually delete from the validator set - candidate := m.getCandidate(address) - if candidate != nil { - store.Delete(GetValidatorKey(address, candidate.VotingPower, m.cdc)) + candidate, found := k.getCandidate(ctx, address) + if found { + store.Delete(GetValidatorKey(address, candidate.VotingPower, k.cdc)) } } // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower -func (m Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Validator) { - store := ctx.KVStore(storeKey) +func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Validator) { + store := ctx.KVStore(k.storeKey) iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest @@ -185,7 +185,7 @@ func (m Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali } valBytes := iterator.Value() var val Validator - err := m.cdc.UnmarshalJSON(valBytes, &val) + err := k.cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -199,15 +199,15 @@ func (m Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali //_________________________________________________________________________ // get the most updated validators -func (m Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { - store := ctx.KVStore(storeKey) +func (k Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { + store := ctx.KVStore(k.storeKey) iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val Validator - err := m.cdc.UnmarshalJSON(valBytes, &val) + err := k.cdc.UnmarshalJSON(valBytes, &val) if err != nil { panic(err) } @@ -219,8 +219,8 @@ func (m Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { } // remove all validator update entries -func (m Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { - store := ctx.KVStore(storeKey) +func (k Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { + store := ctx.KVStore(k.storeKey) iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) for ; iterator.Valid(); iterator.Next() { store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop @@ -230,22 +230,19 @@ func (m Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { //--------------------------------------------------------------------- -// getCandidates - get the active list of all candidates TODO replace with multistore -func (m Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { - store := ctx.KVStore(storeKey) - +// getCandidates - get the active list of all candidates +func (k Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { + store := ctx.KVStore(k.storeKey) iterator := store.Iterator(subspace(CandidateKeyPrefix)) - //iterator := store.Iterator(CandidateKeyPrefix, []byte(nil)) - //iterator := store.Iterator([]byte{}, []byte(nil)) for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() var candidate Candidate - err := m.cdc.UnmarshalJSON(candidateBytes, &candidate) + err := k.cdc.UnmarshalJSON(candidateBytes, &candidate) if err != nil { panic(err) } - candidates = append(candidates, &candidate) + candidates = append(candidates, candidate) } iterator.Close() return candidates @@ -253,17 +250,17 @@ func (m Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { //_____________________________________________________________________ -// XXX use a store iterator to get +// XXX use a store iterator here instead //// load the pubkeys of all candidates a delegator is delegated too -//func (m Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) { -//store := ctx.KVStore(storeKey) +//func (k Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) { +//store := ctx.KVStore(k.storeKey) -//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, m.cdc)) +//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, k.cdc)) //if candidateBytes == nil { //return nil //} -//err := m.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) +//err := k.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) //if err != nil { //panic(err) //} @@ -272,126 +269,229 @@ func (m Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { //_____________________________________________________________________ -func (m Keeper) getDelegatorBond(ctx sdk.Context, - delegator, candidate sdk.Address) (bond DelegatorBond) { +func (k Keeper) getDelegatorBond(ctx sdk.Context, + delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) { - store := ctx.KVStore(storeKey) - delegatorBytes := store.Get(GetDelegatorBondKey(delegator, candidate, m.cdc)) + store := ctx.KVStore(k.storeKey) + delegatorBytes := store.Get(GetDelegatorBondKey(delegatorAddr, candidateAddr, k.cdc)) if delegatorBytes == nil { - return nil + return bond, false } - err := m.cdc.UnmarshalJSON(delegatorBytes, &bond) + err := k.cdc.UnmarshalJSON(delegatorBytes, &bond) if err != nil { panic(err) } - return bond + return bond, true } -func (m Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { - store := ctx.KVStore(storeKey) +func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(k.storeKey) // XXX use store iterator // if a new bond add to the list of bonds - //if m.getDelegatorBond(delegator, bond.Address) == nil { - //pks := m.getDelegatorCandidates(delegator) + //if k.getDelegatorBond(delegator, bond.Address) == nil { + //pks := k.getDelegatorCandidates(delegator) //pks = append(pks, bond.Address) - //b, err := m.cdc.MarshalJSON(pks) + //b, err := k.cdc.MarshalJSON(pks) //if err != nil { //panic(err) //} - //store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) + //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) //} // now actually save the bond - b, err := m.cdc.MarshalJSON(bond) + b, err := k.cdc.MarshalJSON(bond) if err != nil { panic(err) } - store.Set(GetDelegatorBondKey(delegator, bond.Address, m.cdc), b) + store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) } -func (m Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { - store := ctx.KVStore(storeKey) +func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(k.storeKey) // XXX use store iterator // TODO use list queries on multistore to remove iterations here! // first remove from the list of bonds - //addrs := m.getDelegatorCandidates(delegator) + //addrs := k.getDelegatorCandidates(delegator) //for i, addr := range addrs { //if bytes.Equal(candidateAddr, addr) { //addrs = append(addrs[:i], addrs[i+1:]...) //} //} - //b, err := m.cdc.MarshalJSON(addrs) + //b, err := k.cdc.MarshalJSON(addrs) //if err != nil { //panic(err) //} - //store.Set(GetDelegatorBondsKey(delegator, m.cdc), b) + //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) // now remove the actual bond - store.Delete(GetDelegatorBondKey(bond.delegatorAddr, bond.candidateAddr, m.cdc)) + store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) //updateDelegatorBonds(store, delegator) //XXX remove? } //_______________________________________________________________________ // load/save the global staking params -func (m Keeper) getParams(ctx sdk.Context) (params Params) { +func (k Keeper) getParams(ctx sdk.Context) (params Params) { // check if cached before anything - if m.params != (Params{}) { - return m.params + if k.params != (Params{}) { + return k.params } - store := ctx.KVStore(storeKey) + store := ctx.KVStore(k.storeKey) b := store.Get(ParamKey) if b == nil { return defaultParams() } - err := m.cdc.UnmarshalJSON(b, ¶ms) + err := k.cdc.UnmarshalJSON(b, ¶ms) if err != nil { panic(err) // This error should never occur big problem if does } return } -func (m Keeper) setParams(ctx sdk.Context, params Params) { - store := ctx.KVStore(storeKey) - b, err := m.cdc.MarshalJSON(params) +func (k Keeper) setParams(ctx sdk.Context, params Params) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalJSON(params) if err != nil { panic(err) } store.Set(ParamKey, b) - m.params = Params{} // clear the cache + k.params = Params{} // clear the cache } //_______________________________________________________________________ // XXX nothing is this Keeper should return a pointer...!!!!!! // load/save the global staking state -func (m Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { +func (k Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { // check if cached before anything - if m.gs != nil { - return m.gs + if k.gs != (GlobalState{}) { + return k.gs } - store := ctx.KVStore(storeKey) + store := ctx.KVStore(k.storeKey) b := store.Get(GlobalStateKey) if b == nil { return initialGlobalState() } - gs = new(GlobalState) - err := m.cdc.UnmarshalJSON(b, &gs) + err := k.cdc.UnmarshalJSON(b, &gs) if err != nil { panic(err) // This error should never occur big problem if does } return } -func (m Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { - store := ctx.KVStore(storeKey) - b, err := m.cdc.MarshalJSON(gs) +func (k Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalJSON(gs) if err != nil { panic(err) } store.Set(GlobalStateKey, b) - m.gs = GlobalState{} // clear the cache + k.gs = GlobalState{} // clear the cache +} + +//_______________________________________________________________________ + +//TODO make these next two functions more efficient should be reading and writting to state ye know + +// move a candidates asset pool from bonded to unbonded pool +func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) { + + // replace bonded shares with unbonded shares + tokens := k.removeSharesBonded(ctx, candidate.Assets) + candidate.Assets = k.addTokensUnbonded(ctx, tokens) + candidate.Status = Unbonded + k.setCandidate(ctx, candidate) +} + +// move a candidates asset pool from unbonded to bonded pool +func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { + + // replace unbonded shares with bonded shares + tokens := k.removeSharesUnbonded(ctx, candidate.Assets) + candidate.Assets = k.addTokensBonded(ctx, tokens) + candidate.Status = Bonded + k.setCandidate(ctx, candidate) +} + +// XXX expand to include the function of actually transfering the tokens + +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! +func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { + gs := k.getGlobalState(ctx) + issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + gs.BondedPool += amount + gs.BondedShares = gs.BondedShares.Add(issuedShares) + k.setGlobalState(ctx, gs) + return +} + +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! +func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { + gs := k.getGlobalState(ctx) + removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.BondedShares = gs.BondedShares.Sub(shares) + gs.BondedPool -= removedTokens + k.setGlobalState(ctx, gs) + return +} + +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! +func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { + gs := k.getGlobalState(ctx) + issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + gs.UnbondedPool += amount + k.setGlobalState(ctx, gs) + return +} + +//XXX CONFIRM that use of the exRate is correct with Zarko Spec! +func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { + gs := k.getGlobalState(ctx) + removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.UnbondedShares = gs.UnbondedShares.Sub(shares) + gs.UnbondedPool -= removedTokens + k.setGlobalState(ctx, gs) + return +} + +// add tokens to a candidate +func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { + + gs := k.getGlobalState(ctx) + exRate := candidate.delegatorShareExRate() + + var receivedGlobalShares sdk.Rat + if candidate.Status == Bonded { + receivedGlobalShares = k.addTokensBonded(ctx, amount) + } else { + receivedGlobalShares = k.addTokensUnbonded(ctx, amount) + } + candidate.Assets = candidate.Assets.Add(receivedGlobalShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) + k.setGlobalState(ctx, gs) // TODO cache GlobalState? + return +} + +// remove shares from a candidate +func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { + + gs := k.getGlobalState(ctx) + //exRate := candidate.delegatorShareExRate() //XXX make sure not used + + globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) + if candidate.Status == Bonded { + createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove) + } else { + createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove) + } + candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) + candidate.Liabilities = candidate.Liabilities.Sub(shares) + k.setGlobalState(ctx, gs) // TODO cache GlobalState? + return } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 680b516429..fa01bb9a3a 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -37,7 +37,7 @@ import ( //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) //// test the max validators term -//params.MaxVals = 4 +//params.MaxValidators = 4 //setParams(store, params) //candidates.updateVotingPower(store, gs, params) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) @@ -131,7 +131,7 @@ import ( //require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 //// test the max value and test again -//params.MaxVals = 4 +//params.MaxValidators = 4 //setParams(store, params) //change, err = UpdateValidatorSet(store, gs, params) //require.Nil(err) @@ -161,7 +161,7 @@ import ( //} func TestState(t *testing.T) { - _, _, keeper, _ := createTestInput(t, nil, false, 0) + ctx, _, keeper := createTestInput(t, nil, false, 0) addrDel := sdk.Address([]byte("addressdelegator")) addrVal := sdk.Address([]byte("addressvalidator")) @@ -172,7 +172,7 @@ func TestState(t *testing.T) { // Candidate checks // XXX expand to include both liabilities and assets use/test all candidate fields - candidate := &Candidate{ + candidate := Candidate{ Address: addrVal, PubKey: pk, Assets: sdk.NewRat(9), @@ -180,7 +180,7 @@ func TestState(t *testing.T) { VotingPower: sdk.ZeroRat, } - candidatesEqual := func(c1, c2 *Candidate) bool { + candidatesEqual := func(c1, c2 Candidate) bool { return c1.Status == c2.Status && c1.PubKey.Equals(c2.PubKey) && bytes.Equal(c1.Address, c2.Address) && @@ -191,54 +191,59 @@ func TestState(t *testing.T) { } // check the empty keeper first - resCand := keeper.getCandidate(addrVal) - assert.Nil(t, resCand) - resPks := keeper.getCandidates() + _, found := keeper.getCandidate(ctx, addrVal) + assert.False(t, found) + resPks := keeper.getCandidates(ctx) assert.Zero(t, len(resPks)) // set and retrieve a record - keeper.setCandidate(candidate) - resCand = keeper.getCandidate(addrVal) - //assert.Equal(candidate, resCand) + keeper.setCandidate(ctx, candidate) + resCand, found := keeper.getCandidate(ctx, addrVal) + assert.True(t, found) assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) // modify a records, save, and retrieve candidate.Liabilities = sdk.NewRat(99) - keeper.setCandidate(candidate) - resCand = keeper.getCandidate(addrVal) + keeper.setCandidate(ctx, candidate) + resCand, found = keeper.getCandidate(ctx, addrVal) + assert.True(t, found) assert.True(t, candidatesEqual(candidate, resCand)) // also test that the pubkey has been added to pubkey list - resPks = keeper.getCandidates() + resPks = keeper.getCandidates(ctx) require.Equal(t, 1, len(resPks)) assert.Equal(t, addrVal, resPks[0].PubKey) //---------------------------------------------------------------------- // Bond checks - bond := &DelegatorBond{ - Address: addrDel, - Shares: sdk.NewRat(9), + bond := DelegatorBond{ + DelegatorAddr: addrDel, + CandidateAddr: addrVal, + Shares: sdk.NewRat(9), } - bondsEqual := func(b1, b2 *DelegatorBond) bool { - return bytes.Equal(b1.Address, b2.Address) && + bondsEqual := func(b1, b2 DelegatorBond) bool { + return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) && + bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) && b1.Shares == b2.Shares } //check the empty keeper first - resBond := keeper.getDelegatorBond(addrDel, addrVal) - assert.Nil(t, resBond) + _, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + assert.False(t, found) //Set and retrieve a record - keeper.setDelegatorBond(addrDel, bond) - resBond = keeper.getDelegatorBond(addrDel, addrVal) + keeper.setDelegatorBond(ctx, bond) + resBond, found := keeper.getDelegatorBond(ctx, addrDel, addrVal) + assert.True(t, found) assert.True(t, bondsEqual(bond, resBond)) //modify a records, save, and retrieve bond.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(addrDel, bond) - resBond = keeper.getDelegatorBond(addrDel, addrVal) + keeper.setDelegatorBond(ctx, bond) + resBond, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + assert.True(t, found) assert.True(t, bondsEqual(bond, resBond)) //---------------------------------------------------------------------- @@ -247,21 +252,21 @@ func TestState(t *testing.T) { params := defaultParams() //check that the empty keeper loads the default - resParams := keeper.getParams() + resParams := keeper.getParams(ctx) assert.Equal(t, params, resParams) //modify a params, save, and retrieve - params.MaxVals = 777 - keeper.setParams(params) - resParams = keeper.getParams() + params.MaxValidators = 777 + keeper.setParams(ctx, params) + resParams = keeper.getParams(ctx) assert.Equal(t, params, resParams) } func TestGetValidators(t *testing.T) { - _, _, keeper, _ := createTestInput(t, nil, false, 0) - candidatesFromAddrs(keeper, addrs, []int64{400, 200, 0, 0, 0}) + ctx, _, keeper := createTestInput(t, nil, false, 0) + candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 0, 0, 0}) - validators := keeper.getValidators(5) + validators := keeper.getValidators(ctx, 5) require.Equal(t, 2, len(validators)) assert.Equal(t, addrs[0], validators[0].Address) assert.Equal(t, addrs[1], validators[1].Address) diff --git a/x/stake/msg.go b/x/stake/msg.go index ad9e7e2eb8..3b94b6bcb2 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -20,8 +20,8 @@ var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelega type MsgDeclareCandidacy struct { Description CandidateAddr sdk.Address `json:"address"` - Bond sdk.Coin `json:"bond"` PubKey crypto.PubKey `json:"pubkey"` + Bond sdk.Coin `json:"bond"` } func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey, @@ -29,8 +29,8 @@ func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey, return MsgDeclareCandidacy{ Description: description, CandidateAddr: candidateAddr, - Bond: bond, PubKey: pubkey, + Bond: bond, } } @@ -39,7 +39,7 @@ func (msg MsgDeclareCandidacy) Type() string { return func (msg MsgDeclareCandidacy) Get(key interface{}) (value interface{}) { return nil } func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } func (msg MsgDeclareCandidacy) String() string { - return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix + return fmt.Sprintf("CandidateAddr{Address: %v}", msg.CandidateAddr) // XXX fix } // get the bytes for the message signer to sign on @@ -53,11 +53,11 @@ func (msg MsgDeclareCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { - if msg.Address == nil { + if msg.CandidateAddr == nil { return ErrCandidateEmpty() } - if msg.Bond.Denom <= 0 { - return sdk.ErrInvalidCoins(coins) + if msg.Bond.Amount <= 0 { + return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}) } empty := Description{} if msg.Description == empty { @@ -74,10 +74,10 @@ type MsgEditCandidacy struct { CandidateAddr sdk.Address `json:"address"` } -func NewMsgEditCandidacy(address sdk.Address, description Description) MsgEditCandidacy { +func NewMsgEditCandidacy(candidateAddr sdk.Address, description Description) MsgEditCandidacy { return MsgEditCandidacy{ - Description: description, - Address: address, + Description: description, + CandidateAddr: candidateAddr, } } @@ -86,7 +86,7 @@ func (msg MsgEditCandidacy) Type() string { return Ms func (msg MsgEditCandidacy) Get(key interface{}) (value interface{}) { return nil } func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} } func (msg MsgEditCandidacy) String() string { - return fmt.Sprintf("CandidateAddr{Address: %v}", msg.Address) // XXX fix + return fmt.Sprintf("CandidateAddr{Address: %v}", msg.CandidateAddr) // XXX fix } // get the bytes for the message signer to sign on @@ -100,12 +100,9 @@ func (msg MsgEditCandidacy) GetSignBytes() []byte { // quick validity check func (msg MsgEditCandidacy) ValidateBasic() sdk.Error { - if msg.Address == nil { + if msg.CandidateAddr == nil { return ErrCandidateEmpty() } - if err != nil { - return err - } empty := Description{} if msg.Description == empty { return newError(CodeInvalidInput, "Transaction must include some information to modify") @@ -155,8 +152,8 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { if msg.CandidateAddr == nil { return ErrBadCandidateAddr() } - if msg.Bond.Denom <= 0 { - return sdk.ErrInvalidCoins(coins) + if msg.Bond.Amount <= 0 { + return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}) } return nil } @@ -204,7 +201,7 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { return ErrBadCandidateAddr() } if msg.Shares != "MAX" { - shares, err = sdk.NewRatFromDecimal(msg.Shares) + _, err := sdk.NewRatFromDecimal(msg.Shares) if err != nil { return ErrBadShares() } diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 0af8b0db6b..26d58a3dba 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -23,39 +23,40 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) -func TestMsgAddrValidateBasic(t *testing.T) { - tests := []struct { - name string - address sdk.Address - wantErr bool - }{ - {"basic good", addrs[0], false}, - {"empty delegator", sdk.Address{}, true}, - } +//TODO add these tests to one of some of the types +//func TestMsgAddrValidateBasic(t *testing.T) { +//tests := []struct { +//name string +//address sdk.Address +//wantErr bool +//}{ +//{"basic good", addrs[0], false}, +//{"empty delegator", sdk.Address{}, true}, +//} - for _, tc := range tests { - tx := NewMsgAddr(tc.address) - assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) - } -} +//for _, tc := range tests { +//tx := NewMsgAddr(tc.address) +//assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, +//"test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) +//} +//} -func TestValidateCoin(t *testing.T) { - tests := []struct { - name string - coin sdk.Coin - wantErr bool - }{ - {"basic good", coinPos, false}, - {"zero coin", coinZero, true}, - {"neg coin", coinNeg, true}, - } +//func TestValidateCoin(t *testing.T) { +//tests := []struct { +//name string +//coin sdk.Coin +//wantErr bool +//}{ +//{"basic good", coinPos, false}, +//{"zero coin", coinZero, true}, +//{"neg coin", coinNeg, true}, +//} - for _, tc := range tests { - assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) - } -} +//for _, tc := range tests { +//assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, +//"test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) +//} +//} func TestSerializeMsg(t *testing.T) { @@ -68,8 +69,8 @@ func TestSerializeMsg(t *testing.T) { }{ {NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})}, {NewMsgEditCandidacy(addrs[0], Description{})}, - {NewMsgDelegate(addrs[0], bond)}, - {NewMsgUnbond(addrs[0], strconv.Itoa(bondAmt))}, + {NewMsgDelegate(addrs[0], addrs[1], bond)}, + {NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, } for i, tc := range tests { diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 601ee50fdb..d3db53af43 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -65,20 +65,16 @@ func paramsNoInflation() Params { InflationMax: sdk.ZeroRat, InflationMin: sdk.ZeroRat, GoalBonded: sdk.NewRat(67, 100), - MaxVals: 100, + MaxValidators: 100, BondDenom: "fermion", - GasDeclareCandidacy: 20, - GasEditCandidacy: 20, - GasDelegate: 20, - GasUnbond: 20, } } // hogpodge of all sorts of input required for testing -func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Mapper, transact) { +func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Keeper) { db := dbm.NewMemDB() keyStake := sdk.NewKVStoreKey("stake") - keyMain := keyStake //sdk.NewKVStoreKey("main") //XXX fix multistore + keyMain := keyStake //sdk.NewKVStoreKey("main") //TODO fix multistore ms := store.NewCommitMultiStore(db) ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) @@ -86,26 +82,23 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins require.Nil(t, err) ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil) - cdc := makeTestCodec() - mapper := NewMapper(ctx, cdc, keyStake) - accountMapper := auth.NewAccountMapperSealed( keyMain, // target store &auth.BaseAccount{}, // prototype ) ck := bank.NewCoinKeeper(accountMapper) + keeper := NewKeeper(ctx, cdc, keyStake, ck) + params := paramsNoInflation() - mapper.setParams(params) + keeper.setParams(ctx, params) // fill all the addresses with some coins for _, addr := range addrs { ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}}) } - tr := newTransact(ctx, sender, mapper, ck) - - return ctx, accountMapper, mapper, tr + return ctx, accountMapper, keeper } func newPubKey(pk string) (res crypto.PubKey) { @@ -157,9 +150,9 @@ var addrs = []sdk.Address{ // NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey // instead this is just being set the address here for testing purposes -func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { +func candidatesFromAddrs(ctx sdk.Context, keeper Keeper, addrs []crypto.Address, amts []int64) { for i := 0; i < len(amts); i++ { - c := &Candidate{ + c := Candidate{ Status: Unbonded, PubKey: pks[i], Address: addrs[i], @@ -167,13 +160,13 @@ func candidatesFromAddrs(mapper Mapper, addrs []crypto.Address, amts []int64) { Liabilities: sdk.NewRat(amts[i]), VotingPower: sdk.NewRat(amts[i]), } - mapper.setCandidate(c) + keeper.setCandidate(ctx, c) } } func candidatesFromAddrsEmpty(addrs []crypto.Address) (candidates Candidates) { for i := 0; i < len(addrs); i++ { - c := &Candidate{ + c := Candidate{ Status: Unbonded, PubKey: pks[i], Address: addrs[i], diff --git a/x/stake/tick.go b/x/stake/tick.go index 27ba44f6a0..9148dcf6ea 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -6,21 +6,21 @@ import ( ) // Tick - called at the end of every block -func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) { +func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { // retrieve params - params := m.getParams() - gs := m.getGlobalState() + params := k.getParams(ctx) + gs := k.getGlobalState(ctx) height := ctx.BlockHeight() // Process Validator Provisions // XXX right now just process every 5 blocks, in new SDK make hourly if gs.InflationLastTime+5 <= height { gs.InflationLastTime = height - processProvisions(m, gs, params) + processProvisions(ctx, k, gs, params) } - newVals := m.getValidators(params.MaxVals) + newVals := k.getValidators(ctx, params.MaxValidators) // XXX determine change from old validators, set to change _ = newVals return change, nil @@ -29,7 +29,7 @@ func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) { var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func processProvisions(m Mapper, gs *GlobalState, params Params) { +func processProvisions(ctx sdk.Context, k Keeper, gs GlobalState, params Params) { gs.Inflation = nextInflation(gs, params).Round(1000000000) @@ -46,11 +46,11 @@ func processProvisions(m Mapper, gs *GlobalState, params Params) { // XXX XXX XXX XXX XXX XXX XXX XXX XXX // save the params - m.setGlobalState(gs) + k.setGlobalState(ctx, gs) } // get the next inflation rate for the hour -func nextInflation(gs *GlobalState, params Params) (inflation sdk.Rat) { +func nextInflation(gs GlobalState, params Params) (inflation sdk.Rat) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive of negative) depending or diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index fcd67af9cd..aceeb1b63b 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -8,9 +8,9 @@ import ( ) func TestGetInflation(t *testing.T) { - _, _, mapper, _ := createTestInput(t, nil, false, 0) - params := mapper.getParams() - gs := mapper.getGlobalState() + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := keeper.getParams(ctx) + gs := keeper.getGlobalState(ctx) // Governing Mechanism: // bondedRatio = BondedPool / TotalSupply @@ -53,9 +53,9 @@ func TestGetInflation(t *testing.T) { } func TestProcessProvisions(t *testing.T) { - _, _, mapper, _ := createTestInput(t, nil, false, 0) - params := mapper.getParams() - gs := mapper.getGlobalState() + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := keeper.getParams(ctx) + gs := keeper.getGlobalState(ctx) // create some candidates some bonded, some unbonded candidates := candidatesFromAddrsEmpty(addrs) @@ -65,8 +65,8 @@ func TestProcessProvisions(t *testing.T) { } mintedTokens := int64((i + 1) * 10000000) gs.TotalSupply += mintedTokens - candidate.addTokens(mintedTokens, gs) - mapper.setCandidate(candidate) + keeper.candidateAddTokens(ctx, candidate, mintedTokens) + keeper.setCandidate(ctx, candidate) } var totalSupply int64 = 550000000 var bondedShares int64 = 150000000 @@ -92,7 +92,7 @@ func TestProcessProvisions(t *testing.T) { expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() startBondedPool := gs.BondedPool startTotalSupply := gs.TotalSupply - processProvisions(mapper, gs, params) + processProvisions(ctx, keeper, gs, params) assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) } diff --git a/x/stake/types.go b/x/stake/types.go index d1cf6df5fa..72bc9db514 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -42,8 +42,8 @@ type GlobalState struct { // XXX define globalstate interface? -func initialGlobalState() *GlobalState { - return &GlobalState{ +func initialGlobalState() GlobalState { + return GlobalState{ TotalSupply: 0, BondedShares: sdk.ZeroRat, UnbondedShares: sdk.ZeroRat, @@ -55,7 +55,7 @@ func initialGlobalState() *GlobalState { } // get the bond ratio of the global state -func (gs *GlobalState) bondedRatio() sdk.Rat { +func (gs GlobalState) bondedRatio() sdk.Rat { if gs.TotalSupply > 0 { return sdk.NewRat(gs.BondedPool, gs.TotalSupply) } @@ -63,7 +63,7 @@ func (gs *GlobalState) bondedRatio() sdk.Rat { } // get the exchange rate of bonded token per issued share -func (gs *GlobalState) bondedShareExRate() sdk.Rat { +func (gs GlobalState) bondedShareExRate() sdk.Rat { if gs.BondedShares.IsZero() { return sdk.OneRat } @@ -71,48 +71,13 @@ func (gs *GlobalState) bondedShareExRate() sdk.Rat { } // get the exchange rate of unbonded tokens held in candidates per issued share -func (gs *GlobalState) unbondedShareExRate() sdk.Rat { +func (gs GlobalState) unbondedShareExRate() sdk.Rat { if gs.UnbondedShares.IsZero() { return sdk.OneRat } return gs.UnbondedShares.Inv().Mul(sdk.NewRat(gs.UnbondedPool)) } -// XXX XXX XXX -// expand to include the function of actually transfering the tokens - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) addTokensBonded(amount int64) (issuedShares sdk.Rat) { - issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.BondedPool += amount - gs.BondedShares = gs.BondedShares.Add(issuedShares) - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) removeSharesBonded(shares sdk.Rat) (removedTokens int64) { - removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.BondedShares = gs.BondedShares.Sub(shares) - gs.BondedPool -= removedTokens - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) addTokensUnbonded(amount int64) (issuedShares sdk.Rat) { - issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - gs.UnbondedPool += amount - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (gs *GlobalState) removeSharesUnbonded(shares sdk.Rat) (removedTokens int64) { - removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.UnbondedShares = gs.UnbondedShares.Sub(shares) - gs.UnbondedPool -= removedTokens - return -} - //_______________________________________________________________________________________________________ // CandidateStatus - status of a validator-candidate @@ -151,8 +116,8 @@ type Description struct { } // NewCandidate - initialize a new candidate -func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) *Candidate { - return &Candidate{ +func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) Candidate { + return Candidate{ Status: Unbonded, Address: address, PubKey: pubKey, @@ -164,50 +129,16 @@ func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Descrip } // get the exchange rate of global pool shares over delegator shares -func (c *Candidate) delegatorShareExRate() sdk.Rat { +func (c Candidate) delegatorShareExRate() sdk.Rat { if c.Liabilities.IsZero() { return sdk.OneRat } return c.Assets.Quo(c.Liabilities) } -// add tokens to a candidate -func (c *Candidate) addTokens(amount int64, gs *GlobalState) (issuedDelegatorShares sdk.Rat) { - - exRate := c.delegatorShareExRate() - - var receivedGlobalShares sdk.Rat - if c.Status == Bonded { - receivedGlobalShares = gs.addTokensBonded(amount) - } else { - receivedGlobalShares = gs.addTokensUnbonded(amount) - } - c.Assets = c.Assets.Add(receivedGlobalShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - c.Liabilities = c.Liabilities.Add(issuedDelegatorShares) - return -} - -// remove shares from a candidate -func (c *Candidate) removeShares(shares sdk.Rat, gs *GlobalState) (createdCoins int64) { - - globalPoolSharesToRemove := c.delegatorShareExRate().Mul(shares) - - if c.Status == Bonded { - createdCoins = gs.removeSharesBonded(globalPoolSharesToRemove) - } else { - createdCoins = gs.removeSharesUnbonded(globalPoolSharesToRemove) - } - c.Assets = c.Assets.Sub(globalPoolSharesToRemove) - - c.Liabilities = c.Liabilities.Sub(shares) - return -} - // Validator returns a copy of the Candidate as a Validator. // Should only be called when the Candidate qualifies as a validator. -func (c *Candidate) validator() Validator { +func (c Candidate) validator() Validator { return Validator{ Address: c.Address, // XXX !!! VotingPower: c.VotingPower, @@ -242,7 +173,7 @@ func (v Validator) ABCIValidator() (*abci.Validator, error) { //_________________________________________________________________________ // Candidates - list of Candidates -type Candidates []*Candidate +type Candidates []Candidate //_________________________________________________________________________ @@ -251,7 +182,7 @@ type Candidates []*Candidate // pubKey. // TODO better way of managing space type DelegatorBond struct { - Address sdk.Address `json:"address"` + DelegatorAddr sdk.Address `json:"delegatoraddr"` CandidateAddr sdk.Address `json:"candidate_addr"` Shares sdk.Rat `json:"shares"` } From 7da2789535632fcd7508f9f887137dbe6e36d0ab Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Mar 2018 18:52:17 +0100 Subject: [PATCH 36/54] Rational -> Rat --- docs/spec/governance/governance.md | 4 +- docs/spec/governance/state.md | 4 +- docs/spec/staking/old/spec.md | 10 +-- types/rational.go | 105 +++++++++-------------------- x/stake/keeper.go | 4 +- 5 files changed, 43 insertions(+), 84 deletions(-) diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 2e40e92dd4..9fe9635bd2 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -147,8 +147,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index b6f0d3a61c..5457590d0d 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -15,8 +15,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index dac69aa066..bbec3a94fd 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -43,11 +43,11 @@ type Params struct { HoldBonded Address // account where all bonded coins are held HoldUnbonded Address // account where all delegated but unbonded coins are held - InflationRateChange rational.Rational // maximum annual change in inflation rate - InflationMax rational.Rational // maximum inflation rate - InflationMin rational.Rational // minimum inflation rate - GoalBonded rational.Rational // Goal of percent bonded atoms - ReserveTax rational.Rational // Tax collected on all fees + InflationRateChange rational.Rat // maximum annual change in inflation rate + InflationMax rational.Rat // maximum inflation rate + InflationMin rational.Rat // minimum inflation rate + GoalBonded rational.Rat // Goal of percent bonded atoms + ReserveTax rational.Rat // Tax collected on all fees MaxValidators uint16 // maximum number of validators BondDenom string // bondable coin denomination diff --git a/types/rational.go b/types/rational.go index 3fe1a31742..b28cba4afb 100644 --- a/types/rational.go +++ b/types/rational.go @@ -23,31 +23,28 @@ type Rat struct { *big.Rat `json:"rat"` } -type Rational = Rat - -// RationalInterface - big Rat with additional functionality +// RatInterface - big Rat with additional functionality // NOTE: we only have one implementation of this interface // and don't use it anywhere, but it might come in handy -// if we want to provide Rational types that include +// if we want to provide Rat types that include // the units of the value in the type system. -type RationalInterface interface { - GetRat() *big.Rat - Num() int64 - Denom() int64 - GT(Rational) bool - LT(Rational) bool - Equal(Rational) bool - IsZero() bool - Inv() Rational - Mul(Rational) Rational - Quo(Rational) Rational - Add(Rational) Rational - Sub(Rational) Rational - Round(int64) Rational - Evaluate() int64 -} - -var _ Rational = Rat{} // enforce at compile time +//type RatInterface interface { +//GetRat() *big.Rat +//Num() int64 +//Denom() int64 +//GT(Rat) bool +//LT(Rat) bool +//Equal(Rat) bool +//IsZero() bool +//Inv() Rat +//Mul(Rat) Rat +//Quo(Rat) Rat +//Add(Rat) Rat +//Sub(Rat) Rat +//Round(int64) Rat +//Evaluate() int64 +//} +//var _ Rat = Rat{} // enforce at compile time // nolint - common values var ( @@ -111,18 +108,18 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { } //nolint -func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat -func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator -func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator -func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero -func (r Rat) Equal(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal -func (r Rat) GT(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than -func (r Rat) LT(r2 Rational) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than -func (r Rat) Inv() Rational { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse -func (r Rat) Mul(r2 Rational) Rational { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication -func (r Rat) Quo(r2 Rational) Rational { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient -func (r Rat) Add(r2 Rational) Rational { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition -func (r Rat) Sub(r2 Rational) Rational { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction +func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat +func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator +func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator +func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero +func (r Rat) Equal(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal +func (r Rat) GT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than +func (r Rat) LT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than +func (r Rat) Inv() Rat { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse +func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication +func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient +func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition +func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction var zero = big.NewInt(0) var one = big.NewInt(1) @@ -167,51 +164,13 @@ func (r Rat) Evaluate() int64 { } // Round - round Rat with the provided precisionFactor -func (r Rat) Round(precisionFactor int64) Rational { +func (r Rat) Round(precisionFactor int64) Rat { rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))} return Rat{big.NewRat(rTen.Evaluate(), precisionFactor)} } //___________________________________________________________________________________ -//var ratCdc = RegisterWire(wire.NewCodec()) -//// add rational codec elements to provided codec -//func RegisterWire(cdc *wire.Codec) *wire.Codec { -//cdc.RegisterInterface((*Rational)(nil), nil) -//cdc.RegisterConcrete(Rat{}, "rat", nil) -//return cdc -//} - -//TODO there has got to be a better way using native MarshalText and UnmarshalText - -// RatMarshal - Marshable Rat Struct -//type RatMarshal struct { -//Numerator int64 `json:"numerator"` -//Denominator int64 `json:"denominator"` -//} - -//// MarshalJSON - custom implementation of JSON Marshal -//func (r Rat) MarshalJSON() ([]byte, error) { -//return ratCdc.MarshalJSON(RatMarshal{r.Num(), r.Denom()}) -//} - -//// UnmarshalJSON - custom implementation of JSON Unmarshal -//func (r *Rat) UnmarshalJSON(data []byte) (err error) { -//defer func() { -//if rcv := recover(); rcv != nil { -//err = fmt.Errorf("Panic during UnmarshalJSON: %v", rcv) -//} -//}() - -//ratMar := new(RatMarshal) -//if err := ratCdc.UnmarshalJSON(data, ratMar); err != nil { -//return err -//} -//r.Rat = big.NewRat(ratMar.Numerator, ratMar.Denominator) - -//return nil -//} - var ratCdc JSONCodec // TODO wire.Codec // Hack to just use json.Marshal for everything until diff --git a/x/stake/keeper.go b/x/stake/keeper.go index a54d006bc2..a664edb14a 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -28,7 +28,7 @@ func GetCandidateKey(addr sdk.Address) []byte { } // GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(addr sdk.Address, power sdk.Rational, cdc *wire.Codec) []byte { +func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store } @@ -122,7 +122,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { //___________________________________________________________________________ -//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rational) *Validator { +//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rat) *Validator { //b := store.Get(GetValidatorKey(address, votingPower)) //if b == nil { //return nil From db0a275b23a1e465ef424e1e61f1126f1a956fbe Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Mar 2018 22:52:17 +0100 Subject: [PATCH 37/54] rational to Exposed Fields --- types/errors.go | 1 - types/rational.go | 131 ++++++++++++++++------------- types/rational_test.go | 68 +++++++-------- types/tx_msg.go | 2 + x/stake/keeper.go | 3 +- x/stake/keeper_test.go | 2 +- x/stake/tick_test.go | 182 +++++++++++++++++++++-------------------- x/stake/types.go | 4 +- 8 files changed, 210 insertions(+), 183 deletions(-) diff --git a/types/errors.go b/types/errors.go index 48ca8287a0..da4519725f 100644 --- a/types/errors.go +++ b/types/errors.go @@ -108,7 +108,6 @@ func ErrInsufficientCoins(msg string) Error { func ErrInvalidCoins(msg string) Error { return newError(CodeInvalidCoins, msg) } - func ErrInvalidCoins(coins Coins) Error { return newError(CodeInvalidCoins, coins.String()) } diff --git a/types/rational.go b/types/rational.go index b28cba4afb..b3a914659c 100644 --- a/types/rational.go +++ b/types/rational.go @@ -1,9 +1,6 @@ package types import ( - "bytes" - "encoding/json" - "fmt" "math/big" "strconv" "strings" @@ -20,7 +17,9 @@ import ( // we will panic unmarshalling into the // nil embedded big.Rat type Rat struct { - *big.Rat `json:"rat"` + Num int64 `json:"num"` + Denom int64 `json:"denom"` + //*big.Rat `json:"rat"` } // RatInterface - big Rat with additional functionality @@ -48,17 +47,33 @@ type Rat struct { // nolint - common values var ( - ZeroRat = Rat{big.NewRat(0, 1)} - OneRat = Rat{big.NewRat(1, 1)} + ZeroRat = NewRat(0) // Rat{big.NewRat(0, 1)} + OneRat = NewRat(1) // Rat{big.NewRat(1, 1)} ) // New - create a new Rat from integers -func NewRat(Numerator int64, Denominator ...int64) Rat { - switch len(Denominator) { +//func NewRat(Numerator int64, Denominator ...int64) Rat { +//switch len(Denominator) { +//case 0: +//return Rat{big.NewRat(Numerator, 1)} +//case 1: +//return Rat{big.NewRat(Numerator, Denominator[0])} +//default: +//panic("improper use of New, can only have one denominator") +//} +//} +func NewRat(num int64, denom ...int64) Rat { + switch len(denom) { case 0: - return Rat{big.NewRat(Numerator, 1)} + return Rat{ + Num: num, + Denom: 1, + } case 1: - return Rat{big.NewRat(Numerator, Denominator[0])} + return Rat{ + Num: num, + Denom: denom[0], + } default: panic("improper use of New, can only have one denominator") } @@ -104,22 +119,34 @@ func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { num *= -1 } - return Rat{big.NewRat(int64(num), denom)}, nil + return NewRat(int64(num), denom), nil } //nolint -func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat -func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator -func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator -func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero -func (r Rat) Equal(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal -func (r Rat) GT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than -func (r Rat) LT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than -func (r Rat) Inv() Rat { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse -func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication -func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient -func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition -func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction +func ToRat(r *big.Rat) Rat { return NewRat(r.Num().Int64(), r.Denom().Int64()) } // GetRat - get big.Rat +func (r Rat) GetRat() *big.Rat { return big.NewRat(r.Num, r.Denom) } // GetRat - get big.Rat +func (r Rat) IsZero() bool { return r.Num == 0 } // IsZero - Is the Rat equal to zero +func (r Rat) Equal(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal +func (r Rat) GT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 1 } // GT - greater than +func (r Rat) LT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == -1 } // LT - less than +func (r Rat) Inv() Rat { return ToRat(new(big.Rat).Inv(r.GetRat())) } // Inv - inverse +func (r Rat) Mul(r2 Rat) Rat { return ToRat(new(big.Rat).Mul(r.GetRat(), r2.GetRat())) } // Mul - multiplication +func (r Rat) Quo(r2 Rat) Rat { return ToRat(new(big.Rat).Quo(r.GetRat(), r2.GetRat())) } // Quo - quotient +func (r Rat) Add(r2 Rat) Rat { return ToRat(new(big.Rat).Add(r.GetRat(), r2.GetRat())) } // Add - addition +func (r Rat) Sub(r2 Rat) Rat { return ToRat(new(big.Rat).Sub(r.GetRat(), r2.GetRat())) } // Sub - subtraction +//func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat +//func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator +//func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator +//func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero +//func (r Rat) Equal(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal +//func (r Rat) GT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than +//func (r Rat) LT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than +//func (r Rat) Inv() Rat { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse +//func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication +//func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient +//func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition +//func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction +//func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } // Sub - subtraction var zero = big.NewInt(0) var one = big.NewInt(1) @@ -131,8 +158,8 @@ var ten = big.NewInt(10) // EvaluateBig - evaluate the rational using bankers rounding func (r Rat) EvaluateBig() *big.Int { - num := r.Rat.Num() - denom := r.Rat.Denom() + num := r.GetRat().Num() + denom := r.GetRat().Denom() d, rem := new(big.Int), new(big.Int) d.QuoRem(num, denom, rem) @@ -165,43 +192,35 @@ func (r Rat) Evaluate() int64 { // Round - round Rat with the provided precisionFactor func (r Rat) Round(precisionFactor int64) Rat { - rTen := Rat{new(big.Rat).Mul(r.Rat, big.NewRat(precisionFactor, 1))} - return Rat{big.NewRat(rTen.Evaluate(), precisionFactor)} + rTen := ToRat(new(big.Rat).Mul(r.GetRat(), big.NewRat(precisionFactor, 1))) + return ToRat(big.NewRat(rTen.Evaluate(), precisionFactor)) } //___________________________________________________________________________________ -var ratCdc JSONCodec // TODO wire.Codec - // Hack to just use json.Marshal for everything until // we update for amino -type JSONCodec struct{} - -func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) { - return json.Marshal(o) -} - -func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error { - return json.Unmarshal(bz, o) -} +//type JSONCodec struct{} +//func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) { return json.Marshal(o) } +//func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error { return json.Unmarshal(bz, o) } // Wraps r.MarshalText() in quotes to make it a valid JSON string. -func (r Rat) MarshalJSON() ([]byte, error) { - bz, err := r.MarshalText() - if err != nil { - return bz, err - } - return []byte(fmt.Sprintf(`"%s"`, bz)), nil -} +//func (r Rat) MarshalAmino() (string, error) { +//bz, err := r.MarshalText() +//if err != nil { +//return "", err +//} +//return fmt.Sprintf(`%s`, bz), nil +//} -// Requires a valid JSON string - strings quotes and calls UnmarshalText -func (r *Rat) UnmarshalJSON(data []byte) (err error) { - quote := []byte(`"`) - if len(data) < 2 || - !bytes.HasPrefix(data, quote) || - !bytes.HasSuffix(data, quote) { - return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string") - } - data = bytes.Trim(data, `"`) - return r.UnmarshalText(data) -} +//// Requires a valid JSON string - strings quotes and calls UnmarshalText +//func (r *Rat) UnmarshalAmino(data string) (err error) { +////quote := []byte(`"`) +////if len(data) < 2 || +////!bytes.HasPrefix(data, quote) || +////!bytes.HasSuffix(data, quote) { +////return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string") +////} +////data = bytes.Trim(data, `"`) +//return r.UnmarshalText([]byte(data)) +//} diff --git a/types/rational_test.go b/types/rational_test.go index 3794627789..cd0141bc5b 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + wire "github.com/cosmos/cosmos-sdk/wire" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -132,7 +133,7 @@ func TestArithmatic(t *testing.T) { assert.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) assert.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) - if tc.r2.Num() == 0 { // panic for divide by zero + if tc.r2.Num == 0 { // panic for divide by zero assert.Panics(t, func() { tc.r1.Quo(tc.r2) }) } else { assert.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat()) @@ -175,58 +176,61 @@ func TestRound(t *testing.T) { require.True(t, worked) tests := []struct { - r1, res Rat + r, res Rat precFactor int64 }{ {NewRat(333, 777), NewRat(429, 1000), 1000}, - {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(429, 1000), 1000}, - {Rat{new(big.Rat).SetFrac(big3, big7)}, NewRat(4285714286, 10000000000), 10000000000}, + {ToRat(new(big.Rat).SetFrac(big3, big7)), NewRat(429, 1000), 1000}, + {ToRat(new(big.Rat).SetFrac(big3, big7)), ToRat(big.NewRat(4285714286, 10000000000)), 10000000000}, {NewRat(1, 2), NewRat(1, 2), 1000}, } for _, tc := range tests { - assert.Equal(t, tc.res, tc.r1.Round(tc.precFactor), "%v", tc.r1) - negR1, negRes := tc.r1.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1)) + assert.Equal(t, tc.res, tc.r.Round(tc.precFactor), "%v", tc.r) + negR1, negRes := tc.r.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1)) assert.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1) } } -func TestZeroSerializationJSON(t *testing.T) { - r := NewRat(0, 1) - err := r.UnmarshalJSON([]byte(`"0/1"`)) - assert.Nil(t, err) - err = r.UnmarshalJSON([]byte(`"0/0"`)) - assert.NotNil(t, err) - err = r.UnmarshalJSON([]byte(`"1/0"`)) - assert.NotNil(t, err) - err = r.UnmarshalJSON([]byte(`"{}"`)) - assert.NotNil(t, err) -} +//func TestZeroSerializationJSON(t *testing.T) { +//r := NewRat(0, 1) +//err := r.UnmarshalJSON([]byte(`"0/1"`)) +//assert.Nil(t, err) +//err = r.UnmarshalJSON([]byte(`"0/0"`)) +//assert.NotNil(t, err) +//err = r.UnmarshalJSON([]byte(`"1/0"`)) +//assert.NotNil(t, err) +//err = r.UnmarshalJSON([]byte(`"{}"`)) +//assert.NotNil(t, err) +//} -func TestSerializationJSON(t *testing.T) { - r := NewRat(1, 3) +//func TestSerializationJSON(t *testing.T) { +//r := NewRat(1, 3) - bz, err := r.MarshalText() - require.Nil(t, err) +//bz, err := r.MarshalText() +//require.Nil(t, err) - r2 := NewRat(0, 1) - err = r2.UnmarshalText(bz) - require.Nil(t, err) +//r2 := NewRat(0, 1) +//err = r2.UnmarshalText(bz) +//require.Nil(t, err) - assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) -} +//assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) +//} + +var cdc = wire.NewCodec() //var jsonCdc JSONCodec // TODO wire.Codec func TestSerializationGoWire(t *testing.T) { r := NewRat(1, 3) - bz, err := ratCdc.MarshalJSON(r) + bz, err := cdc.MarshalBinary(r) require.Nil(t, err) - bz, err = r.MarshalJSON() - require.Nil(t, err) + //str, err := r.MarshalJSON() + //require.Nil(t, err) r2 := NewRat(0, 1) - err = ratCdc.UnmarshalJSON(bz, &r2) + err = cdc.UnmarshalBinary([]byte(bz), &r2) + //panic(fmt.Sprintf("debug bz: %v\n", string(bz))) require.Nil(t, err) assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2) @@ -241,12 +245,12 @@ type testEmbedStruct struct { func TestEmbeddedStructSerializationGoWire(t *testing.T) { obj := testEmbedStruct{"foo", 10, NewRat(1, 3)} - bz, err := ratCdc.MarshalJSON(obj) + bz, err := cdc.MarshalJSON(obj) require.Nil(t, err) var obj2 testEmbedStruct obj2.Field3 = NewRat(0, 1) // ... needs to be initialized - err = ratCdc.UnmarshalJSON(bz, &obj2) + err = cdc.UnmarshalJSON(bz, &obj2) require.Nil(t, err) assert.Equal(t, obj.Field1, obj2.Field1) diff --git a/types/tx_msg.go b/types/tx_msg.go index 16e5c59eb8..25d35512db 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -91,6 +91,7 @@ func NewStdFee(gas int64, amount ...Coin) StdFee { } } +// fee bytes for signing later func (fee StdFee) Bytes() []byte { // normalize. XXX // this is a sign of something ugly @@ -172,6 +173,7 @@ func NewTestMsg(addrs ...Address) *TestMsg { } } +//nolint func (msg *TestMsg) Type() string { return "TestMsg" } func (msg *TestMsg) Get(key interface{}) (value interface{}) { return nil } func (msg *TestMsg) GetSignBytes() []byte { diff --git a/x/stake/keeper.go b/x/stake/keeper.go index a664edb14a..06d274a93c 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -342,7 +342,8 @@ func (k Keeper) getParams(ctx sdk.Context) (params Params) { store := ctx.KVStore(k.storeKey) b := store.Get(ParamKey) if b == nil { - return defaultParams() + k.params = defaultParams() + return k.params } err := k.cdc.UnmarshalJSON(b, ¶ms) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index fa01bb9a3a..0745b4df76 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -200,7 +200,7 @@ func TestState(t *testing.T) { keeper.setCandidate(ctx, candidate) resCand, found := keeper.getCandidate(ctx, addrVal) assert.True(t, found) - assert.True(t, candidatesEqual(candidate, resCand), "%#v \n %#v", resCand, candidate) + assert.True(t, candidatesEqual(candidate, resCand), "%v \n %v", resCand, candidate) // modify a records, save, and retrieve candidate.Liabilities = sdk.NewRat(99) diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index aceeb1b63b..1d5cdaf746 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -1,114 +1,116 @@ 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" +//) -func TestGetInflation(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) - params := keeper.getParams(ctx) - gs := keeper.getGlobalState(ctx) +//func TestGetInflation(t *testing.T) { +//ctx, _, keeper := createTestInput(t, nil, false, 0) +//params := defaultParams() +//keeper.setParams(ctx, params) +//gs := keeper.getGlobalState(ctx) - // 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 { +//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)}, - // 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, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, - // 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 +//{1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, - // 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) +//{1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, +//{1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)}, - // 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) +//{0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, +//{0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)}, - // perfect balance shouldn't change inflation - {67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, - } - for _, tc := range tests { - gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply - gs.Inflation = tc.setInflation +//// perfect balance shouldn't change inflation +//{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, +//} +//for _, tc := range tests { +//gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply +//gs.Inflation = tc.setInflation - inflation := nextInflation(gs, params) - diffInflation := inflation.Sub(tc.setInflation) +//inflation := nextInflation(gs, params) +//diffInflation := inflation.Sub(tc.setInflation) - assert.True(t, diffInflation.Equal(tc.expectedChange), - "%v, %v", diffInflation, tc.expectedChange) - } -} +//assert.True(t, diffInflation.Equal(tc.expectedChange), +//"%v, %v", diffInflation, tc.expectedChange) +//} +//} -func TestProcessProvisions(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) - params := keeper.getParams(ctx) - gs := keeper.getGlobalState(ctx) +//func TestProcessProvisions(t *testing.T) { +//ctx, _, keeper := createTestInput(t, nil, false, 0) +//params := defaultParams() +//keeper.setParams(ctx, params) +//gs := keeper.getGlobalState(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 := 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 - // initial bonded ratio ~ 27% - assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", gs.bondedRatio()) +//// initial bonded ratio ~ 27% +//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", gs.bondedRatio()) - // Supplies - assert.Equal(t, totalSupply, gs.TotalSupply) - assert.Equal(t, bondedShares, gs.BondedPool) - assert.Equal(t, unbondedShares, gs.UnbondedPool) +//// Supplies +//assert.Equal(t, totalSupply, gs.TotalSupply) +//assert.Equal(t, bondedShares, gs.BondedPool) +//assert.Equal(t, unbondedShares, gs.UnbondedPool) - // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Equal(sdk.OneRat), "%v", gs.bondedShareExRate()) +//// test the value of candidate shares +//assert.True(t, gs.bondedShareExRate().Equal(sdk.OneRat), "%v", gs.bondedShareExRate()) - initialSupply := gs.TotalSupply - initialUnbonded := gs.TotalSupply - gs.BondedPool +//initialSupply := gs.TotalSupply +//initialUnbonded := gs.TotalSupply - gs.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 := gs.BondedPool - startTotalSupply := gs.TotalSupply - processProvisions(ctx, keeper, gs, params) - assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) - assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) - } - assert.NotEqual(t, initialSupply, gs.TotalSupply) - assert.Equal(t, initialUnbonded, gs.UnbondedPool) - //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.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 := gs.BondedPool +//startTotalSupply := gs.TotalSupply +//processProvisions(ctx, keeper, gs, params) +//assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) +//assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) +//} +//assert.NotEqual(t, initialSupply, gs.TotalSupply) +//assert.Equal(t, initialUnbonded, gs.UnbondedPool) +////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) - // initial bonded ratio ~ 35% ~ 30% increase for bonded holders - assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", gs.bondedRatio()) +//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders +//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", gs.bondedRatio()) - // global supply - assert.Equal(t, int64(611813022), gs.TotalSupply) - assert.Equal(t, int64(211813022), gs.BondedPool) - assert.Equal(t, unbondedShares, gs.UnbondedPool) +//// global supply +//assert.Equal(t, int64(611813022), gs.TotalSupply) +//assert.Equal(t, int64(211813022), gs.BondedPool) +//assert.Equal(t, unbondedShares, gs.UnbondedPool) - // test the value of candidate shares - assert.True(t, gs.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", gs.bondedShareExRate()) +//// test the value of candidate shares +//assert.True(t, gs.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", gs.bondedShareExRate()) -} +//} diff --git a/x/stake/types.go b/x/stake/types.go index 72bc9db514..096e2636c3 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -101,8 +101,8 @@ type Candidate struct { Status CandidateStatus `json:"status"` // Bonded status Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Assets sdk.Rat `json:"assets"` // total shares of a global hold pools TODO custom type PoolShares - Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators TODO custom type DelegatorShares + Assets sdk.Rat `json:"assets"` // total shares of a global hold pools + Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator Description Description `json:"description"` // Description terms for the candidate } From a4ab2fcf2fb60e23097463da2de3a6797c1dc7f6 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 14:39:31 +0100 Subject: [PATCH 38/54] staking passing, low test coverage --- types/rational_test.go | 4 +- x/stake/keeper.go | 147 ++++++----------------------------------- x/stake/keeper_test.go | 33 ++++----- x/stake/msg_test.go | 116 ++++++++++++++++---------------- x/stake/pool.go | 105 +++++++++++++++++++++++++++++ x/stake/types.go | 4 +- 6 files changed, 204 insertions(+), 205 deletions(-) create mode 100644 x/stake/pool.go diff --git a/types/rational_test.go b/types/rational_test.go index cd0141bc5b..8c85cda06e 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -245,12 +245,12 @@ type testEmbedStruct struct { func TestEmbeddedStructSerializationGoWire(t *testing.T) { obj := testEmbedStruct{"foo", 10, NewRat(1, 3)} - bz, err := cdc.MarshalJSON(obj) + bz, err := cdc.MarshalBinary(obj) require.Nil(t, err) var obj2 testEmbedStruct obj2.Field3 = NewRat(0, 1) // ... needs to be initialized - err = cdc.UnmarshalJSON(bz, &obj2) + err = cdc.UnmarshalBinary(bz, &obj2) require.Nil(t, err) assert.Equal(t, obj.Field1, obj2.Field1) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 06d274a93c..d4b6c1b176 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -29,7 +29,7 @@ func GetCandidateKey(addr sdk.Address) []byte { // GetValidatorKey - get the key for the validator used in the power-store func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { - b, _ := cdc.MarshalJSON(power) // TODO need to handle error here? + b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store } @@ -45,7 +45,7 @@ func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Cod // GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalJSON(&delegatorAddr) + res, err := cdc.MarshalBinary(&delegatorAddr) if err != nil { panic(err) } @@ -54,7 +54,7 @@ func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byt // GetDelegatorBondsKey - get the key for list of all the delegator's bonds func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalJSON(&delegatorAddr) + res, err := cdc.MarshalBinary(&delegatorAddr) if err != nil { panic(err) } @@ -90,7 +90,7 @@ func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi if b == nil { return candidate, false } - err := k.cdc.UnmarshalJSON(b, &candidate) + err := k.cdc.UnmarshalBinary(b, &candidate) if err != nil { panic(err) } @@ -105,7 +105,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { validator := Validator{candidate.Address, candidate.VotingPower} k.updateValidator(ctx, validator) - b, err := k.cdc.MarshalJSON(candidate) + b, err := k.cdc.MarshalBinary(candidate) if err != nil { panic(err) } @@ -128,7 +128,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { //return nil //} //validator := new(Validator) -//err := cdc.UnmarshalJSON(b, validator) +//err := cdc.UnmarshalBinary(b, validator) //if err != nil { //panic(err) // This error should never occur big problem if does //} @@ -140,7 +140,7 @@ func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(validator) + b, err := k.cdc.MarshalBinary(validator) if err != nil { panic(err) } @@ -156,7 +156,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) //add validator with zero power to the validator updates - b, err := k.cdc.MarshalJSON(Validator{address, sdk.ZeroRat}) + b, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat}) if err != nil { panic(err) } @@ -178,14 +178,15 @@ func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators = make([]Validator, maxVal) + for i := 0; ; i++ { - if !iterator.Valid() || i > int(maxVal) { + if !iterator.Valid() || i > int(maxVal-1) { iterator.Close() break } valBytes := iterator.Value() var val Validator - err := k.cdc.UnmarshalJSON(valBytes, &val) + err := k.cdc.UnmarshalBinary(valBytes, &val) if err != nil { panic(err) } @@ -207,7 +208,7 @@ func (k Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val Validator - err := k.cdc.UnmarshalJSON(valBytes, &val) + err := k.cdc.UnmarshalBinary(valBytes, &val) if err != nil { panic(err) } @@ -238,7 +239,7 @@ func (k Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { for ; iterator.Valid(); iterator.Next() { candidateBytes := iterator.Value() var candidate Candidate - err := k.cdc.UnmarshalJSON(candidateBytes, &candidate) + err := k.cdc.UnmarshalBinary(candidateBytes, &candidate) if err != nil { panic(err) } @@ -260,7 +261,7 @@ func (k Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { //return nil //} -//err := k.cdc.UnmarshalJSON(candidateBytes, &candidateAddrs) +//err := k.cdc.UnmarshalBinary(candidateBytes, &candidateAddrs) //if err != nil { //panic(err) //} @@ -278,7 +279,7 @@ func (k Keeper) getDelegatorBond(ctx sdk.Context, return bond, false } - err := k.cdc.UnmarshalJSON(delegatorBytes, &bond) + err := k.cdc.UnmarshalBinary(delegatorBytes, &bond) if err != nil { panic(err) } @@ -293,7 +294,7 @@ func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { //if k.getDelegatorBond(delegator, bond.Address) == nil { //pks := k.getDelegatorCandidates(delegator) //pks = append(pks, bond.Address) - //b, err := k.cdc.MarshalJSON(pks) + //b, err := k.cdc.MarshalBinary(pks) //if err != nil { //panic(err) //} @@ -301,7 +302,7 @@ func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { //} // now actually save the bond - b, err := k.cdc.MarshalJSON(bond) + b, err := k.cdc.MarshalBinary(bond) if err != nil { panic(err) } @@ -320,7 +321,7 @@ func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { //addrs = append(addrs[:i], addrs[i+1:]...) //} //} - //b, err := k.cdc.MarshalJSON(addrs) + //b, err := k.cdc.MarshalBinary(addrs) //if err != nil { //panic(err) //} @@ -346,7 +347,7 @@ func (k Keeper) getParams(ctx sdk.Context) (params Params) { return k.params } - err := k.cdc.UnmarshalJSON(b, ¶ms) + err := k.cdc.UnmarshalBinary(b, ¶ms) if err != nil { panic(err) // This error should never occur big problem if does } @@ -354,7 +355,7 @@ func (k Keeper) getParams(ctx sdk.Context) (params Params) { } func (k Keeper) setParams(ctx sdk.Context, params Params) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(params) + b, err := k.cdc.MarshalBinary(params) if err != nil { panic(err) } @@ -376,7 +377,7 @@ func (k Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { if b == nil { return initialGlobalState() } - err := k.cdc.UnmarshalJSON(b, &gs) + err := k.cdc.UnmarshalBinary(b, &gs) if err != nil { panic(err) // This error should never occur big problem if does } @@ -385,114 +386,10 @@ func (k Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { func (k Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalJSON(gs) + b, err := k.cdc.MarshalBinary(gs) if err != nil { panic(err) } store.Set(GlobalStateKey, b) k.gs = GlobalState{} // clear the cache } - -//_______________________________________________________________________ - -//TODO make these next two functions more efficient should be reading and writting to state ye know - -// move a candidates asset pool from bonded to unbonded pool -func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) { - - // replace bonded shares with unbonded shares - tokens := k.removeSharesBonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensUnbonded(ctx, tokens) - candidate.Status = Unbonded - k.setCandidate(ctx, candidate) -} - -// move a candidates asset pool from unbonded to bonded pool -func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { - - // replace unbonded shares with bonded shares - tokens := k.removeSharesUnbonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensBonded(ctx, tokens) - candidate.Status = Bonded - k.setCandidate(ctx, candidate) -} - -// XXX expand to include the function of actually transfering the tokens - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.BondedPool += amount - gs.BondedShares = gs.BondedShares.Add(issuedShares) - k.setGlobalState(ctx, gs) - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.BondedShares = gs.BondedShares.Sub(shares) - gs.BondedPool -= removedTokens - k.setGlobalState(ctx, gs) - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - gs.UnbondedPool += amount - k.setGlobalState(ctx, gs) - return -} - -//XXX CONFIRM that use of the exRate is correct with Zarko Spec! -func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.UnbondedShares = gs.UnbondedShares.Sub(shares) - gs.UnbondedPool -= removedTokens - k.setGlobalState(ctx, gs) - return -} - -// add tokens to a candidate -func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { - - gs := k.getGlobalState(ctx) - exRate := candidate.delegatorShareExRate() - - var receivedGlobalShares sdk.Rat - if candidate.Status == Bonded { - receivedGlobalShares = k.addTokensBonded(ctx, amount) - } else { - receivedGlobalShares = k.addTokensUnbonded(ctx, amount) - } - candidate.Assets = candidate.Assets.Add(receivedGlobalShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? - return -} - -// remove shares from a candidate -func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { - - gs := k.getGlobalState(ctx) - //exRate := candidate.delegatorShareExRate() //XXX make sure not used - - globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) - if candidate.Status == Bonded { - createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove) - } else { - createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove) - } - candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) - candidate.Liabilities = candidate.Liabilities.Sub(shares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? - return -} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 0745b4df76..c7e00c534a 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -160,6 +160,16 @@ import ( //testChange(t, candidates[4].validator(), change[4]) //} +//func TestGetValidators(t *testing.T) { +//ctx, _, keeper := createTestInput(t, nil, false, 0) +//candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 0, 0, 0}) + +//validators := keeper.getValidators(ctx, 2) +//require.Equal(t, 2, len(validators)) +//assert.Equal(t, addrs[0], validators[0].Address) +//assert.Equal(t, addrs[1], validators[1].Address) +//} + func TestState(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) @@ -193,8 +203,8 @@ func TestState(t *testing.T) { // check the empty keeper first _, found := keeper.getCandidate(ctx, addrVal) assert.False(t, found) - resPks := keeper.getCandidates(ctx) - assert.Zero(t, len(resPks)) + resAddrs := keeper.getCandidates(ctx) + assert.Zero(t, len(resAddrs)) // set and retrieve a record keeper.setCandidate(ctx, candidate) @@ -209,10 +219,10 @@ func TestState(t *testing.T) { assert.True(t, found) assert.True(t, candidatesEqual(candidate, resCand)) - // also test that the pubkey has been added to pubkey list - resPks = keeper.getCandidates(ctx) - require.Equal(t, 1, len(resPks)) - assert.Equal(t, addrVal, resPks[0].PubKey) + // also test that the address has been added to address list + resAddrs = keeper.getCandidates(ctx) + require.Equal(t, 1, len(resAddrs)) + assert.Equal(t, addrVal, resAddrs[0].Address) //---------------------------------------------------------------------- // Bond checks @@ -249,6 +259,7 @@ func TestState(t *testing.T) { //---------------------------------------------------------------------- // Param checks + keeper.setParams(ctx, defaultParams()) params := defaultParams() //check that the empty keeper loads the default @@ -261,13 +272,3 @@ func TestState(t *testing.T) { resParams = keeper.getParams(ctx) assert.Equal(t, params, resParams) } - -func TestGetValidators(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) - candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 0, 0, 0}) - - validators := keeper.getValidators(ctx, 5) - require.Equal(t, 2, len(validators)) - assert.Equal(t, addrs[0], validators[0].Address) - assert.Equal(t, addrs[1], validators[1].Address) -} diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 26d58a3dba..476995d659 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -1,13 +1,6 @@ package stake import ( - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - - wire "github.com/tendermint/go-wire" - sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -23,62 +16,65 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) -//TODO add these tests to one of some of the types -//func TestMsgAddrValidateBasic(t *testing.T) { -//tests := []struct { -//name string -//address sdk.Address -//wantErr bool -//}{ -//{"basic good", addrs[0], false}, -//{"empty delegator", sdk.Address{}, true}, -//} - -//for _, tc := range tests { -//tx := NewMsgAddr(tc.address) -//assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, -//"test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) -//} -//} - -//func TestValidateCoin(t *testing.T) { -//tests := []struct { -//name string -//coin sdk.Coin -//wantErr bool -//}{ -//{"basic good", coinPos, false}, -//{"zero coin", coinZero, true}, -//{"neg coin", coinNeg, true}, -//} - -//for _, tc := range tests { -//assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, -//"test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) -//} -//} - -func TestSerializeMsg(t *testing.T) { - - // make sure all types construct properly - bondAmt := 1234321 - bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} - +// TODO SUNNY make validate basic tests for each msg.. take these commented tests as inspiration +/* +func TestMsgAddrValidateBasic(t *testing.T) { tests := []struct { - tx sdk.Msg + name string + address sdk.Address + wantErr bool }{ - {NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})}, - {NewMsgEditCandidacy(addrs[0], Description{})}, - {NewMsgDelegate(addrs[0], addrs[1], bond)}, - {NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, + {"basic good", addrs[0], false}, + {"empty delegator", sdk.Address{}, true}, } - for i, tc := range tests { - var tx sdk.Tx - bs := wire.BinaryBytes(tc.tx) - err := wire.ReadBinaryBytes(bs, &tx) - if assert.NoError(t, err, "%d", i) { - assert.Equal(t, tc.tx, tx, "%d", i) - } + for _, tc := range tests { + tx := NewMsgAddr(tc.address) + assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) } } + +func TestValidateCoin(t *testing.T) { + tests := []struct { + name string + coin sdk.Coin + wantErr bool + }{ + {"basic good", coinPos, false}, + {"zero coin", coinZero, true}, + {"neg coin", coinNeg, true}, + } + + for _, tc := range tests { + assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, + "test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) + } +} +*/ + +// TODO introduce with go-amino +//func TestSerializeMsg(t *testing.T) { + +//// make sure all types construct properly +//bondAmt := 1234321 +//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)} + +//tests := []struct { +//tx sdk.Msg +//}{ +//{NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})}, +//{NewMsgEditCandidacy(addrs[0], Description{})}, +//{NewMsgDelegate(addrs[0], addrs[1], bond)}, +//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))}, +//} + +//for i, tc := range tests { +//var tx sdk.Tx +//bs := wire.BinaryBytes(tc.tx) +//err := wire.ReadBinaryBytes(bs, &tx) +//if assert.NoError(t, err, "%d", i) { +//assert.Equal(t, tc.tx, tx, "%d", i) +//} +//} +//} diff --git a/x/stake/pool.go b/x/stake/pool.go new file mode 100644 index 0000000000..21b92ef816 --- /dev/null +++ b/x/stake/pool.go @@ -0,0 +1,105 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +//TODO make these next two functions more efficient should be reading and writting to state ye know + +// move a candidates asset pool from bonded to unbonded pool +func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) { + + // replace bonded shares with unbonded shares + tokens := k.removeSharesBonded(ctx, candidate.Assets) + candidate.Assets = k.addTokensUnbonded(ctx, tokens) + candidate.Status = Unbonded + k.setCandidate(ctx, candidate) +} + +// move a candidates asset pool from unbonded to bonded pool +func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { + + // replace unbonded shares with bonded shares + tokens := k.removeSharesUnbonded(ctx, candidate.Assets) + candidate.Assets = k.addTokensBonded(ctx, tokens) + candidate.Status = Bonded + k.setCandidate(ctx, candidate) +} + +//_______________________________________________________________________ + +func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { + gs := k.getGlobalState(ctx) + issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + gs.BondedPool += amount + gs.BondedShares = gs.BondedShares.Add(issuedShares) + k.setGlobalState(ctx, gs) + return +} + +func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { + gs := k.getGlobalState(ctx) + removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.BondedShares = gs.BondedShares.Sub(shares) + gs.BondedPool -= removedTokens + k.setGlobalState(ctx, gs) + return +} + +func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { + gs := k.getGlobalState(ctx) + issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + gs.UnbondedPool += amount + k.setGlobalState(ctx, gs) + return +} + +func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { + gs := k.getGlobalState(ctx) + removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + gs.UnbondedShares = gs.UnbondedShares.Sub(shares) + gs.UnbondedPool -= removedTokens + k.setGlobalState(ctx, gs) + return +} + +//_______________________________________________________________________ + +// add tokens to a candidate +func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { + + gs := k.getGlobalState(ctx) + exRate := candidate.delegatorShareExRate() + + var receivedGlobalShares sdk.Rat + if candidate.Status == Bonded { + receivedGlobalShares = k.addTokensBonded(ctx, amount) + } else { + receivedGlobalShares = k.addTokensUnbonded(ctx, amount) + } + candidate.Assets = candidate.Assets.Add(receivedGlobalShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) + k.setGlobalState(ctx, gs) // TODO cache GlobalState? + return +} + +// remove shares from a candidate +func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { + + gs := k.getGlobalState(ctx) + //exRate := candidate.delegatorShareExRate() //XXX make sure not used + + globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) + if candidate.Status == Bonded { + createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove) + } else { + createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove) + } + candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) + candidate.Liabilities = candidate.Liabilities.Sub(shares) + k.setGlobalState(ctx, gs) // TODO cache GlobalState? + return +} diff --git a/x/stake/types.go b/x/stake/types.go index 096e2636c3..8fd039da75 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -67,7 +67,7 @@ func (gs GlobalState) bondedShareExRate() sdk.Rat { if gs.BondedShares.IsZero() { return sdk.OneRat } - return gs.BondedShares.Inv().Mul(sdk.NewRat(gs.BondedPool)) + return sdk.NewRat(gs.BondedPool).Quo(gs.BondedShares) } // get the exchange rate of unbonded tokens held in candidates per issued share @@ -75,7 +75,7 @@ func (gs GlobalState) unbondedShareExRate() sdk.Rat { if gs.UnbondedShares.IsZero() { return sdk.OneRat } - return gs.UnbondedShares.Inv().Mul(sdk.NewRat(gs.UnbondedPool)) + return sdk.NewRat(gs.UnbondedPool).Quo(gs.UnbondedShares) } //_______________________________________________________________________________________________________ From 3a011678e785d31502b264bd48347a8afc6ba2bc Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 17:00:45 +0100 Subject: [PATCH 39/54] keeper tests/revisions --- docs/spec/README.md | 15 +- docs/spec/governance/governance.md | 4 +- docs/spec/governance/state.md | 4 +- docs/spec/staking/definitions and examples.md | 250 +++--- docs/spec/staking/old/spec.md | 14 +- docs/spec/staking/spec-technical.md | 763 +++++++----------- x/stake/keeper.go | 233 ++---- x/stake/keeper_keys.go | 51 ++ x/stake/keeper_test.go | 165 ++-- x/stake/pool.go | 78 +- x/stake/pool_test.go | 22 + x/stake/test_common.go | 4 +- x/stake/tick.go | 26 +- x/stake/tick_test.go | 46 +- x/stake/types.go | 26 +- 15 files changed, 758 insertions(+), 943 deletions(-) create mode 100644 x/stake/keeper_keys.go create mode 100644 x/stake/pool_test.go diff --git a/docs/spec/README.md b/docs/spec/README.md index 5f3942ff9b..6a507dc031 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,18 +1,13 @@ # Cosmos Hub Spec -This directory contains specifications for the application level components of -the Cosmos Hub. +This directory contains specifications for the application level components of the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. -- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for - sending tokens. -- [Staking](staking) - Proof of Stake related specifications including bonding - and delegation transactions, inflation, fees, etc. -- [Governance](governance) - Governance related specifications including - proposals and voting. -- [Other](other) - Other components of the Cosmos Hub, including the reserve - pool, All in Bits vesting, etc. +- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. +- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. +- [Governance](governance) - Governance related specifications including proposals and voting. +- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), i.e. the underlying blockchain, can be found elsewhere. diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md index 9fe9635bd2..2e40e92dd4 100644 --- a/docs/spec/governance/governance.md +++ b/docs/spec/governance/governance.md @@ -147,8 +147,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/governance/state.md b/docs/spec/governance/state.md index 5457590d0d..b6f0d3a61c 100644 --- a/docs/spec/governance/state.md +++ b/docs/spec/governance/state.md @@ -15,8 +15,8 @@ type Procedure struct { MinDeposit int64 // Minimum deposit for a proposal to enter voting period. OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} - Threshold rational.Rat // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 - Veto rational.Rat // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months GovernancePenalty int64 // Penalty if validator does not vote diff --git a/docs/spec/staking/definitions and examples.md b/docs/spec/staking/definitions and examples.md index ba4c1563e2..72dbc3a3d4 100644 --- a/docs/spec/staking/definitions and examples.md +++ b/docs/spec/staking/definitions and examples.md @@ -2,183 +2,113 @@ ## Basic Terms and Definitions -* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system -* Atom - native token of the Cosmsos Hub -* Atom holder - an entity that holds some amount of Atoms -* Candidate - an Atom holder that is actively involved in the Tendermint - blockchain protocol (running Tendermint Full Node (TODO: add link to Full - Node definition) and is competing with other candidates to be elected as a - validator (TODO: add link to Validator definition)) -* Validator - a candidate that is currently selected among a set of candidates - to be able to sign protocol messages in the Tendermint consensus protocol -* Delegator - an Atom holder that has bonded some of its Atoms by delegating - them to a validator (or a candidate) -* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms - under protocol control). Atoms are always bonded through a validator (or - candidate) process. Bonded atoms can be slashed (burned) in case a validator - process misbehaves (does not behave according to the protocol specification). - Atom holders can regain access to their bonded Atoms if they have not been - slashed by waiting an Unbonding period. -* Unbonding period - a period of time after which Atom holder gains access to - its bonded Atoms (they can be withdrawn to a user account) or they can be - re-delegated. -* Inflationary provisions - inflation is the process of increasing the Atom supply. - Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders. - The goal of inflation is to incentize most of the Atoms in existence to be bonded. -* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub - transaction. The fees are collected by the current validator set and - distributed among validators and delegators in proportion to their bonded - Atom share. -* Commission fee - a fee taken from the transaction fees by a validator for - their service +- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system +- Atom - native token of the Cosmsos Hub +- Atom holder - an entity that holds some amount of Atoms +- Candidate - an Atom holder that is actively involved in Tendermint blockchain protocol (running Tendermint Full Node +TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator +(TODO: add link to Validator definition)) +- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages +in the Tendermint consensus protocol +- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate) +- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control). +Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a +validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access +to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account, +after Unbonding period has expired. +- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn +to a user account) or they can re-delegate +- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of +(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions +proportional to it's bonded Atom share. +- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected +by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share. +- Commission fee - a fee taken from the transaction fees by a validator for it's service ## The pool and the share -At the core of the Staking module is the concept of a pool which denotes a -collection of Atoms contributed by different Atom holders. There are two global -pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms -are part of the global bonded pool. If a candidate or delegator wants to unbond -its Atoms, those Atoms are moved to the the unbonding pool for the duration of -the unbonding period. In the Staking module, a pool is a logical concept, i.e., -there is no pool data structure that would be responsible for managing pool -resources. Instead, it is managed in a distributed way. More precisely, at the -global level, for each pool, we track only the total amount of bonded or unbonded -Atoms and the current amount of issued shares. A share is a unit of Atom distribution -and the value of the share (share-to-atom exchange rate) changes during -system execution. The share-to-atom exchange rate can be computed as: +At the core of the Staking module is the concept of a pool which denotes collection of Atoms contributed by different +Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part +of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are +kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept, +i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed +in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of +(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the +value of the share (share-to-atom exchange rate) is changing during the system execution. The +share-to-atom exchange rate can be computed as: -`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` +`share-to-atom-ex-rate = size of the pool / ammount of issued shares` -Then for each validator candidate (in a per candidate data structure) we keep track of -the amount of shares the candidate owns in a pool. At any point in time, -the exact amount of Atoms a candidate has in the pool can be computed as the -number of shares it owns multiplied with the current share-to-atom exchange rate: +Then for each candidate (in a per candidate data structure) we keep track of an amount of shares the candidate is owning +in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool +can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate: -`candidate-coins = candidate.Shares * share-to-atom-exchange-rate` +`candidate-coins = candidate.Shares * share-to-atom-ex-rate` -The benefit of such accounting of the pool resources is the fact that a -modification to the pool from bonding/unbonding/slashing/provisioning of -Atoms affects only global data (size of the pool and the number of shares) and -not the related validator/candidate data structure, i.e., the data structure of -other validators do not need to be modified. This has the advantage that -modifying global data is much cheaper computationally than modifying data of -every validator. Let's explain this further with several small examples: +The benefit of such accounting of the pool resources is the fact that a modification to the pool because of +bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares) +and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be +modified. Let's explain this further with several small examples: -We consider initially 4 validators p1, p2, p3 and p4, and that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 shares (note that the initial distribution of the shares, -i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., -share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we -have, the size of the pool is 40 Atoms, and the amount of issued shares is -equal to 40. And for each validator we store in their corresponding data -structure that each has 10 shares of the bonded pool. Now lets assume that the -validator p4 starts process of unbonding of 5 shares. Then the total size of -the pool is decreased and now it will be 35 shares and the amount of Atoms is -35 . Note that the only change in other data structures needed is reducing the -number of shares for a validator p4 from 10 to 5. +We consider initially 4 validators p1, p2, p3 and p4, and that each validator has bonded 10 Atoms +to a bonded pool. Furthermore, let's assume that we have issued initially 40 shares (note that the initial distribution +of the shares, i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., +share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we have, the size of the pool is 40 Atoms, and +the amount of issued shares is equal to 40. And for each validator we store in their corresponding data structure +that each has 10 shares of the bonded pool. Now lets assume that the validator p4 starts process of unbonding of 5 +shares. Then the total size of the pool is decreased and now it will be 35 shares and the amount of Atoms is 35. +Note that the only change in other data structures needed is reducing the number of shares for a validator p4 from 10 +to 5. -Let's consider now the case where a validator p1 wants to bond 15 more atoms to -the pool. Now the size of the pool is 50, and as the exchange rate hasn't -changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we -now have 50 shares in the pool in total. Validators p2, p3 and p4 still have -(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we -don't need to modify anything in their corresponding data structures. But p1 -now has 25 shares, so we update the amount of shares owned by p1 in its -data structure. Note that apart from the size of the pool that is in Atoms, all -other data structures refer only to shares. +Let's consider now the case where a validator p1 wants to bond 15 more atoms to the pool. Now the size of the pool +is 50, and as the exchange rate hasn't changed (1 share is still worth 1 Atom), we need to create more shares, +i.e. we now have 50 shares in the pool in total. +Validators p2, p3 and p4 still have (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we +don't need to modify anything in their corresponding data structures. But p1 now has 25 shares, so we update the amount +of shares owned by the p1 in its data structure. Note that apart from the size of the pool that is in Atoms, all other +data structures refer only to shares. -Finally, let's consider what happens when new Atoms are created and added to -the pool due to inflation. Let's assume that the inflation rate is 10 percent -and that it is applied to the current state of the pool. This means that 5 -Atoms are created and added to the pool and that each validator now -proportionally increase it's Atom count. Let's analyse how this change is -reflected in the data structures. First, the size of the pool is increased and -is now 55 atoms. As a share of each validator in the pool hasn't changed, this -means that the total number of shares stay the same (50) and that the amount of -shares of each validator stays the same (correspondingly 25, 10, 10, 5). But -the exchange rate has changed and each share is now worth 55/50 Atoms per -share, so each validator has effectively increased amount of Atoms it has. So -validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. +Finally, let's consider what happens when new Atoms are created and added to the pool due to inflation. Let's assume that +the inflation rate is 10 percent and that it is applied to the current state of the pool. This means that 5 Atoms are +created and added to the pool and that each validator now proportionally increase it's Atom count. Let's analyse how this +change is reflected in the data structures. First, the size of the pool is increased and is now 55 atoms. As a share of +each validator in the pool hasn't changed, this means that the total number of shares stay the same (50) and that the +amount of shares of each validator stays the same (correspondingly 25, 10, 10, 5). But the exchange rate has changed and +each share is now worth 55/50 Atoms per share, so each validator has effectively increased amount of Atoms it has. +So validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. -The concepts of the pool and its shares is at the core of the accounting in the -Staking module. It is used for managing the global pools (such as bonding and -unbonding pool), but also for distribution of Atoms between validator and its -delegators (we will explain this in section X). +The concepts of the pool and its shares is at the core of the accounting in the Staking module. It is used for managing +the global pools (such as bonding and unbonding pool), but also for distribution of Atoms between validator and its +delegators (we will explain this in section X). #### Delegator shares -A candidate is, depending on it's status, contributing Atoms to either the -bonded or unbonding pool, and in return gets some amount of (global) pool -shares. Note that not all those Atoms (and respective shares) are owned by the -candidate as some Atoms could be delegated to a candidate. The mechanism for -distribution of Atoms (and shares) between a candidate and it's delegators is -based on a notion of delegator shares. More precisely, every candidate is -issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that -represents some portion of global shares managed by the candidate -(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares -is the same as described in [Section](#The pool and the share). We now -illustrate it with an example. +A candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets +some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate +as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a +candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing +(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares +managed by the candidate (`Candidate.GlobalStakeShares`). The principle behind managing delegator shares is the same +as described in [Section](#The pool and the share). We now illustrate it with an example. -Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator -has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have -issued initially 40 global shares, i.e., that -`share-to-atom-exchange-rate = 1 atom per share`. So we will set -`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the -Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`. -Furthermore, each validator issued 10 delegator shares which are initially -owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where +Lets consider 4 validators p1, p2, p3 and p4, and assume that each validator has bonded 10 Atoms to a bonded pool. +Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per +share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure +of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator +shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where `delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. -Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and -consider what are the updates we need to make to the data structures. First, -`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for -validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to -issue also additional delegator shares, i.e., -`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator -shares of validator p1, where each delegator share is worth 1 global shares, -i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to -inflation. In that case, we only need to update `GlobalState.BondedPool` which -is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note -that the amount of global and delegator shares stay the same but they are now -worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. -Therefore, a delegator d1 now owns: +Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and consider what are the updates we need +to make to the data structures. First, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, +for validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares, +i.e., `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator shares of validator p1, where +each delegator share is worth 1 global shares, i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due +to inflation. In that case, we only need to update `GlobalState.BondedPool` which is now equal to 50 Atoms as created +Atoms are added to the bonded pool. Note that the amount of global and delegator shares stay the same but they are now +worth more as share-to-atom-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns + +`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms` + -`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` - -### Inflation provisions - -Validator provisions are minted on an hourly basis (the first block of a new -hour). The annual target of between 7% and 20%. The long-term target ratio of -bonded tokens to unbonded tokens is 67%. - -The target annual inflation rate is recalculated for each provisions cycle. The -inflation is also subject to a rate change (positive or negative) depending on -the distance from the desired ratio (67%). The maximum rate change possible is -defined to be 13% per year, however the annual inflation is capped as between -7% and 20%. - -```go -inflationRateChange(0) = 0 -GlobalState.Inflation(0) = 0.07 - -bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply -AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 - -annualInflation += AnnualInflationRateChange - -if annualInflation > 0.20 then GlobalState.Inflation = 0.20 -if annualInflation < 0.07 then GlobalState.Inflation = 0.07 - -provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) -``` - -Because the validators hold a relative bonded share (`GlobalStakeShares`), when -more bonded tokens are added proportionally to all validators, the only term -which needs to be updated is the `GlobalState.BondedPool`. So for each -provisions cycle: - -```go -GlobalState.BondedPool += provisionTokensHourly -``` diff --git a/docs/spec/staking/old/spec.md b/docs/spec/staking/old/spec.md index bbec3a94fd..bd87ec0285 100644 --- a/docs/spec/staking/old/spec.md +++ b/docs/spec/staking/old/spec.md @@ -43,14 +43,14 @@ type Params struct { HoldBonded Address // account where all bonded coins are held HoldUnbonded Address // account where all delegated but unbonded coins are held - InflationRateChange rational.Rat // maximum annual change in inflation rate - InflationMax rational.Rat // maximum inflation rate - InflationMin rational.Rat // minimum inflation rate - GoalBonded rational.Rat // Goal of percent bonded atoms - ReserveTax rational.Rat // Tax collected on all fees + InflationRateChange rational.Rational // maximum annual change in inflation rate + InflationMax rational.Rational // maximum inflation rate + InflationMin rational.Rational // minimum inflation rate + GoalBonded rational.Rational // Goal of percent bonded atoms + ReserveTax rational.Rational // Tax collected on all fees - MaxValidators uint16 // maximum number of validators - BondDenom string // bondable coin denomination + MaxVals uint16 // maximum number of validators + AllowedBondDenom string // bondable coin denomination // gas costs for txs GasDeclareCandidacy int64 diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index 5f340877e7..e3a528d948 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -2,66 +2,54 @@ ## Overview -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that -serves as a backbone of the Cosmos ecosystem. It is operated and secured by an -open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in -the process of exchanging protocol messages in the production of each block. To -avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the -validator's private key, and this is a basis for Tendermint strict -accountability that allows punishing misbehaving validators by slashing -(burning) their bonded Atoms. On the other hand, validators are rewarded for -their service of securing blockchain network by the inflationary provisions and -transactions fees. This incentives correct behavior of the validators and -provides the economic security of the network. - -The native token of the Cosmos Hub is called Atom; becoming a validator of the -Cosmos Hub requires holding Atoms. However, not all Atom holders are validators -of the Cosmos Hub. More precisely, there is a selection process that determines -the validator set as a subset of all validator candidates (Atom holders that -wants to become a validator). The other option for Atom holder is to delegate -their atoms to validators, i.e., being a delegator. A delegator is an Atom -holder that has bonded its Atoms by delegating it to a validator (or validator -candidate). By bonding Atoms to secure the network (and taking a risk of being -slashed in case of misbehaviour), a user is rewarded with inflationary -provisions and transaction fees proportional to the amount of its bonded Atoms. -The Cosmos Hub is designed to efficiently facilitate a small numbers of -validators (hundreds), and large numbers of delegators (tens of thousands). -More precisely, it is the role of the Staking module of the Cosmos Hub to -support various staking functionality including validator set selection, -delegating, bonding and withdrawing Atoms, and the distribution of inflationary -provisions and transaction fees. +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that serves as a backbone of the Cosmos ecosystem. +It is operated and secured by an open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol +messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for +Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms. +On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary +provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security +of the network. +The native token of the Cosmos Hub is called Atom; becoming a validator of the Cosmos Hub requires holding Atoms. +However, not all Atom holders are validators of the Cosmos Hub. More precisely, there is a selection process that +determines the validator set as a subset of all validator candidates (Atom holder that wants to +become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e., +being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator +(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the +validator misbehaves), a user is rewarded with inflationary provisions and transaction fees proportional to the amount +of its bonded Atoms. The Cosmos Hub is designed to efficiently facilitate a small numbers of validators (hundreds), and +large numbers of delegators (tens of thousands). More precisely, it is the role of the Staking module of the Cosmos Hub +to support various staking functionality including validator set selection; delegating, bonding and withdrawing Atoms; +and the distribution of inflationary provisions and transaction fees. + ## State The staking module persists the following information to the store: -* `GlobalState`, describing the global pools and the inflation related fields -* validator candidates (including current validators), indexed by public key and shares in the global pool -(bonded or unbonded depending on candidate status) -* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate - public key -* the queue of unbonding delegations -* the queue of re-delegations +- `GlobalState`, describing the global pools and the inflation related fields +- `map[PubKey]Candidate`, a map of validator candidates (including current validators), indexed by public key +- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by +shares in the global pool (bonded or unbonded depending on candidate status) +- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by +the delegator address and the candidate public key +- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations +- `queue[QueueElemReDelegate]`, a queue of re-delegations ### Global State -The GlobalState data structure contains total Atom supply, amount of Atoms in -the bonded pool, sum of all shares distributed for the bonded pool, amount of -Atoms in the unbonded pool, sum of all shares distributed for the unbonded -pool, a timestamp of the last processing of inflation, the current annual -inflation rate, a timestamp for the last comission accounting reset, the global -fee pool, a pool of reserve taxes collected for the governance use and an -adjustment factor for calculating global fee accum. `Params` is global data -structure that stores system parameters and defines overall functioning of the -module. - -``` go +GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares +distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the +unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp +for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use +and an adjustment factor for calculating global feel accum (?). + +``` golang type GlobalState struct { TotalSupply int64 // total supply of Atoms BondedPool int64 // reserve of bonded tokens BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonding tokens held with candidates + UnbondedPool int64 // reserve of unbonded tokens held with candidates UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool InflationLastTime int64 // timestamp of last processing of inflation Inflation rational.Rat // current annual inflation rate @@ -70,40 +58,19 @@ type GlobalState struct { ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use Adjustment rational.Rat // Adjustment factor for calculating global fee accum } - -type Params struct { - HoldBonded Address // account where all bonded coins are held - HoldUnbonding Address // account where all delegated but unbonding coins are held - - InflationRateChange rational.Rational // maximum annual change in inflation rate - InflationMax rational.Rational // maximum inflation rate - InflationMin rational.Rational // minimum inflation rate - GoalBonded rational.Rational // Goal of percent bonded atoms - ReserveTax rational.Rational // Tax collected on all fees - - MaxVals uint16 // maximum number of validators - AllowedBondDenom string // bondable coin denomination - - // gas costs for txs - GasDeclareCandidacy int64 - GasEditCandidacy int64 - GasDelegate int64 - GasRedelegate int64 - GasUnbond int64 -} ``` ### Candidate -The `Candidate` data structure holds the current state and some historical -actions of validators or candidate-validators. +The `Candidate` data structure holds the current state and some historical actions of +validators or candidate-validators. -``` go +``` golang type Candidate struct { Status CandidateStatus - ConsensusPubKey crypto.PubKey + PubKey crypto.PubKey GovernancePubKey crypto.PubKey - Owner crypto.Address + Owner Address GlobalStakeShares rational.Rat IssuedDelegatorShares rational.Rat RedelegatingShares rational.Rat @@ -116,115 +83,118 @@ type Candidate struct { Adjustment rational.Rat Description Description } +``` +CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded. + + +``` golang type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string + Name string + DateBonded string + Identity string + Website string + Details string } ``` Candidate parameters are described: -* Status: it can be Bonded (active validator), Unbonding (validator candidate) - or Revoked -* ConsensusPubKey: candidate public key that is used strictly for participating in - consensus -* GovernancePubKey: public key used by the validator for governance voting -* Owner: Address that is allowed to unbond coins. -* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if - `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` - otherwise -* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators - (which includes the candidate's self-bond); a delegator share represents - their stake in the Candidate's `GlobalStakeShares` -* RedelegatingShares: The portion of `IssuedDelegatorShares` which are - currently re-delegating to a new validator -* VotingPower: Proportional to the amount of bonded tokens which the validator - has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` -* Commission: The commission rate of fees charged to any delegators -* CommissionMax: The maximum commission rate this candidate can charge each - day from the date `GlobalState.DateLastCommissionReset` -* CommissionChangeRate: The maximum daily increase of the candidate commission -* CommissionChangeToday: Counter for the amount of change to commission rate - which has occurred today, reset on the first block of each day (UTC time) -* ProposerRewardPool: reward pool for extra fees collected when this candidate - is the proposer of a block -* Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` -* Description - * Name: moniker - * DateBonded: date determined which the validator was bonded - * Identity: optional field to provide a signature which verifies the - validators identity (ex. UPort or Keybase) - * Website: optional website link - * Details: optional details + - Status: signal that the candidate is either vying for validator status, + either unbonded or unbonding, an active validator, or a kicked validator + either unbonding or unbonded. + - PubKey: separated key from the owner of the candidate as is used strictly + for participating in consensus. + - Owner: Address where coins are bonded from and unbonded to + - GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` otherwise + - IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which + includes the candidate's self-bond); a delegator share represents their stake in + the Candidate's `GlobalStakeShares` + - RedelegatingShares: The portion of `IssuedDelegatorShares` which are + currently re-delegating to a new validator + - VotingPower: Proportional to the amount of bonded tokens which the validator + has if the candidate is a validator. + - Commission: The commission rate of fees charged to any delegators + - CommissionMax: The maximum commission rate this candidate can charge + each day from the date `GlobalState.DateLastCommissionReset` + - CommissionChangeRate: The maximum daily increase of the candidate commission + - CommissionChangeToday: Counter for the amount of change to commission rate + which has occurred today, reset on the first block of each day (UTC time) + - ProposerRewardPool: reward pool for extra fees collected when this candidate + is the proposer of a block + - Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` + - Description + - Name: moniker + - DateBonded: date determined which the validator was bonded + - Identity: optional field to provide a signature which verifies the + validators identity (ex. UPort or Keybase) + - Website: optional website link + - Details: optional details ### DelegatorBond -Atom holders may delegate coins to candidates; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one -delegator, and is associated with the shares for one candidate. The sender of -the transaction is the owner of the bond. +Atom holders may delegate coins to validators; under this circumstance their +funds are held in a `DelegatorBond` data structure. It is owned by one delegator, and is +associated with the shares for one validator. The sender of the transaction is +considered the owner of the bond. -``` go +``` golang type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat + Candidate crypto.PubKey + Shares rational.Rat AdjustmentFeePool coin.Coins AdjustmentRewardPool coin.Coins } ``` Description: -* Candidate: the public key of the validator candidate: bonding too -* Shares: the number of delegator shares received from the validator candidate -* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` -* AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Candidate.ProposerRewardPool` - + - Candidate: the public key of the validator candidate: bonding too + - Shares: the number of delegator shares received from the validator candidate + - AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` + - AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Candidate.ProposerRewardPool`` ### QueueElem -The Unbonding and re-delegation process is implemented using the ordered queue -data structure. All queue elements share a common structure: +Unbonding and re-delegation process is implemented using the ordered queue data structure. +All queue elements used share a common structure: -```golang +``` golang type QueueElem struct { - Candidate crypto.PubKey - InitTime int64 // when the element was added to the queue + Candidate crypto.PubKey + InitHeight int64 // when the queue was initiated } ``` -The queue is ordered so the next element to unbond/re-delegate is at the head. -Every tick the head of the queue is checked and if the unbonding period has -passed since `InitTime`, the final settlement of the unbonding is started or -re-delegation is executed, and the element is popped from the queue. Each -`QueueElem` is persisted in the store until it is popped from the queue. +The queue is ordered so the next to unbond/re-delegate is at the head. Every +tick the head of the queue is checked and if the unbonding period has passed +since `InitHeight`, the final settlement of the unbonding is started or re-delegation is executed, and the element is +pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue. ### QueueElemUnbondDelegation -QueueElemUnbondDelegation structure is used in the unbonding queue. - -```golang +``` golang type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio + QueueElem + Payout Address // account to pay out to + Shares rational.Rat // amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation } ``` +In the unbonding queue - the fraction of all historical slashings on +that validator are recorded (`StartSlashRatio`). When this queue reaches maturity +if that total slashing applied is greater on the validator then the +difference (amount that should have been slashed from the first validator) is +assigned to the amount being paid out. ### QueueElemReDelegate -QueueElemReDelegate structure is used in the re-delegation queue. - -```golang +``` golang type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to + QueueElem + Payout Address // account to pay out to Shares rational.Rat // amount of shares which are unbonding NewCandidate crypto.PubKey // validator to bond to after unbond } @@ -233,38 +203,31 @@ type QueueElemReDelegate struct { ### Transaction Overview Available Transactions: -* TxDeclareCandidacy -* TxEditCandidacy -* TxDelegate -* TxUnbond -* TxRedelegate -* TxLivelinessCheck -* TxProveLive + - TxDeclareCandidacy + - TxEditCandidacy + - TxLivelinessCheck + - TxProveLive + - TxDelegate + - TxUnbond + - TxRedelegate ## Transaction processing -In this section we describe the processing of the transactions and the -corresponding updates to the global state. In the following text we will use -`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a -reference to the queue of unbond delegations, `reDelegationQueue` is the -reference for the queue of redelegations. We use `tx` to denote a -reference to a transaction that is being processed, and `sender` to denote the -address of the sender of the transaction. We use function -`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, -and `saveCandidate(store, candidate)` to save it. Similarly, we use -`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the -key (sender and PubKey) from the store, and -`saveDelegatorBond(store, sender, bond)` to save it. -`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the -store. +In this section we describe the processing of the transactions and the corresponding updates to the global state. +For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the +map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a +reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the +queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed. ### TxDeclareCandidacy -A validator candidacy is declared using the `TxDeclareCandidacy` transaction. +A validator candidacy can be declared using the `TxDeclareCandidacy` transaction. +During this transaction a self-delegation transaction is executed to bond +tokens which are sent in with the transaction (TODO: What does this mean?). -```golang +``` golang type TxDeclareCandidacy struct { - ConsensusPubKey crypto.PubKey + PubKey crypto.PubKey Amount coin.Coin GovernancePubKey crypto.PubKey Commission rational.Rat @@ -272,25 +235,28 @@ type TxDeclareCandidacy struct { CommissionMaxChange int64 Description Description } +``` +``` declareCandidacy(tx TxDeclareCandidacy): - candidate = getCandidate(store, msg.Address) - if candidate != nil return // candidate with that public key already exists + // create and save the empty candidate + candidate = loadCandidate(store, tx.PubKey) + if candidate != nil then return candidate = NewCandidate(tx.PubKey) candidate.Status = Unbonded candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares,RedelegatingShares and Adjustment to rational.Zero init commision related fields based on the values from tx candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description - setCandidate(store, candidate) + saveCandidate(store, candidate) - txDelegate = TxDelegate(tx.PubKey, tx.Amount) - return delegateWithCandidate(txDelegate, candidate) - -// see delegateWithCandidate function in [TxDelegate](TxDelegate) + // move coins from the sender account to a (self-bond) delegator account + // the candidate account and global shares are updated within here + txDelegate = TxDelegate{tx.BondUpdate} + return delegateWithCandidate(txDelegate, candidate) ``` ### TxEditCandidacy @@ -299,326 +265,221 @@ If either the `Description` (excluding `DateBonded` which is constant), `Commission`, or the `GovernancePubKey` need to be updated, the `TxEditCandidacy` transaction should be sent from the owner account: -```golang +``` golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey Commission int64 Description Description } +``` +``` editCandidacy(tx TxEditCandidacy): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Revoked return - - if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 candidate.Commission = tx.Commission - if tx.Description != nil candidate.Description = tx.Description - + if candidate == nil or candidate.Status == Unbonded return + if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 then candidate.Commission = tx.Commission + if tx.Description != nil then candidate.Description = tx.Description saveCandidate(store, candidate) return -``` + ``` ### TxDelegate -Delegator bonds are created using the `TxDelegate` transaction. Within this -transaction the delegator provides an amount of coins, and in return receives -some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. +All bonding, whether self-bonding or delegation, is done via `TxDelegate`. -```golang -type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin +Delegator bonds are created using the `TxDelegate` transaction. Within this transaction the delegator provides +an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to +`DelegatorBond.Shares`. The amount of created delegator shares depends on the candidate's +delegator-shares-to-atoms exchange rate and is computed as +`delegator-shares = delegator-coins / delegator-shares-to-atom-ex-rate`. + +``` golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin } +``` +``` delegate(tx TxDelegate): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return + if candidate == nil then return return delegateWithCandidate(tx, candidate) delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked return + if candidate.Status == Revoked then return - if candidate.Status == Bonded - poolAccount = params.HoldBonded - else - poolAccount = params.HoldUnbonded - - err = transfer(sender, poolAccount, tx.Amount) - if err != nil return - - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) + if candidate.Status == Bonded then + poolAccount = address of the bonded pool + else + poolAccount = address of the unbonded pool - issuedDelegatorShares = addTokens(tx.Amount, candidate) - bond.Shares += issuedDelegatorShares - - saveCandidate(store, candidate) - saveDelegatorBond(store, sender, bond) - saveGlobalState(store, gs) - return + // Move coins from the delegator account to the bonded pool account + err = transfer(sender, poolAccount, tx.Amount) + if err != nil then return -addTokens(amount coin.Coin, candidate Candidate): - if candidate.Status == Bonded - gs.BondedPool += amount - issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - else - gs.UnbondedPool += amount - issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares + // Get or create the delegator bond + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then + bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)} - candidate.GlobalStakeShares += issuedShares - - if candidate.IssuedDelegatorShares.IsZero() + issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) + bond.Shares = bond.Shares.Add(issuedDelegatorShares) + + saveCandidate(store, candidate) + + store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) + + saveGlobalState(store, gs) + return + +addTokens(amount int64, gs GlobalState, candidate Candidate): + + // get the exchange rate of global pool shares over delegator shares + if candidate.IssuedDelegatorShares.IsZero() then exRate = rational.One else - exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares) + + if candidate.Status == Bonded then + gs.BondedPool += amount + issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens + gs.BondedShares = gs.BondedShares.Add(issuedShares) + else + gs.UnbondedPool += amount + issuedShares = exchangeRate(gs.UnbondedShares, gs.UnbondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens + gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - issuedDelegatorShares = issuedShares / exRate - candidate.IssuedDelegatorShares += issuedDelegatorShares - return issuedDelegatorShares + candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares) + + issuedDelegatorShares = exRate.Mul(receivedGlobalShares) + candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares) + return exchangeRate(shares rational.Rat, tokenAmount int64): if shares.IsZero() then return rational.One - return tokenAmount / shares + return shares.Inv().Mul(tokenAmount) ``` ### TxUnbond - Delegator unbonding is defined with the following transaction: -```golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat +``` golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat } +``` -unbond(tx TxUnbond): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil return - if bond.Shares < tx.Shares return - - bond.Shares -= tx.Shares +``` +unbond(tx TxUnbond): - candidate = loadCandidate(store, tx.PubKey) + // get delegator bond + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then return + + // subtract bond tokens from delegator bond + if bond.Shares.LT(tx.Shares) return // bond shares < tx shares - revokeCandidacy = false - if bond.Shares.IsZero() - if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) - else + bond.Shares = bond.Shares.Sub(ts.Shares) + + candidate = loadCandidate(store, tx.PubKey) + if candidate == nil return + + revokeCandidacy = false + if bond.Shares.IsZero() { + // if the bond is the owner of the candidate then trigger a revoke candidacy + if sender.Equals(candidate.Owner) and candidate.Status != Revoked then + revokeCandidacy = true + + // remove the bond + removeDelegatorBond(store, sender, tx.PubKey) + else saveDelegatorBond(store, sender, bond) - if candidate.Status == Bonded - poolAccount = params.HoldBonded + // transfer coins back to account + if candidate.Status == Bonded then + poolAccount = address of the bonded pool else - poolAccount = params.HoldUnbonded + poolAccount = address of the unbonded pool - returnedCoins = removeShares(candidate, shares) - - unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) - unbondDelegationQueue.add(unbondDelegationElem) - - transfer(poolAccount, unbondingPoolAddress, returnCoins) - - if revokeCandidacy - if candidate.Status == Bonded then bondedToUnbondedPool(candidate) - candidate.Status = Revoked + returnCoins = candidate.removeShares(shares, gs) + // TODO: Shouldn't it be created a queue element in this case? + transfer(poolAccount, sender, returnCoins) - if candidate.IssuedDelegatorShares.IsZero() - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) - - saveGlobalState(store, gs) - return - -removeShares(candidate Candidate, shares rational.Rat): - globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares - - if candidate.Status == Bonded - gs.BondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove - gs.BondedPool -= removedTokens - else - gs.UnbondedShares -= globalPoolSharesToRemove - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove - gs.UnbondedPool -= removedTokens - - candidate.GlobalStakeShares -= removedTokens - candidate.IssuedDelegatorShares -= shares - return returnedCoins - -delegatorShareExRate(candidate Candidate): - if candidate.IssuedDelegatorShares.IsZero() then return rational.One - return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - -bondedToUnbondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares - gs.BondedShares -= candidate.GlobalStakeShares - gs.BondedPool -= removedTokens - - gs.UnbondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) - gs.UnbondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Unbonded - - return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) -``` - -### TxRedelegate - -The re-delegation command allows delegators to switch validators while still -receiving equal reward to as if they had never unbonded. - -```golang -type TxRedelegate struct { - PubKeyFrom crypto.PubKey - PubKeyTo crypto.PubKey - Shares rational.Rat -} - -redelegate(tx TxRedelegate): - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return - - if bond.Shares < tx.Shares return - candidate = loadCandidate(store, tx.PubKeyFrom) - if candidate == nil return - - candidate.RedelegatingShares += tx.Shares - reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) - redelegationQueue.add(reDelegationElem) - return -``` - -### TxLivelinessCheck - -Liveliness issues are calculated by keeping track of the block precommits in -the block header. A queue is persisted which contains the block headers from -all recent blocks for the duration of the unbonding period. A validator is -defined as having livliness issues if they have not been included in more than -33% of the blocks over: -* The most recent 24 Hours if they have >= 20% of global stake -* The most recent week if they have = 0% of global stake -* Linear interpolation of the above two scenarios - -Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is -submitted. - -```golang -type TxLivelinessCheck struct { - PubKey crypto.PubKey - RewardAccount Addresss -} -``` - -If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the -liveliness punishment is provided as a reward to `RewardAccount`. - -### TxProveLive - -If the validator was kicked for liveliness issues and is able to regain -liveliness then all delegators in the temporary unbonding pool which have not -transacted to move will be bonded back to the now-live validator and begin to -once again collect provisions and rewards. Regaining liveliness is demonstrated -by sending in a `TxProveLive` transaction: - -```golang -type TxProveLive struct { - PubKey crypto.PubKey -} -``` - -### End of block handling - -```golang -tick(ctx Context): - hrsPerYr = 8766 // as defined by a julian year of 365.25 days - - time = ctx.Time() - if time > gs.InflationLastTime + ProvisionTimeout - gs.InflationLastTime = time - gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) + if revokeCandidacy then + // change the share types to unbonded if they were not already + if candidate.Status == Bonded then + // replace bonded shares with unbonded shares + tokens = gs.removeSharesBonded(candidate.GlobalStakeShares) + candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens) + candidate.Status = Unbonded - provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) - - gs.BondedPool += provisions - gs.TotalSupply += provisions - - saveGlobalState(store, gs) - - if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) - unbondDelegationQueue.remove(elem) - - if time > reDelegationQueue.head().InitTime + UnbondingPeriod - for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do - candidate = getCandidate(store, elem.PubKey) - returnedCoins = removeShares(candidate, elem.Shares) - candidate.RedelegatingShares -= elem.Shares - delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) - reDelegationQueue.remove(elem) - - return UpdateValidatorSet() + transfer(address of the bonded pool, address of the unbonded pool, tokens) + // lastly update the status + candidate.Status = Revoked -nextInflation(hrsPerYr rational.Rat): - if gs.TotalSupply > 0 - bondedRatio = gs.BondedPool / gs.TotalSupply - else - bondedRation = 0 - - inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange - inflationRateChange = inflationRateChangePerYear / hrsPerYr + // deduct shares from the candidate and save + if candidate.GlobalStakeShares.IsZero() then + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) - inflation = gs.Inflation + inflationRateChange - if inflation > params.InflationMax then inflation = params.InflationMax + saveGlobalState(store, gs) + return - if inflation < params.InflationMin then inflation = params.InflationMin - - return inflation +removeDelegatorBond(candidate Candidate): -UpdateValidatorSet(): - candidates = loadCandidates(store) + // first remove from the list of bonds + pks = loadDelegatorCandidates(store, sender) + for i, pk := range pks { + if candidate.Equals(pk) { + pks = append(pks[:i], pks[i+1:]...) + } + } + b := wire.BinaryBytes(pks) + store.Set(GetDelegatorBondsKey(delegator), b) - v1 = candidates.Validators() - v2 = updateVotingPower(candidates).Validators() - - change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets - return change - -updateVotingPower(candidates Candidates): - foreach candidate in candidates do - candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) - - candidates.Sort() - - foreach candidate in candidates do - if candidate is not in the first params.MaxVals - candidate.VotingPower = rational.Zero - if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) - - else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) - - saveCandidate(store, c) - - return candidates - -unbondedToBondedPool(candidate Candidate): - removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares - gs.UnbondedShares -= candidate.GlobalStakeShares - gs.UnbondedPool -= removedTokens - - gs.BondedPool += removedTokens - issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) - gs.BondedShares += issuedShares - - candidate.GlobalStakeShares = issuedShares - candidate.Status = Bonded - - return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) + // now remove the actual bond + store.Remove(GetDelegatorBondKey(delegator, candidate)) + //updateDelegatorBonds(store, delegator) +} +``` + +### Inflation provisions + +Validator provisions are minted on an hourly basis (the first block of a new +hour). The annual target of between 7% and 20%. The long-term target ratio of +bonded tokens to unbonded tokens is 67%. + +The target annual inflation rate is recalculated for each previsions cycle. The +inflation is also subject to a rate change (positive of negative) depending or +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +``` +inflationRateChange(0) = 0 +GlobalState.Inflation(0) = 0.07 + +bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply +AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 + +annualInflation += AnnualInflationRateChange + +if annualInflation > 0.20 then GlobalState.Inflation = 0.20 +if annualInflation < 0.07 then GlobalState.Inflation = 0.07 + +provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) +``` + +Because the validators hold a relative bonded share (`GlobalStakeShares`), when +more bonded tokens are added proportionally to all validators, the only term +which needs to be updated is the `GlobalState.BondedPool`. So for each previsions +cycle: + +``` +GlobalState.BondedPool += provisionTokensHourly ``` diff --git a/x/stake/keeper.go b/x/stake/keeper.go index d4b6c1b176..eaed55dbcb 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -6,63 +6,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) -//nolint -var ( - // Keys for store prefixes - CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses - ParamKey = []byte{0x02} // key for global parameters relating to staking - GlobalStateKey = []byte{0x03} // key for global parameters relating to staking - - // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond - DelegatorBondsKeyPrefix = []byte{0x08} // prefix for each key to a delegator's bond -) - -// XXX remove beggining word get from all these keys -// GetCandidateKey - get the key for the candidate with address -func GetCandidateKey(addr sdk.Address) []byte { - return append(CandidateKeyPrefix, addr.Bytes()...) -} - -// GetValidatorKey - get the key for the validator used in the power-store -func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { - b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? - return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store -} - -// GetValidatorUpdatesKey - get the key for the validator used in the power-store -func GetValidatorUpdatesKey(addr sdk.Address) []byte { - return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store -} - -// GetDelegatorBondKey - get the key for delegator bond with candidate -func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte { - return append(GetDelegatorBondKeyPrefix(delegatorAddr, cdc), candidateAddr.Bytes()...) -} - -// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates -func GetDelegatorBondKeyPrefix(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegatorBondKeyPrefix, res...) -} - -// GetDelegatorBondsKey - get the key for list of all the delegator's bonds -func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { - res, err := cdc.MarshalBinary(&delegatorAddr) - if err != nil { - panic(err) - } - return append(DelegatorBondsKeyPrefix, res...) -} - -//___________________________________________________________________________ - // keeper of the staking store type Keeper struct { storeKey sdk.StoreKey @@ -70,7 +13,7 @@ type Keeper struct { coinKeeper bank.CoinKeeper //just caches - gs GlobalState + gs Pool params Params } @@ -83,7 +26,8 @@ func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinK return keeper } -//XXX load/save -> get/set +//_________________________________________________________________________ + func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { store := ctx.KVStore(k.storeKey) b := store.Get(GetCandidateKey(addr)) @@ -100,7 +44,6 @@ func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store := ctx.KVStore(k.storeKey) - // XXX should only remove validator if we know candidate is a validator k.removeValidator(ctx, candidate.Address) validator := Validator{candidate.Address, candidate.VotingPower} k.updateValidator(ctx, validator) @@ -114,26 +57,34 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { store := ctx.KVStore(k.storeKey) - - // XXX should only remove validator if we know candidate is a validator k.removeValidator(ctx, candidateAddr) store.Delete(GetCandidateKey(candidateAddr)) } -//___________________________________________________________________________ +func (k Keeper) getCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { + store := ctx.KVStore(k.storeKey) + iterator := store.Iterator(subspace(CandidateKeyPrefix)) -//func loadValidator(store sdk.KVStore, address sdk.Address, votingPower sdk.Rat) *Validator { -//b := store.Get(GetValidatorKey(address, votingPower)) -//if b == nil { -//return nil -//} -//validator := new(Validator) -//err := cdc.UnmarshalBinary(b, validator) -//if err != nil { -//panic(err) // This error should never occur big problem if does -//} -//return validator -//} + candidates = make([]Candidate, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + iterator.Close() + break + } + bz := iterator.Value() + var candidate Candidate + err := k.cdc.UnmarshalBinary(bz, &candidate) + if err != nil { + panic(err) + } + candidates[i] = candidate + iterator.Next() + } + return candidates[:i] // trim +} + +//___________________________________________________________________________ // updateValidator - update a validator and create accumulate any changes // in the changed validator substore @@ -155,6 +106,8 @@ func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) { func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store := ctx.KVStore(k.storeKey) + // XXX ensure that this record is a validator even? + //add validator with zero power to the validator updates b, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat}) if err != nil { @@ -193,7 +146,6 @@ func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali validators[i] = val iterator.Next() } - return } @@ -229,45 +181,6 @@ func (k Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { iterator.Close() } -//--------------------------------------------------------------------- - -// getCandidates - get the active list of all candidates -func (k Keeper) getCandidates(ctx sdk.Context) (candidates Candidates) { - store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(CandidateKeyPrefix)) - - for ; iterator.Valid(); iterator.Next() { - candidateBytes := iterator.Value() - var candidate Candidate - err := k.cdc.UnmarshalBinary(candidateBytes, &candidate) - if err != nil { - panic(err) - } - candidates = append(candidates, candidate) - } - iterator.Close() - return candidates -} - -//_____________________________________________________________________ - -// XXX use a store iterator here instead -//// load the pubkeys of all candidates a delegator is delegated too -//func (k Keeper) getDelegatorCandidates(ctx sdk.Context, delegator sdk.Address) (candidateAddrs []sdk.Address) { -//store := ctx.KVStore(k.storeKey) - -//candidateBytes := store.Get(GetDelegatorBondsKey(delegator, k.cdc)) -//if candidateBytes == nil { -//return nil -//} - -//err := k.cdc.UnmarshalBinary(candidateBytes, &candidateAddrs) -//if err != nil { -//panic(err) -//} -//return -//} - //_____________________________________________________________________ func (k Keeper) getDelegatorBond(ctx sdk.Context, @@ -288,20 +201,6 @@ func (k Keeper) getDelegatorBond(ctx sdk.Context, func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { store := ctx.KVStore(k.storeKey) - - // XXX use store iterator - // if a new bond add to the list of bonds - //if k.getDelegatorBond(delegator, bond.Address) == nil { - //pks := k.getDelegatorCandidates(delegator) - //pks = append(pks, bond.Address) - //b, err := k.cdc.MarshalBinary(pks) - //if err != nil { - //panic(err) - //} - //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) - //} - - // now actually save the bond b, err := k.cdc.MarshalBinary(bond) if err != nil { panic(err) @@ -311,25 +210,32 @@ func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { store := ctx.KVStore(k.storeKey) - - // XXX use store iterator - // TODO use list queries on multistore to remove iterations here! - // first remove from the list of bonds - //addrs := k.getDelegatorCandidates(delegator) - //for i, addr := range addrs { - //if bytes.Equal(candidateAddr, addr) { - //addrs = append(addrs[:i], addrs[i+1:]...) - //} - //} - //b, err := k.cdc.MarshalBinary(addrs) - //if err != nil { - //panic(err) - //} - //store.Set(GetDelegatorBondsKey(delegator, k.cdc), b) - - // now remove the actual bond store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) - //updateDelegatorBonds(store, delegator) //XXX remove? +} + +// load all bonds of a delegator +func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { + store := ctx.KVStore(k.storeKey) + delegatorPrefixKey := GetDelegatorBondsKey(delegator, k.cdc) + iterator := store.Iterator(subspace(delegatorPrefixKey)) //smallest to largest + + bonds = make([]DelegatorBond, maxRetrieve) + i := 0 + for ; ; i++ { + if !iterator.Valid() || i > int(maxRetrieve-1) { + iterator.Close() + break + } + bondBytes := iterator.Value() + var bond DelegatorBond + err := k.cdc.UnmarshalBinary(bondBytes, &bond) + if err != nil { + panic(err) + } + bonds[i] = bond + iterator.Next() + } + return bonds[:i] // trim } //_______________________________________________________________________ @@ -349,7 +255,7 @@ func (k Keeper) getParams(ctx sdk.Context) (params Params) { err := k.cdc.UnmarshalBinary(b, ¶ms) if err != nil { - panic(err) // This error should never occur big problem if does + panic(err) } return } @@ -362,34 +268,3 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) { store.Set(ParamKey, b) k.params = Params{} // clear the cache } - -//_______________________________________________________________________ - -// XXX nothing is this Keeper should return a pointer...!!!!!! -// load/save the global staking state -func (k Keeper) getGlobalState(ctx sdk.Context) (gs GlobalState) { - // check if cached before anything - if k.gs != (GlobalState{}) { - return k.gs - } - store := ctx.KVStore(k.storeKey) - b := store.Get(GlobalStateKey) - if b == nil { - return initialGlobalState() - } - err := k.cdc.UnmarshalBinary(b, &gs) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return -} - -func (k Keeper) setGlobalState(ctx sdk.Context, gs GlobalState) { - store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(gs) - if err != nil { - panic(err) - } - store.Set(GlobalStateKey, b) - k.gs = GlobalState{} // clear the cache -} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go new file mode 100644 index 0000000000..c31d65f2c5 --- /dev/null +++ b/x/stake/keeper_keys.go @@ -0,0 +1,51 @@ +package stake + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +//nolint +var ( + // Keys for store prefixes + CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses + ParamKey = []byte{0x02} // key for global parameters relating to staking + PoolKey = []byte{0x03} // key for global parameters relating to staking + + // Key prefixes + CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate + ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate + ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate + DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond +) + +// XXX remove beggining word get from all these keys +// GetCandidateKey - get the key for the candidate with address +func GetCandidateKey(addr sdk.Address) []byte { + return append(CandidateKeyPrefix, addr.Bytes()...) +} + +// GetValidatorKey - get the key for the validator used in the power-store +func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { + b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? + return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store +} + +// GetValidatorUpdatesKey - get the key for the validator used in the power-store +func GetValidatorUpdatesKey(addr sdk.Address) []byte { + return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store +} + +// GetDelegatorBondKey - 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()...) +} + +// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates +func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { + res, err := cdc.MarshalBinary(&delegatorAddr) + if err != nil { + panic(err) + } + return append(DelegatorBondKeyPrefix, res...) +} diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index c7e00c534a..4b0a52d050 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -19,7 +19,7 @@ import ( //assert := assert.New(t) //store := initTestStore(t) //params := getParams(store) -//gs := getGlobalState(store) +//gs := getPool(store) //N := 5 //actors := newAddrs(N) @@ -27,19 +27,19 @@ import ( //// test a basic change in voting power //candidates[0].Assets = sdk.NewRat(500) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //// test a swap in voting power //candidates[1].Assets = sdk.NewRat(600) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) //assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) //// test the max validators term //params.MaxValidators = 4 //setParams(store, params) -//candidates.updateVotingPower(store, gs, params) +//candidates.updateVotingPower(store, p, params) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) //} @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getGlobalState(store) //N := 5 +//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getPool(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -126,14 +126,14 @@ import ( //} //// they should all already be validators -//change, err := UpdateValidatorSet(store, gs, params) +//change, err := UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 //// test the max value and test again //params.MaxValidators = 4 //setParams(store, params) -//change, err = UpdateValidatorSet(store, gs, params) +//change, err = UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(1, len(change), "%v", change) //testRemove(t, candidates[4].validator(), change[0]) @@ -149,7 +149,7 @@ import ( //for _, c := range candidates { //setCandidate(store, c) //} -//change, err = UpdateValidatorSet(store, gs, params) +//change, err = UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed //candidates = getCandidates(store) @@ -170,25 +170,42 @@ import ( //assert.Equal(t, addrs[1], validators[1].Address) //} -func TestState(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) +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() - addrDel := sdk.Address([]byte("addressdelegator")) - addrVal := sdk.Address([]byte("addressvalidator")) - //pk := newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57") - pk := crypto.GenPrivKeyEd25519().PubKey() - - //---------------------------------------------------------------------- - // Candidate checks - - // XXX expand to include both liabilities and assets use/test all candidate fields - candidate := Candidate{ - Address: addrVal, - PubKey: pk, + candidate1 = Candidate{ + Address: addrVal1, + PubKey: pk1, Assets: sdk.NewRat(9), Liabilities: sdk.NewRat(9), VotingPower: sdk.ZeroRat, } + candidate2 = Candidate{ + Address: addrVal2, + PubKey: pk2, + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + VotingPower: sdk.ZeroRat, + } + candidate3 = Candidate{ + Address: addrVal3, + PubKey: pk3, + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + VotingPower: sdk.ZeroRat, + } +) + +// XXX expand to include both liabilities and assets use/test all candidate1 fields +func TestCandidate(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) candidatesEqual := func(c1, c2 Candidate) bool { return c1.Status == c2.Status && @@ -201,35 +218,40 @@ func TestState(t *testing.T) { } // check the empty keeper first - _, found := keeper.getCandidate(ctx, addrVal) + _, found := keeper.getCandidate(ctx, addrVal1) assert.False(t, found) - resAddrs := keeper.getCandidates(ctx) + resAddrs := keeper.getCandidates(ctx, 100) assert.Zero(t, len(resAddrs)) // set and retrieve a record - keeper.setCandidate(ctx, candidate) - resCand, found := keeper.getCandidate(ctx, addrVal) + keeper.setCandidate(ctx, candidate1) + resCand, found := keeper.getCandidate(ctx, addrVal1) assert.True(t, found) - assert.True(t, candidatesEqual(candidate, resCand), "%v \n %v", resCand, candidate) + assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1) // modify a records, save, and retrieve - candidate.Liabilities = sdk.NewRat(99) - keeper.setCandidate(ctx, candidate) - resCand, found = keeper.getCandidate(ctx, addrVal) + candidate1.Liabilities = sdk.NewRat(99) + keeper.setCandidate(ctx, candidate1) + resCand, found = keeper.getCandidate(ctx, addrVal1) assert.True(t, found) - assert.True(t, candidatesEqual(candidate, resCand)) + assert.True(t, candidatesEqual(candidate1, resCand)) // also test that the address has been added to address list - resAddrs = keeper.getCandidates(ctx) + resAddrs = keeper.getCandidates(ctx, 100) require.Equal(t, 1, len(resAddrs)) - assert.Equal(t, addrVal, resAddrs[0].Address) + assert.Equal(t, addrVal1, resAddrs[0].Address) - //---------------------------------------------------------------------- - // Bond checks +} - bond := DelegatorBond{ - DelegatorAddr: addrDel, - CandidateAddr: addrVal, +func TestBond(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + // first add a candidate1 to delegate too + keeper.setCandidate(ctx, candidate1) + + bond1to1 := DelegatorBond{ + DelegatorAddr: addrDel1, + CandidateAddr: addrVal1, Shares: sdk.NewRat(9), } @@ -239,36 +261,65 @@ func TestState(t *testing.T) { b1.Shares == b2.Shares } - //check the empty keeper first - _, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + // check the empty keeper first + _, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.False(t, found) - //Set and retrieve a record - keeper.setDelegatorBond(ctx, bond) - resBond, found := keeper.getDelegatorBond(ctx, addrDel, addrVal) + // set and retrieve a record + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.True(t, found) - assert.True(t, bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond1to1, resBond)) - //modify a records, save, and retrieve - bond.Shares = sdk.NewRat(99) - keeper.setDelegatorBond(ctx, bond) - resBond, found = keeper.getDelegatorBond(ctx, addrDel, addrVal) + // modify a records, save, and retrieve + bond1to1.Shares = sdk.NewRat(99) + keeper.setDelegatorBond(ctx, bond1to1) + resBond, found = keeper.getDelegatorBond(ctx, addrDel1, addrVal1) assert.True(t, found) - assert.True(t, bondsEqual(bond, resBond)) + assert.True(t, bondsEqual(bond1to1, resBond)) - //---------------------------------------------------------------------- - // Param checks + // 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.setDelegatorBond(ctx, bond1to2) + keeper.setDelegatorBond(ctx, bond1to3) + keeper.setDelegatorBond(ctx, bond2to1) + keeper.setDelegatorBond(ctx, bond2to2) + keeper.setDelegatorBond(ctx, bond2to3) - keeper.setParams(ctx, defaultParams()) - params := defaultParams() + // test all bond retrieve capabilities + resBonds := keeper.getDelegatorBonds(ctx, addrDel1, 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) + require.Equal(t, 3, len(resBonds)) + resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 2) + require.Equal(t, 2, len(resBonds)) + resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + require.Equal(t, 3, len(resBonds)) + assert.True(t, bondsEqual(bond2to1, resBonds[0])) + assert.True(t, bondsEqual(bond2to2, resBonds[1])) + assert.True(t, bondsEqual(bond2to3, resBonds[2])) +} + +func TestParams(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + expParams := defaultParams() //check that the empty keeper loads the default resParams := keeper.getParams(ctx) - assert.Equal(t, params, resParams) + assert.Equal(t, expParams, resParams) //modify a params, save, and retrieve - params.MaxValidators = 777 - keeper.setParams(ctx, params) + expParams.MaxValidators = 777 + keeper.setParams(ctx, expParams) resParams = keeper.getParams(ctx) - assert.Equal(t, params, resParams) + assert.Equal(t, expParams, resParams) } diff --git a/x/stake/pool.go b/x/stake/pool.go index 21b92ef816..7bf1682ae5 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -4,6 +4,36 @@ 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 + } + 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 +} + +//_______________________________________________________________________ + //TODO make these next two functions more efficient should be reading and writting to state ye know // move a candidates asset pool from bonded to unbonded pool @@ -29,38 +59,38 @@ func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { //_______________________________________________________________________ func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.BondedPool += amount - gs.BondedShares = gs.BondedShares.Add(issuedShares) - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens + p.BondedPool += amount + p.BondedShares = p.BondedShares.Add(issuedShares) + k.setPool(ctx, p) return } func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.BondedShares = gs.BondedShares.Sub(shares) - gs.BondedPool -= removedTokens - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.BondedShares = p.BondedShares.Sub(shares) + p.BondedPool -= removedTokens + k.setPool(ctx, p) return } func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - gs := k.getGlobalState(ctx) - issuedShares = gs.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) - gs.UnbondedPool += amount - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + 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 } func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - gs := k.getGlobalState(ctx) - removedTokens = gs.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares - gs.UnbondedShares = gs.UnbondedShares.Sub(shares) - gs.UnbondedPool -= removedTokens - k.setGlobalState(ctx, gs) + p := k.getPool(ctx) + removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares + p.UnbondedShares = p.UnbondedShares.Sub(shares) + p.UnbondedPool -= removedTokens + k.setPool(ctx, p) return } @@ -69,7 +99,7 @@ func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTo // add tokens to a candidate func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) exRate := candidate.delegatorShareExRate() var receivedGlobalShares sdk.Rat @@ -82,14 +112,14 @@ func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount issuedDelegatorShares = exRate.Mul(receivedGlobalShares) candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? + k.setPool(ctx, p) // TODO cache Pool? return } // remove shares from a candidate func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) //exRate := candidate.delegatorShareExRate() //XXX make sure not used globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) @@ -100,6 +130,6 @@ func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shar } candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) candidate.Liabilities = candidate.Liabilities.Sub(shares) - k.setGlobalState(ctx, gs) // TODO cache GlobalState? + k.setPool(ctx, p) // TODO cache Pool? return } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go new file mode 100644 index 0000000000..f95b833e96 --- /dev/null +++ b/x/stake/pool_test.go @@ -0,0 +1,22 @@ +package stake + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +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/test_common.go b/x/stake/test_common.go index d3db53af43..3cdd0fb747 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -90,8 +90,8 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins ck := bank.NewCoinKeeper(accountMapper) keeper := NewKeeper(ctx, cdc, keyStake, ck) - params := paramsNoInflation() - keeper.setParams(ctx, params) + //params := paramsNoInflation() + params := keeper.getParams(ctx) // fill all the addresses with some coins for _, addr := range addrs { diff --git a/x/stake/tick.go b/x/stake/tick.go index 9148dcf6ea..de0b52c0c3 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -10,14 +10,14 @@ func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { // retrieve params params := k.getParams(ctx) - gs := k.getGlobalState(ctx) + p := k.getPool(ctx) height := ctx.BlockHeight() // Process Validator Provisions // XXX right now just process every 5 blocks, in new SDK make hourly - if gs.InflationLastTime+5 <= height { - gs.InflationLastTime = height - processProvisions(ctx, k, gs, params) + if p.InflationLastTime+5 <= height { + p.InflationLastTime = height + processProvisions(ctx, k, p, params) } newVals := k.getValidators(ctx, params.MaxValidators) @@ -29,28 +29,28 @@ func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func processProvisions(ctx sdk.Context, k Keeper, gs GlobalState, params Params) { +func processProvisions(ctx sdk.Context, k Keeper, p Pool, params Params) { - gs.Inflation = nextInflation(gs, params).Round(1000000000) + p.Inflation = nextInflation(p, params).Round(1000000000) // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term // which needs to be updated is the `BondedPool`. So for each previsions cycle: - provisions := gs.Inflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr).Evaluate() - gs.BondedPool += provisions - gs.TotalSupply += provisions + provisions := p.Inflation.Mul(sdk.NewRat(p.TotalSupply)).Quo(hrsPerYr).Evaluate() + p.BondedPool += provisions + p.TotalSupply += provisions // XXX XXX XXX XXX XXX XXX XXX XXX XXX // XXX Mint them to the hold account // XXX XXX XXX XXX XXX XXX XXX XXX XXX // save the params - k.setGlobalState(ctx, gs) + k.setPool(ctx, p) } // get the next inflation rate for the hour -func nextInflation(gs GlobalState, params Params) (inflation sdk.Rat) { +func nextInflation(p Pool, params Params) (inflation sdk.Rat) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive of negative) depending or @@ -59,11 +59,11 @@ func nextInflation(gs GlobalState, params Params) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat.Sub(gs.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChangePerYear := sdk.OneRat.Sub(p.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) // increase the new annual inflation for this next cycle - inflation = gs.Inflation.Add(inflationRateChange) + inflation = p.Inflation.Add(inflationRateChange) if inflation.GT(params.InflationMax) { inflation = params.InflationMax } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 1d5cdaf746..4a1a9f6867 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -11,7 +11,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getGlobalState(ctx) +//gs := keeper.getPool(ctx) //// Governing Mechanism: //// bondedRatio = BondedPool / TotalSupply @@ -42,7 +42,7 @@ package stake //{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, //} //for _, tc := range tests { -//gs.BondedPool, gs.TotalSupply = tc.setBondedPool, tc.setTotalSupply +//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply //gs.Inflation = tc.setInflation //inflation := nextInflation(gs, params) @@ -57,7 +57,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getGlobalState(ctx) +//gs := keeper.getPool(ctx) //// create some candidates some bonded, some unbonded //candidates := candidatesFromAddrsEmpty(addrs) @@ -75,42 +75,42 @@ package stake //var unbondedShares int64 = 400000000 //// initial bonded ratio ~ 27% -//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", gs.bondedRatio()) +//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio()) //// Supplies -//assert.Equal(t, totalSupply, gs.TotalSupply) -//assert.Equal(t, bondedShares, gs.BondedPool) -//assert.Equal(t, unbondedShares, gs.UnbondedPool) +//assert.Equal(t, totalSupply, p.TotalSupply) +//assert.Equal(t, bondedShares, p.BondedPool) +//assert.Equal(t, unbondedShares, p.UnbondedPool) //// test the value of candidate shares -//assert.True(t, gs.bondedShareExRate().Equal(sdk.OneRat), "%v", gs.bondedShareExRate()) +//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate()) -//initialSupply := gs.TotalSupply -//initialUnbonded := gs.TotalSupply - gs.BondedPool +//initialSupply := p.TotalSupply +//initialUnbonded := p.TotalSupply - p.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 := gs.BondedPool -//startTotalSupply := gs.TotalSupply -//processProvisions(ctx, keeper, gs, params) -//assert.Equal(t, startBondedPool+expProvisions, gs.BondedPool) -//assert.Equal(t, startTotalSupply+expProvisions, gs.TotalSupply) +//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, gs.TotalSupply) -//assert.Equal(t, initialUnbonded, gs.UnbondedPool) -////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", gs.TotalSupply, gs.BondedPool, gs.TotalSupply-gs.BondedPool)) +//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)) //// initial bonded ratio ~ 35% ~ 30% increase for bonded holders -//assert.True(t, gs.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", gs.bondedRatio()) +//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio()) //// global supply -//assert.Equal(t, int64(611813022), gs.TotalSupply) -//assert.Equal(t, int64(211813022), gs.BondedPool) -//assert.Equal(t, unbondedShares, gs.UnbondedPool) +//assert.Equal(t, int64(611813022), p.TotalSupply) +//assert.Equal(t, int64(211813022), p.BondedPool) +//assert.Equal(t, unbondedShares, p.UnbondedPool) //// test the value of candidate shares -//assert.True(t, gs.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", gs.bondedShareExRate()) +//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate()) //} diff --git a/x/stake/types.go b/x/stake/types.go index 8fd039da75..cdf8ece066 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -29,8 +29,8 @@ func defaultParams() Params { //_________________________________________________________________________ -// GlobalState - dynamic parameters of the current state -type GlobalState struct { +// Pool - dynamic parameters of the current state +type Pool struct { TotalSupply int64 `json:"total_supply"` // total supply of all tokens BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool @@ -42,8 +42,8 @@ type GlobalState struct { // XXX define globalstate interface? -func initialGlobalState() GlobalState { - return GlobalState{ +func initialPool() Pool { + return Pool{ TotalSupply: 0, BondedShares: sdk.ZeroRat, UnbondedShares: sdk.ZeroRat, @@ -55,27 +55,27 @@ func initialGlobalState() GlobalState { } // get the bond ratio of the global state -func (gs GlobalState) bondedRatio() sdk.Rat { - if gs.TotalSupply > 0 { - return sdk.NewRat(gs.BondedPool, gs.TotalSupply) +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 (gs GlobalState) bondedShareExRate() sdk.Rat { - if gs.BondedShares.IsZero() { +func (p Pool) bondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { return sdk.OneRat } - return sdk.NewRat(gs.BondedPool).Quo(gs.BondedShares) + return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) } // get the exchange rate of unbonded tokens held in candidates per issued share -func (gs GlobalState) unbondedShareExRate() sdk.Rat { - if gs.UnbondedShares.IsZero() { +func (p Pool) unbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { return sdk.OneRat } - return sdk.NewRat(gs.UnbondedPool).Quo(gs.UnbondedShares) + return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) } //_______________________________________________________________________________________________________ From 5ba297089adfb57dcc86410e9f9c36a6ecd853e2 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 18:10:25 +0100 Subject: [PATCH 40/54] expose some keeper functions, tick cleanup --- client/rpc/validators.go | 8 +++---- x/stake/handler.go | 18 ++++++++-------- x/stake/handler_test.go | 12 +++++------ x/stake/keeper.go | 19 ++++++++++------- x/stake/keeper_test.go | 26 +++++++++++------------ x/stake/pool.go | 14 ++++++------ x/stake/pool_test.go | 4 ++-- x/stake/test_common.go | 2 +- x/stake/tick.go | 46 ++++++++++++++++++++++------------------ x/stake/tick_test.go | 4 ++-- 10 files changed, 80 insertions(+), 73 deletions(-) diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 32c7680ec6..15c3230e3e 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -24,7 +24,7 @@ func validatorCommand() *cobra.Command { return cmd } -func getValidators(height *int64) ([]byte, error) { +func GetValidators(height *int64) ([]byte, error) { // get the node node, err := client.GetNode() if err != nil { @@ -59,7 +59,7 @@ func printValidators(cmd *cobra.Command, args []string) error { } } - output, err := getValidators(height) + output, err := GetValidators(height) if err != nil { return err } @@ -84,7 +84,7 @@ func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) return } - output, err := getValidators(&height) + output, err := GetValidators(&height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) @@ -100,7 +100,7 @@ func LatestValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) return } - output, err := getValidators(&height) + output, err := GetValidators(&height) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/x/stake/handler.go b/x/stake/handler.go index 8dda9e7f38..091268b440 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -19,7 +19,7 @@ const ( // separated for testing //func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error { -//params := k.getParams(ctx) +//params := k.GetParams(ctx) //switch key { //case "allowed_bond_denom": //params.BondDenom = value @@ -88,11 +88,11 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) sdk.Result { // check to see if the pubkey or sender has been registered before - _, found := k.getCandidate(ctx, msg.CandidateAddr) + _, found := k.GetCandidate(ctx, msg.CandidateAddr) if found { return ErrCandidateExistsAddr().Result() } - if msg.Bond.Denom != k.getParams(ctx).BondDenom { + if msg.Bond.Denom != k.GetParams(ctx).BondDenom { return ErrBadBondingDenom().Result() } if ctx.IsCheckTx() { @@ -112,7 +112,7 @@ func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandida func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sdk.Result { // candidate must already be registered - candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) if !found { return ErrBadCandidateAddr().Result() } @@ -146,11 +146,11 @@ func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sd func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { - candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) if !found { return ErrBadCandidateAddr().Result() } - if msg.Bond.Denom != k.getParams(ctx).BondDenom { + if msg.Bond.Denom != k.GetParams(ctx).BondDenom { return ErrBadBondingDenom().Result() } if ctx.IsCheckTx() { @@ -250,7 +250,7 @@ func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { bond.Shares = bond.Shares.Sub(shares) // get pubKey candidate - candidate, found := k.getCandidate(ctx, msg.CandidateAddr) + candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) if !found { return ErrNoCandidateForAddress().Result() } @@ -273,7 +273,7 @@ func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { // Add the coins returnAmount := k.candidateRemoveShares(ctx, candidate, shares) - returnCoins := sdk.Coins{{k.getParams(ctx).BondDenom, returnAmount}} + returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) // lastly if an revoke candidate if necessary @@ -308,7 +308,7 @@ func (k Keeper) UnbondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candi bond.Shares = bond.Shares.Sub(shares) returnAmount := k.candidateRemoveShares(ctx, candidate, shares) - returnCoins := sdk.Coins{{k.getParams(ctx).BondDenom, returnAmount}} + returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} _, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) if err != nil { diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 665458c1aa..b6953df5c9 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -68,7 +68,7 @@ package stake //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() +//candidates := mapper.GetCandidates() //expectedBond += bondAmount ////expectedSender := initSender - expectedBond //gotBonded := candidates[0].Liabilities.Evaluate() @@ -97,7 +97,7 @@ package stake //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() +//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() @@ -148,7 +148,7 @@ package stake //assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) ////Check that the account is bonded -//candidates := mapper.getCandidates() +//candidates := mapper.GetCandidates() //require.Equal(t, i, len(candidates)) //val := candidates[i] //balanceExpd := initSender - 10 @@ -160,17 +160,17 @@ package stake //// unbond them all //for i, addr := range addrs { -//candidatePre := mapper.getCandidate(addrs[i]) +//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() +//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]) +//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) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index eaed55dbcb..8f314e41cc 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -28,7 +28,8 @@ func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinK //_________________________________________________________________________ -func (k Keeper) getCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { +// get a single candidate +func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) { store := ctx.KVStore(k.storeKey) b := store.Get(GetCandidateKey(addr)) if b == nil { @@ -61,7 +62,8 @@ func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { store.Delete(GetCandidateKey(candidateAddr)) } -func (k Keeper) getCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { +// Get the set of all candidates, retrieve a maxRetrieve number of records +func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { store := ctx.KVStore(k.storeKey) iterator := store.Iterator(subspace(CandidateKeyPrefix)) @@ -116,7 +118,7 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { store.Set(GetValidatorUpdatesKey(address), b) // now actually delete from the validator set - candidate, found := k.getCandidate(ctx, address) + candidate, found := k.GetCandidate(ctx, address) if found { store.Delete(GetValidatorKey(address, candidate.VotingPower, k.cdc)) } @@ -125,14 +127,15 @@ func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower -func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Validator) { +func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) + maxVal := k.GetParams(ctx).MaxValidators iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest validators = make([]Validator, maxVal) - - for i := 0; ; i++ { + i := 0 + for ; ; i++ { if !iterator.Valid() || i > int(maxVal-1) { iterator.Close() break @@ -146,7 +149,7 @@ func (k Keeper) getValidators(ctx sdk.Context, maxVal uint16) (validators []Vali validators[i] = val iterator.Next() } - return + return validators[:i] // trim } //_________________________________________________________________________ @@ -241,7 +244,7 @@ func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet //_______________________________________________________________________ // load/save the global staking params -func (k Keeper) getParams(ctx sdk.Context) (params Params) { +func (k Keeper) GetParams(ctx sdk.Context) (params Params) { // check if cached before anything if k.params != (Params{}) { return k.params diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 4b0a52d050..181093d19b 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -18,8 +18,8 @@ import ( //func TestUpdateVotingPower(t *testing.T) { //assert := assert.New(t) //store := initTestStore(t) -//params := getParams(store) -//gs := getPool(store) +//params := GetParams(store) +//gs := GetPool(store) //N := 5 //actors := newAddrs(N) @@ -118,7 +118,7 @@ import ( //testRemove(t, vs1[1], changed[0]) //testRemove(t, vs1[2], changed[1]) -//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := getParams(store) //gs := getPool(store) //N := 5 +//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := GetParams(store) //gs := GetPool(store) //N := 5 //actors := newAddrs(N) //candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) //for _, c := range candidates { @@ -137,7 +137,7 @@ import ( //require.Nil(err) //require.Equal(1, len(change), "%v", change) //testRemove(t, candidates[4].validator(), change[0]) -//candidates = getCandidates(store) +//candidates = GetCandidates(store) //assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) //// mess with the power's of the candidates and test @@ -152,7 +152,7 @@ import ( //change, err = UpdateValidatorSet(store, p, params) //require.Nil(err) //require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed -//candidates = getCandidates(store) +//candidates = GetCandidates(store) //testChange(t, candidates[0].validator(), change[0]) //testChange(t, candidates[1].validator(), change[1]) //testChange(t, candidates[2].validator(), change[2]) @@ -164,7 +164,7 @@ import ( //ctx, _, keeper := createTestInput(t, nil, false, 0) //candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 0, 0, 0}) -//validators := keeper.getValidators(ctx, 2) +//validators := keeper.GetValidators(ctx, 2) //require.Equal(t, 2, len(validators)) //assert.Equal(t, addrs[0], validators[0].Address) //assert.Equal(t, addrs[1], validators[1].Address) @@ -218,26 +218,26 @@ func TestCandidate(t *testing.T) { } // check the empty keeper first - _, found := keeper.getCandidate(ctx, addrVal1) + _, found := keeper.GetCandidate(ctx, addrVal1) assert.False(t, found) - resAddrs := keeper.getCandidates(ctx, 100) + resAddrs := keeper.GetCandidates(ctx, 100) assert.Zero(t, len(resAddrs)) // set and retrieve a record keeper.setCandidate(ctx, candidate1) - resCand, found := keeper.getCandidate(ctx, addrVal1) + resCand, found := keeper.GetCandidate(ctx, addrVal1) assert.True(t, found) assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1) // modify a records, save, and retrieve candidate1.Liabilities = sdk.NewRat(99) keeper.setCandidate(ctx, candidate1) - resCand, found = keeper.getCandidate(ctx, addrVal1) + resCand, found = keeper.GetCandidate(ctx, addrVal1) assert.True(t, found) assert.True(t, candidatesEqual(candidate1, resCand)) // also test that the address has been added to address list - resAddrs = keeper.getCandidates(ctx, 100) + resAddrs = keeper.GetCandidates(ctx, 100) require.Equal(t, 1, len(resAddrs)) assert.Equal(t, addrVal1, resAddrs[0].Address) @@ -314,12 +314,12 @@ func TestParams(t *testing.T) { expParams := defaultParams() //check that the empty keeper loads the default - resParams := keeper.getParams(ctx) + resParams := keeper.GetParams(ctx) assert.Equal(t, expParams, resParams) //modify a params, save, and retrieve expParams.MaxValidators = 777 keeper.setParams(ctx, expParams) - resParams = keeper.getParams(ctx) + resParams = keeper.GetParams(ctx) assert.Equal(t, expParams, resParams) } diff --git a/x/stake/pool.go b/x/stake/pool.go index 7bf1682ae5..4c185580e0 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -5,7 +5,7 @@ import ( ) // load/save the global staking state -func (k Keeper) getPool(ctx sdk.Context) (gs Pool) { +func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) { // check if cached before anything if k.gs != (Pool{}) { return k.gs @@ -59,7 +59,7 @@ func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { //_______________________________________________________________________ func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.getPool(ctx) + p := k.GetPool(ctx) issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens p.BondedPool += amount p.BondedShares = p.BondedShares.Add(issuedShares) @@ -68,7 +68,7 @@ func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk } func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.getPool(ctx) + p := k.GetPool(ctx) removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.BondedShares = p.BondedShares.Sub(shares) p.BondedPool -= removedTokens @@ -77,7 +77,7 @@ func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedToke } func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.getPool(ctx) + p := k.GetPool(ctx) issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens p.UnbondedShares = p.UnbondedShares.Add(issuedShares) p.UnbondedPool += amount @@ -86,7 +86,7 @@ func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares s } func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.getPool(ctx) + p := k.GetPool(ctx) removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.UnbondedShares = p.UnbondedShares.Sub(shares) p.UnbondedPool -= removedTokens @@ -99,7 +99,7 @@ func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTo // add tokens to a candidate func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { - p := k.getPool(ctx) + p := k.GetPool(ctx) exRate := candidate.delegatorShareExRate() var receivedGlobalShares sdk.Rat @@ -119,7 +119,7 @@ func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount // remove shares from a candidate func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { - p := k.getPool(ctx) + p := k.GetPool(ctx) //exRate := candidate.delegatorShareExRate() //XXX make sure not used globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index f95b833e96..760a89a16b 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -11,12 +11,12 @@ func TestPool(t *testing.T) { expPool := initialPool() //check that the empty keeper loads the default - resPool := keeper.getPool(ctx) + 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) + resPool = keeper.GetPool(ctx) assert.Equal(t, expPool, resPool) } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 3cdd0fb747..96923872cd 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -91,7 +91,7 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins keeper := NewKeeper(ctx, cdc, keyStake, ck) //params := paramsNoInflation() - params := keeper.getParams(ctx) + params := keeper.GetParams(ctx) // fill all the addresses with some coins for _, addr := range addrs { diff --git a/x/stake/tick.go b/x/stake/tick.go index de0b52c0c3..47be614e07 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -5,53 +5,57 @@ import ( abci "github.com/tendermint/abci/types" ) +const ( + hrsPerYear = 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 + // Tick - called at the end of every block -func Tick(ctx sdk.Context, k Keeper) (change []*abci.Validator, err error) { +func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) { // retrieve params - params := k.getParams(ctx) - p := k.getPool(ctx) + 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 - processProvisions(ctx, k, p, params) + k.processProvisions(ctx) } - newVals := k.getValidators(ctx, params.MaxValidators) + newVals := k.GetValidators(ctx) + // XXX determine change from old validators, set to change _ = newVals return change, nil } -var hrsPerYr = sdk.NewRat(8766) // as defined by a julian year of 365.25 days - // process provisions for an hour period -func processProvisions(ctx sdk.Context, k Keeper, p Pool, params Params) { +func (k Keeper) processProvisions(ctx sdk.Context) { - p.Inflation = nextInflation(p, params).Round(1000000000) + pool := k.GetPool(ctx) + pool.Inflation = k.nextInflation(ctx).Round(precision) //TODO make this number a const somewhere? // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term // which needs to be updated is the `BondedPool`. So for each previsions cycle: - provisions := p.Inflation.Mul(sdk.NewRat(p.TotalSupply)).Quo(hrsPerYr).Evaluate() - p.BondedPool += provisions - p.TotalSupply += provisions - - // XXX XXX XXX XXX XXX XXX XXX XXX XXX - // XXX Mint them to the hold account - // XXX XXX XXX XXX XXX XXX XXX XXX XXX + provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate() + pool.BondedPool += provisions + pool.TotalSupply += provisions // save the params - k.setPool(ctx, p) + k.setPool(ctx, pool) } // get the next inflation rate for the hour -func nextInflation(p Pool, params Params) (inflation sdk.Rat) { +func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { + params := k.GetParams(ctx) + pool := k.GetPool(ctx) // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive of negative) depending or // the distance from the desired ratio (67%). The maximum rate change possible is @@ -59,11 +63,11 @@ func nextInflation(p Pool, params Params) (inflation sdk.Rat) { // 7% and 20%. // (1 - bondedRatio/GoalBonded) * InflationRateChange - inflationRateChangePerYear := sdk.OneRat.Sub(p.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) - inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr) + inflationRateChangePerYear := sdk.OneRat.Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange) + inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat) // increase the new annual inflation for this next cycle - inflation = p.Inflation.Add(inflationRateChange) + inflation = pool.Inflation.Add(inflationRateChange) if inflation.GT(params.InflationMax) { inflation = params.InflationMax } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 4a1a9f6867..540ce46999 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -11,7 +11,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getPool(ctx) +//gs := keeper.GetPool(ctx) //// Governing Mechanism: //// bondedRatio = BondedPool / TotalSupply @@ -57,7 +57,7 @@ package stake //ctx, _, keeper := createTestInput(t, nil, false, 0) //params := defaultParams() //keeper.setParams(ctx, params) -//gs := keeper.getPool(ctx) +//gs := keeper.GetPool(ctx) //// create some candidates some bonded, some unbonded //candidates := candidatesFromAddrsEmpty(addrs) From 27e0bbca4e63906cd2d2f0b92936f1f6de65e66a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 22:47:57 +0100 Subject: [PATCH 41/54] major keeper revisions --- x/stake/commands/query.go | 2 +- x/stake/keeper.go | 169 ++++++++++++++++++++++---------------- x/stake/keeper_keys.go | 46 ++++++----- 3 files changed, 126 insertions(+), 91 deletions(-) diff --git a/x/stake/commands/query.go b/x/stake/commands/query.go index b6ccb712e5..ed436305c3 100644 --- a/x/stake/commands/query.go +++ b/x/stake/commands/query.go @@ -45,7 +45,7 @@ func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command { Short: "Query for the set of validator-candidates pubkeys", RunE: func(cmd *cobra.Command, args []string) error { - key := PrefixedKey(stake.MsgType, stake.CandidatesAddrKey) + key := PrefixedKey(stake.MsgType, stake.CandidatesKey) res, err := builder.Query(key, storeName) if err != nil { diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 8f314e41cc..7f82d747a8 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -12,7 +12,7 @@ type Keeper struct { cdc *wire.Codec coinKeeper bank.CoinKeeper - //just caches + // caches gs Pool params Params } @@ -42,30 +42,10 @@ func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candi return candidate, true } -func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { - store := ctx.KVStore(k.storeKey) - - k.removeValidator(ctx, candidate.Address) - validator := Validator{candidate.Address, candidate.VotingPower} - k.updateValidator(ctx, validator) - - b, err := k.cdc.MarshalBinary(candidate) - if err != nil { - panic(err) - } - store.Set(GetCandidateKey(candidate.Address), b) -} - -func (k Keeper) removeCandidate(ctx sdk.Context, candidateAddr sdk.Address) { - store := ctx.KVStore(k.storeKey) - k.removeValidator(ctx, candidateAddr) - store.Delete(GetCandidateKey(candidateAddr)) -} - // Get the set of all candidates, retrieve a maxRetrieve number of records func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) { store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(CandidateKeyPrefix)) + iterator := store.Iterator(subspace(CandidatesKey)) candidates = make([]Candidate, maxRetrieve) i := 0 @@ -86,53 +66,82 @@ func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Ca return candidates[:i] // trim } -//___________________________________________________________________________ - -// updateValidator - update a validator and create accumulate any changes -// in the changed validator substore -func (k Keeper) updateValidator(ctx sdk.Context, validator Validator) { +func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store := ctx.KVStore(k.storeKey) + address := candidate.Address - b, err := k.cdc.MarshalBinary(validator) + // retreive the old candidate record + oldCandidate, oldFound := k.GetCandidate(ctx, address) + + // marshal the candidate record and add to the state + bz, err := k.cdc.MarshalBinary(candidate) if err != nil { panic(err) } + store.Set(GetCandidateKey(candidate.Address), bz) - // add to the validators to update list if necessary - store.Set(GetValidatorUpdatesKey(validator.Address), b) + // mashal the new validator record + validator := Validator{address, candidate.VotingPower} + bz, err = k.cdc.MarshalBinary(validator) + if err != nil { + panic(err) + } // update the list ordered by voting power - store.Set(GetValidatorKey(validator.Address, validator.VotingPower, k.cdc), b) + if oldFound { + store.Delete(GetValidatorKey(address, oldCandidate.VotingPower, k.cdc)) + } + 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) + } -func (k Keeper) removeValidator(ctx sdk.Context, address sdk.Address) { +func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { + + // first retreive the old candidate record + oldCandidate, found := k.GetCandidate(ctx, address) + if !found { + return + } + + // delete the old candidate record store := ctx.KVStore(k.storeKey) + store.Delete(GetCandidateKey(address)) - // XXX ensure that this record is a validator even? - - //add validator with zero power to the validator updates - b, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat}) + // delete from recent and power weighted validator groups if the validator + // exists and add validator with zero power to the validator updates + if store.Get(GetRecentValidatorKey(address)) == nil { + return + } + bz, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat}) if err != nil { panic(err) } - store.Set(GetValidatorUpdatesKey(address), b) - - // now actually delete from the validator set - candidate, found := k.GetCandidate(ctx, address) - if found { - store.Delete(GetValidatorKey(address, candidate.VotingPower, k.cdc)) - } + store.Set(GetAccUpdateValidatorKey(address), bz) + store.Delete(GetRecentValidatorKey(address)) + store.Delete(GetValidatorKey(address, oldCandidate.VotingPower, k.cdc)) } +//___________________________________________________________________________ + // get the most recent updated validator set from the Candidates. These bonds // are already sorted by VotingPower from the UpdateVotingPower function which // is the only function which is to modify the VotingPower +// this function also updaates the most recent validators saved in store func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) + + // clear the recent validators store + k.deleteSubSpace(store, RecentValidatorsKey) + + // add the actual validator power sorted store maxVal := k.GetParams(ctx).MaxValidators - - iterator := store.Iterator(subspace(ValidatorKeyPrefix)) //smallest to largest - + iterator := store.Iterator(subspace(ValidatorsKey)) //smallest to largest validators = make([]Validator, maxVal) i := 0 for ; ; i++ { @@ -140,26 +149,40 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { iterator.Close() break } - valBytes := iterator.Value() + bz := iterator.Value() var val Validator - err := k.cdc.UnmarshalBinary(valBytes, &val) + err := k.cdc.UnmarshalBinary(bz, &val) if err != nil { panic(err) } validators[i] = val + + // also add to the recent validators group + store.Set(GetRecentValidatorKey(val.Address), bz) + iterator.Next() } + return validators[:i] // trim } -//_________________________________________________________________________ +// 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) + if store.Get(GetRecentValidatorKey(address)) == nil { + return false + } + return true +} -// get the most updated validators -func (k Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { +//_________________________________________________________________________ +// Accumulated updates to the validator set + +// get the most recently updated validators +func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []Validator) { store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) //smallest to largest - + iterator := store.Iterator(subspace(AccUpdateValidatorsKey)) //smallest to largest for ; iterator.Valid(); iterator.Next() { valBytes := iterator.Value() var val Validator @@ -169,17 +192,21 @@ func (k Keeper) getValidatorUpdates(ctx sdk.Context) (updates []Validator) { } updates = append(updates, val) } - iterator.Close() return } // remove all validator update entries -func (k Keeper) clearValidatorUpdates(ctx sdk.Context, maxVal int) { +func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) { store := ctx.KVStore(k.storeKey) - iterator := store.Iterator(subspace(ValidatorUpdatesKeyPrefix)) + k.deleteSubSpace(store, AccUpdateValidatorsKey) +} + +// TODO move to common functionality somewhere +func (k Keeper) deleteSubSpace(store sdk.KVStore, key []byte) { + iterator := store.Iterator(subspace(key)) for ; iterator.Valid(); iterator.Next() { - store.Delete(iterator.Key()) // XXX write test for this, may need to be in a second loop + store.Delete(iterator.Key()) } iterator.Close() } @@ -202,20 +229,6 @@ func (k Keeper) getDelegatorBond(ctx sdk.Context, return bond, true } -func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { - store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(bond) - if err != nil { - panic(err) - } - store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) -} - -func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { - store := ctx.KVStore(k.storeKey) - store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) -} - // load all bonds of a delegator func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) { store := ctx.KVStore(k.storeKey) @@ -241,6 +254,20 @@ func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRet return bonds[:i] // trim } +func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalBinary(bond) + if err != nil { + panic(err) + } + store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b) +} + +func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc)) +} + //_______________________________________________________________________ // load/save the global staking params diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index c31d65f2c5..ae0928bcc6 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -5,43 +5,51 @@ import ( "github.com/cosmos/cosmos-sdk/wire" ) +// TODO remove some of these prefixes once have working multistore + //nolint var ( // Keys for store prefixes - CandidatesAddrKey = []byte{0x01} // key for all candidates' addresses - ParamKey = []byte{0x02} // key for global parameters relating to staking - PoolKey = []byte{0x03} // key for global parameters relating to staking + ParamKey = []byte{0x00} // key for global parameters relating to staking + PoolKey = []byte{0x01} // key for global parameters relating to staking + 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 - // Key prefixes - CandidateKeyPrefix = []byte{0x04} // prefix for each key to a candidate - ValidatorKeyPrefix = []byte{0x05} // prefix for each key to a candidate - ValidatorUpdatesKeyPrefix = []byte{0x06} // prefix for each key to a candidate - DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond + DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond ) -// XXX remove beggining word get from all these keys -// GetCandidateKey - get the key for the candidate with address +// get the key for the candidate with address func GetCandidateKey(addr sdk.Address) []byte { - return append(CandidateKeyPrefix, addr.Bytes()...) + return append(CandidatesKey, addr.Bytes()...) } -// GetValidatorKey - get the key for the validator used in the power-store +// get the key for the validator used in the power-store func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { - b, _ := cdc.MarshalBinary(power) // TODO need to handle error here? - return append(ValidatorKeyPrefix, append(b, addr.Bytes()...)...) // TODO does this need prefix if its in its own store + b, err := cdc.MarshalBinary(power) + if err != nil { + panic(err) + } + return append(ValidatorsKey, append(b, addr.Bytes()...)...) } -// GetValidatorUpdatesKey - get the key for the validator used in the power-store -func GetValidatorUpdatesKey(addr sdk.Address) []byte { - return append(ValidatorUpdatesKeyPrefix, addr.Bytes()...) // TODO does this need prefix if its in its own store +// get the key for the accumulated update validators +func GetAccUpdateValidatorKey(addr sdk.Address) []byte { + return append(AccUpdateValidatorsKey, addr.Bytes()...) } -// GetDelegatorBondKey - get the key for delegator bond with candidate +// get the key for the accumulated update validators +func GetRecentValidatorKey(addr sdk.Address) []byte { + return append(RecentValidatorsKey, 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()...) } -// GetDelegatorBondKeyPrefix - get the prefix for a delegator for all candidates +// get the prefix for a delegator for all candidates func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte { res, err := cdc.MarshalBinary(&delegatorAddr) if err != nil { From c79263f3a88b06c444d06aa6e9f7305a55e15024 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 22 Mar 2018 23:26:47 +0100 Subject: [PATCH 42/54] borken validator keeper tests --- x/stake/keeper_test.go | 266 ++++++++++++++++++++--------------------- 1 file changed, 133 insertions(+), 133 deletions(-) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 181093d19b..77a214df82 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -15,160 +15,160 @@ import ( // XXX revive these tests but for the store update proceedure // XXX XXX XXX -//func TestUpdateVotingPower(t *testing.T) { -//assert := assert.New(t) -//store := initTestStore(t) -//params := GetParams(store) -//gs := GetPool(store) +func TestUpdateVotingPower(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) -//N := 5 -//actors := newAddrs(N) -//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + candidates := candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 100, 10, 1}) -//// test a basic change in voting power -//candidates[0].Assets = sdk.NewRat(500) -//candidates.updateVotingPower(store, p, params) -//assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + // test a basic change in voting power + candidates[0].Assets = sdk.NewRat(500) + candidates.updateVotingPower(store, p, params) + keeper.updaate + assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) -//// test a swap in voting power -//candidates[1].Assets = sdk.NewRat(600) -//candidates.updateVotingPower(store, p, params) -//assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) -//assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) + // test a swap in voting power + candidates[1].Assets = sdk.NewRat(600) + candidates.updateVotingPower(store, p, params) + assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) + assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) -//// test the max validators term -//params.MaxValidators = 4 -//setParams(store, params) -//candidates.updateVotingPower(store, p, params) -//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) -//} + // test the max validators term + params.MaxValidators = 4 + setParams(store, params) + candidates.updateVotingPower(store, p, params) + assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) +} -//func TestValidatorsChanged(t *testing.T) { -//require := require.New(t) +func TestValidatorsChanged(t *testing.T) { + require := require.New(t) -//v1 := (&Candidate{PubKey: pks[0], VotingPower: sdk.NewRat(10)}).validator() -//v2 := (&Candidate{PubKey: pks[1], VotingPower: sdk.NewRat(10)}).validator() -//v3 := (&Candidate{PubKey: pks[2], VotingPower: sdk.NewRat(10)}).validator() -//v4 := (&Candidate{PubKey: pks[3], VotingPower: sdk.NewRat(10)}).validator() -//v5 := (&Candidate{PubKey: pks[4], VotingPower: sdk.NewRat(10)}).validator() + v1 := (&Candidate{PubKey: pks[0], VotingPower: sdk.NewRat(10)}).validator() + v2 := (&Candidate{PubKey: pks[1], VotingPower: sdk.NewRat(10)}).validator() + v3 := (&Candidate{PubKey: pks[2], VotingPower: sdk.NewRat(10)}).validator() + v4 := (&Candidate{PubKey: pks[3], VotingPower: sdk.NewRat(10)}).validator() + v5 := (&Candidate{PubKey: pks[4], VotingPower: sdk.NewRat(10)}).validator() -//// test from nothing to something -//vs1 := []Validator{} -//vs2 := []Validator{v1, v2} -//changed := vs1.validatorsUpdated(vs2) -//require.Equal(2, len(changed)) -//testChange(t, vs2[0], changed[0]) -//testChange(t, vs2[1], changed[1]) + // test from nothing to something + vs1 := []Validator{} + vs2 := []Validator{v1, v2} + changed := vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testChange(t, vs2[0], changed[0]) + testChange(t, vs2[1], changed[1]) -//// test from something to nothing -//vs1 = []Validator{v1, v2} -//vs2 = []Validator{} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(2, len(changed)) -//testRemove(t, vs1[0], changed[0]) -//testRemove(t, vs1[1], changed[1]) + // test from something to nothing + vs1 = []Validator{v1, v2} + vs2 = []Validator{} + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testRemove(t, vs1[0], changed[0]) + testRemove(t, vs1[1], changed[1]) -//// test identical -//vs1 = []Validator{v1, v2, v4} -//vs2 = []Validator{v1, v2, v4} -//changed = vs1.validatorsUpdated(vs2) -//require.ZeroRat(len(changed)) + // test identical + vs1 = []Validator{v1, v2, v4} + vs2 = []Validator{v1, v2, v4} + changed = vs1.validatorsUpdated(vs2) + require.ZeroRat(len(changed)) -//// test single value change -//vs2[2].VotingPower = sdk.OneRat -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testChange(t, vs2[2], changed[0]) + // test single value change + vs2[2].VotingPower = sdk.OneRat + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[2], changed[0]) -//// test multiple value change -//vs2[0].VotingPower = sdk.NewRat(11) -//vs2[2].VotingPower = sdk.NewRat(5) -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(2, len(changed)) -//testChange(t, vs2[0], changed[0]) -//testChange(t, vs2[2], changed[1]) + // test multiple value change + vs2[0].VotingPower = sdk.NewRat(11) + vs2[2].VotingPower = sdk.NewRat(5) + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testChange(t, vs2[0], changed[0]) + testChange(t, vs2[2], changed[1]) -//// test validator added at the beginning -//vs1 = []Validator{v2, v4} -//vs2 = []Validator{v2, v4, v1} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testChange(t, vs2[0], changed[0]) + // test validator added at the beginning + vs1 = []Validator{v2, v4} + vs2 = []Validator{v2, v4, v1} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[0], changed[0]) -//// test validator added in the middle -//vs1 = []Validator{v1, v2, v4} -//vs2 = []Validator{v3, v1, v4, v2} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testChange(t, vs2[2], changed[0]) + // test validator added in the middle + vs1 = []Validator{v1, v2, v4} + vs2 = []Validator{v3, v1, v4, v2} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testChange(t, vs2[2], changed[0]) -//// test validator added at the end -//vs2 = []Validator{v1, v2, v4, v5} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) //testChange(t, vs2[3], changed[0]) //// test multiple validators added //vs2 = []Validator{v1, v2, v3, v4, v5} //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[2], changed[0]) //testChange(t, vs2[4], changed[1]) //// test validator removed at the beginning //vs2 = []Validator{v2, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[0], changed[0]) //// test validator removed in the middle //vs2 = []Validator{v1, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[1], changed[0]) //// test validator removed at the end -//vs2 = []Validator{v1, v2} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(1, len(changed)) -//testRemove(t, vs1[2], changed[0]) + // test validator added at the end + vs2 = []Validator{v1, v2, v4, v5} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) //testChange(t, vs2[3], changed[0]) //// test multiple validators added //vs2 = []Validator{v1, v2, v3, v4, v5} //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[2], changed[0]) //testChange(t, vs2[4], changed[1]) //// test validator removed at the beginning //vs2 = []Validator{v2, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[0], changed[0]) //// test validator removed in the middle //vs2 = []Validator{v1, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[1], changed[0]) //// test validator removed at the end + vs2 = []Validator{v1, v2} + changed = vs1.validatorsUpdated(vs2) + require.Equal(1, len(changed)) + testRemove(t, vs1[2], changed[0]) -//// test multiple validators removed -//vs2 = []Validator{v1} -//changed = vs1.validatorsUpdated(vs2) -//require.Equal(2, len(changed)) -//testRemove(t, vs1[1], changed[0]) -//testRemove(t, vs1[2], changed[1]) + // test multiple validators removed + vs2 = []Validator{v1} + changed = vs1.validatorsUpdated(vs2) + require.Equal(2, len(changed)) + testRemove(t, vs1[1], changed[0]) + testRemove(t, vs1[2], changed[1]) -//// test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := GetParams(store) //gs := GetPool(store) //N := 5 -//actors := newAddrs(N) -//candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) -//for _, c := range candidates { -//setCandidate(store, c) -//} + // test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := GetParams(store) //gs := GetPool(store) //N := 5 + actors := newAddrs(N) + candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) + for _, c := range candidates { + setCandidate(store, c) + } -//// they should all already be validators -//change, err := UpdateValidatorSet(store, p, params) -//require.Nil(err) -//require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 + // they should all already be validators + change, err := UpdateValidatorSet(store, p, params) + require.Nil(err) + require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 -//// test the max value and test again -//params.MaxValidators = 4 -//setParams(store, params) -//change, err = UpdateValidatorSet(store, p, params) -//require.Nil(err) -//require.Equal(1, len(change), "%v", change) -//testRemove(t, candidates[4].validator(), change[0]) -//candidates = GetCandidates(store) -//assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) + // test the max value and test again + params.MaxValidators = 4 + setParams(store, params) + change, err = UpdateValidatorSet(store, p, params) + require.Nil(err) + require.Equal(1, len(change), "%v", change) + testRemove(t, candidates[4].validator(), change[0]) + candidates = GetCandidates(store) + assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) -//// mess with the power's of the candidates and test -//candidates[0].Assets = sdk.NewRat(10) -//candidates[1].Assets = sdk.NewRat(600) -//candidates[2].Assets = sdk.NewRat(1000) -//candidates[3].Assets = sdk.OneRat -//candidates[4].Assets = sdk.NewRat(10) -//for _, c := range candidates { -//setCandidate(store, c) -//} -//change, err = UpdateValidatorSet(store, p, params) -//require.Nil(err) -//require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed -//candidates = GetCandidates(store) -//testChange(t, candidates[0].validator(), change[0]) -//testChange(t, candidates[1].validator(), change[1]) -//testChange(t, candidates[2].validator(), change[2]) -//testRemove(t, candidates[3].validator(), change[3]) -//testChange(t, candidates[4].validator(), change[4]) -//} + // mess with the power's of the candidates and test + candidates[0].Assets = sdk.NewRat(10) + candidates[1].Assets = sdk.NewRat(600) + candidates[2].Assets = sdk.NewRat(1000) + candidates[3].Assets = sdk.OneRat + candidates[4].Assets = sdk.NewRat(10) + for _, c := range candidates { + setCandidate(store, c) + } + change, err = UpdateValidatorSet(store, p, params) + require.Nil(err) + require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed + candidates = GetCandidates(store) + testChange(t, candidates[0].validator(), change[0]) + testChange(t, candidates[1].validator(), change[1]) + testChange(t, candidates[2].validator(), change[2]) + testRemove(t, candidates[3].validator(), change[3]) + testChange(t, candidates[4].validator(), change[4]) +} -//func TestGetValidators(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 0, 0, 0}) +// XXX BROKEN TEST +func TestGetValidators(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := keeper.GetParams(ctx) + params.MaxValidators = 2 + keeper.setParams(ctx, params) + candidatesFromAddrs(ctx, keeper, addrs, []int64{0, 0, 0, 400, 200, 0}) // XXX rearrange these something messed is happenning! -//validators := keeper.GetValidators(ctx, 2) -//require.Equal(t, 2, len(validators)) -//assert.Equal(t, addrs[0], validators[0].Address) -//assert.Equal(t, addrs[1], validators[1].Address) -//} + validators := keeper.GetValidators(ctx) + require.Equal(t, 2, len(validators)) + assert.Equal(t, addrs[0], validators[0].Address, "%v", validators) + assert.Equal(t, addrs[1], validators[1].Address, "%v", validators) +} var ( addrDel1 = addrs[0] From 59562cd86493e4e718d000c8386d28a5a81bdc71 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 23 Mar 2018 16:55:50 +0100 Subject: [PATCH 43/54] remove term voting power from candidates --- x/stake/keeper.go | 10 ++--- x/stake/keeper_test.go | 86 +++++++++++++++++++++++------------------- x/stake/test_common.go | 33 +--------------- x/stake/types.go | 16 ++++---- 4 files changed, 60 insertions(+), 85 deletions(-) diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 7f82d747a8..1caaeb63bc 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -81,7 +81,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { store.Set(GetCandidateKey(candidate.Address), bz) // mashal the new validator record - validator := Validator{address, candidate.VotingPower} + validator := Validator{address, candidate.Assets} bz, err = k.cdc.MarshalBinary(validator) if err != nil { panic(err) @@ -89,7 +89,7 @@ func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) { // update the list ordered by voting power if oldFound { - store.Delete(GetValidatorKey(address, oldCandidate.VotingPower, k.cdc)) + store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc)) } store.Set(GetValidatorKey(address, validator.VotingPower, k.cdc), bz) @@ -124,14 +124,14 @@ func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) { } store.Set(GetAccUpdateValidatorKey(address), bz) store.Delete(GetRecentValidatorKey(address)) - store.Delete(GetValidatorKey(address, oldCandidate.VotingPower, k.cdc)) + store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc)) } //___________________________________________________________________________ // get the most recent updated validator set from the Candidates. These bonds -// are already sorted by VotingPower from the UpdateVotingPower function which -// is the only function which is to modify the VotingPower +// are already sorted by Assets from the UpdateVotingPower function which +// is the only function which is to modify the Assets // this function also updaates the most recent validators saved in store func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { store := ctx.KVStore(k.storeKey) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 77a214df82..911b7036c3 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -11,19 +11,60 @@ import ( "github.com/stretchr/testify/require" ) -// XXX XXX XXX -// XXX revive these tests but for the store update proceedure -// XXX XXX XXX +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), + } + 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), + } +) func TestUpdateVotingPower(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) - candidates := candidatesFromAddrs(ctx, keeper, addrs, []int64{400, 200, 100, 10, 1}) + // initialize some candidates into the state + amts := []int64{400, 200, 100, 10, 1} + candidates := make([]Candidate, 5) + for i := 0; i < len(5); i++ { + c := Candidate{ + Status: Unbonded, + PubKey: pks[i], + Address: addrs[i], + Assets: sdk.NewRat(amts[i]), + Liabilities: sdk.NewRat(amts[i]), + VotingPower: sdk.NewRat(amts[i]), + } + keeper.setCandidate(ctx, c) + candidate[i] = c + } // test a basic change in voting power candidates[0].Assets = sdk.NewRat(500) - candidates.updateVotingPower(store, p, params) - keeper.updaate + keeper.setCandidate(ctx, candidate[0]) + validators + assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) // test a swap in voting power @@ -170,39 +211,6 @@ func TestGetValidators(t *testing.T) { assert.Equal(t, addrs[1], validators[1].Address, "%v", validators) } -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), - VotingPower: sdk.ZeroRat, - } - candidate2 = Candidate{ - Address: addrVal2, - PubKey: pk2, - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), - VotingPower: sdk.ZeroRat, - } - candidate3 = Candidate{ - Address: addrVal3, - PubKey: pk3, - Assets: sdk.NewRat(9), - Liabilities: sdk.NewRat(9), - VotingPower: sdk.ZeroRat, - } -) - // XXX expand to include both liabilities and assets use/test all candidate1 fields func TestCandidate(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 96923872cd..b836194c93 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -148,8 +148,7 @@ var addrs = []sdk.Address{ testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169"), } -// NOTE: PubKey is supposed to be the binaryBytes of the crypto.PubKey -// instead this is just being set the address here for testing purposes +// XXX TODO remove this dep func candidatesFromAddrs(ctx sdk.Context, keeper Keeper, addrs []crypto.Address, amts []int64) { for i := 0; i < len(amts); i++ { c := Candidate{ @@ -158,37 +157,7 @@ func candidatesFromAddrs(ctx sdk.Context, keeper Keeper, addrs []crypto.Address, Address: addrs[i], Assets: sdk.NewRat(amts[i]), Liabilities: sdk.NewRat(amts[i]), - VotingPower: sdk.NewRat(amts[i]), } keeper.setCandidate(ctx, c) } } - -func candidatesFromAddrsEmpty(addrs []crypto.Address) (candidates Candidates) { - for i := 0; i < len(addrs); i++ { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.ZeroRat, - Liabilities: sdk.ZeroRat, - VotingPower: sdk.ZeroRat, - } - candidates = append(candidates, c) - } - return -} - -//// helper function test if Candidate is changed asabci.Validator -//func testChange(t *testing.T, val Validator, chg *abci.Validator) { -//assert := assert.New(t) -//assert.Equal(val.PubKey.Bytes(), chg.PubKey) -//assert.Equal(val.VotingPower.Evaluate(), chg.Power) -//} - -//// helper function test if Candidate is removed as abci.Validator -//func testRemove(t *testing.T, val Validator, chg *abci.Validator) { -//assert := assert.New(t) -//assert.Equal(val.PubKey.Bytes(), chg.PubKey) -//assert.Equal(int64(0), chg.Power) -//} diff --git a/x/stake/types.go b/x/stake/types.go index cdf8ece066..0521bece53 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -98,13 +98,12 @@ const ( // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Candidate struct { - Status CandidateStatus `json:"status"` // Bonded status - Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here - PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate - Assets sdk.Rat `json:"assets"` // total shares of a global hold pools - Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators - VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator - Description Description `json:"description"` // Description terms for the candidate + Status CandidateStatus `json:"status"` // Bonded status + Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here + PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate + Assets sdk.Rat `json:"assets"` // total shares of a global hold pools + Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators + Description Description `json:"description"` // Description terms for the candidate } // Description - description fields for a candidate @@ -123,7 +122,6 @@ func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Descrip PubKey: pubKey, Assets: sdk.ZeroRat, Liabilities: sdk.ZeroRat, - VotingPower: sdk.ZeroRat, Description: description, } } @@ -141,7 +139,7 @@ func (c Candidate) delegatorShareExRate() sdk.Rat { func (c Candidate) validator() Validator { return Validator{ Address: c.Address, // XXX !!! - VotingPower: c.VotingPower, + VotingPower: c.Assets, } } From 4e45d7368bdffe3f5535bf76bba322b37175f2fd Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 23 Mar 2018 17:15:41 +0100 Subject: [PATCH 44/54] rebase fixes --- types/errors.go | 3 --- x/stake/handler.go | 2 +- x/stake/keeper_test.go | 3 +-- x/stake/msg.go | 4 ++-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/types/errors.go b/types/errors.go index da4519725f..1115d39376 100644 --- a/types/errors.go +++ b/types/errors.go @@ -108,9 +108,6 @@ func ErrInsufficientCoins(msg string) Error { func ErrInvalidCoins(msg string) Error { return newError(CodeInvalidCoins, msg) } -func ErrInvalidCoins(coins Coins) Error { - return newError(CodeInvalidCoins, coins.String()) -} //---------------------------------------- // Error & sdkError diff --git a/x/stake/handler.go b/x/stake/handler.go index 091268b440..bc3f4b9d72 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -64,7 +64,7 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { case MsgUnbond: return k.handleMsgUnbond(ctx, msg) default: - return sdk.ErrTxParse("invalid message parse in staking module").Result() + return sdk.ErrTxDecode("invalid message parse in staking module").Result() } } } diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 911b7036c3..9a122bb0f1 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -47,14 +47,13 @@ func TestUpdateVotingPower(t *testing.T) { // initialize some candidates into the state amts := []int64{400, 200, 100, 10, 1} candidates := make([]Candidate, 5) - for i := 0; i < len(5); i++ { + for i := 0; i < 5; i++ { c := Candidate{ Status: Unbonded, PubKey: pks[i], Address: addrs[i], Assets: sdk.NewRat(amts[i]), Liabilities: sdk.NewRat(amts[i]), - VotingPower: sdk.NewRat(amts[i]), } keeper.setCandidate(ctx, c) candidate[i] = c diff --git a/x/stake/msg.go b/x/stake/msg.go index 3b94b6bcb2..7a75e1b8fc 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -57,7 +57,7 @@ func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { return ErrCandidateEmpty() } if msg.Bond.Amount <= 0 { - return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}) + return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) } empty := Description{} if msg.Description == empty { @@ -153,7 +153,7 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { return ErrBadCandidateAddr() } if msg.Bond.Amount <= 0 { - return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}) + return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) } return nil } From c1cb53ca589b71854539ea8f0ce7997e6824fdb1 Mon Sep 17 00:00:00 2001 From: Sunny Aggarwal Date: Sun, 25 Mar 2018 06:06:28 +0200 Subject: [PATCH 45/54] msg_tests --- Gopkg.lock | 5 +- x/stake/errors.go | 4 +- x/stake/msg.go | 21 +++++-- x/stake/msg_test.go | 131 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 131 insertions(+), 30 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 46cb95893f..57f7aaa793 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -145,7 +145,10 @@ [[projects]] name = "github.com/magiconair/properties" - packages = ["."] + packages = [ + ".", + "assert" + ] revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" version = "v1.7.6" diff --git a/x/stake/errors.go b/x/stake/errors.go index a8038a3d81..bd1992959e 100644 --- a/x/stake/errors.go +++ b/x/stake/errors.go @@ -52,10 +52,10 @@ func ErrCandidateEmpty() sdk.Error { return newError(CodeInvalidValidator, "Cannot bond to an empty candidate") } func ErrBadBondingDenom() sdk.Error { - return newError(CodeInvalidValidator, "Invalid coin denomination") + return newError(CodeInvalidBond, "Invalid coin denomination") } func ErrBadBondingAmount() sdk.Error { - return newError(CodeInvalidValidator, "Amount must be > 0") + return newError(CodeInvalidBond, "Amount must be > 0") } func ErrNoBondingAcct() sdk.Error { return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair") diff --git a/x/stake/msg.go b/x/stake/msg.go index 7a75e1b8fc..1305e5f01f 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -9,7 +9,9 @@ import ( ) // name to idetify transaction types -var MsgType = "stake" +const MsgType = "stake" + +const StakingToken = "fermion" //Verify interface at compile time var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{} @@ -56,8 +58,12 @@ func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error { if msg.CandidateAddr == nil { return ErrCandidateEmpty() } + if msg.Bond.Denom != StakingToken { + return ErrBadBondingDenom() + } if msg.Bond.Amount <= 0 { - return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) + return ErrBadBondingAmount() + // return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) } empty := Description{} if msg.Description == empty { @@ -152,8 +158,12 @@ func (msg MsgDelegate) ValidateBasic() sdk.Error { if msg.CandidateAddr == nil { return ErrBadCandidateAddr() } + if msg.Bond.Denom != StakingToken { + return ErrBadBondingDenom() + } if msg.Bond.Amount <= 0 { - return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) + return ErrBadBondingAmount() + // return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String()) } return nil } @@ -201,10 +211,13 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error { return ErrBadCandidateAddr() } if msg.Shares != "MAX" { - _, err := sdk.NewRatFromDecimal(msg.Shares) + rat, err := sdk.NewRatFromDecimal(msg.Shares) if err != nil { return ErrBadShares() } + if rat.IsZero() || rat.LT(sdk.ZeroRat) { + return ErrBadShares() + } } return nil } diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 476995d659..4b46e49b93 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -1,12 +1,21 @@ package stake import ( + "testing" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/magiconair/properties/assert" + crypto "github.com/tendermint/go-crypto" ) var ( - validator = []byte("addressvalidator1") - empty sdk.Address + addr1 = []byte("addr1") + addr2 = []byte("addr2") + addr3 = []byte("addr3") + emptyAddr sdk.Address + + pubkey1 = crypto.GenPrivKeyEd25519().PubKey() + emptyPubkey crypto.PubKey coinPos = sdk.Coin{"fermion", 1000} coinZero = sdk.Coin{"fermion", 0} @@ -16,42 +25,118 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) -// TODO SUNNY make validate basic tests for each msg.. take these commented tests as inspiration -/* -func TestMsgAddrValidateBasic(t *testing.T) { +func TestMsgDeclareCandidacy(t *testing.T) { tests := []struct { - name string - address sdk.Address - wantErr bool + name string + moniker string + identity string + website string + details string + candidateAddr sdk.Address + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool }{ - {"basic good", addrs[0], false}, - {"empty delegator", sdk.Address{}, true}, + {"basic good", "a", "b", "c", "d", addr1, pubkey1, coinPos, true}, + {"partial description", "", "", "c", "", addr1, pubkey1, coinPos, true}, + {"empty description", "", "", "", "", addr1, pubkey1, coinPos, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, pubkey1, coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addr1, pubkey1, coinZero, false}, + {"negative bond", "a", "b", "c", "d", addr1, pubkey1, coinNeg, false}, + {"negative bond", "a", "b", "c", "d", addr1, pubkey1, coinNeg, false}, + {"wrong staking token", "a", "b", "c", "d", addr1, pubkey1, coinPosNotAtoms, false}, } for _, tc := range tests { - tx := NewMsgAddr(tc.address) - assert.Equal(t, tc.wantErr, tx.ValidateBasic() != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, tx.ValidateBasic()) + description := Description{ + Moniker: tc.moniker, + Identity: tc.identity, + Website: tc.website, + Details: tc.details, + } + msg := NewMsgDeclareCandidacy(tc.candidateAddr, tc.pubkey, tc.bond, description) + assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, + "test: ", tc.name) } } -func TestValidateCoin(t *testing.T) { +func TestMsgEditCandidacy(t *testing.T) { tests := []struct { - name string - coin sdk.Coin - wantErr bool + name string + moniker string + identity string + website string + details string + candidateAddr sdk.Address + expectPass bool }{ - {"basic good", coinPos, false}, - {"zero coin", coinZero, true}, - {"neg coin", coinNeg, true}, + {"basic good", "a", "b", "c", "d", addr1, true}, + {"partial description", "", "", "c", "", addr1, true}, + {"empty description", "", "", "", "", addr1, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, false}, } for _, tc := range tests { - assert.Equal(t, tc.wantErr, validateCoin(tc.coin) != nil, - "test: %v, tx.ValidateBasic: %v", tc.name, validateCoin(tc.coin)) + description := Description{ + Moniker: tc.moniker, + Identity: tc.identity, + Website: tc.website, + Details: tc.details, + } + msg := NewMsgEditCandidacy(tc.candidateAddr, description) + assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, + "test: ", tc.name) + } +} + +func TestMsgDelegate(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + candidateAddr sdk.Address + bond sdk.Coin + expectPass bool + }{ + {"basic good", addr1, addr2, coinPos, true}, + {"self bond", addr1, addr1, coinPos, true}, + {"empty delegator", emptyAddr, addr1, coinPos, false}, + {"empty candidate", addr1, emptyAddr, coinPos, false}, + {"empty bond", addr1, addr2, coinZero, false}, + {"negative bond", addr1, addr2, coinNeg, false}, + {"wrong staking token", addr1, addr2, coinPosNotAtoms, false}, + } + + for _, tc := range tests { + msg := NewMsgDelegate(tc.delegatorAddr, tc.candidateAddr, tc.bond) + assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, + "test: ", tc.name) + } +} + +func TestMsgUnbond(t *testing.T) { + tests := []struct { + name string + delegatorAddr sdk.Address + candidateAddr sdk.Address + shares string + expectPass bool + }{ + {"max unbond", addr1, addr2, "MAX", true}, + {"decimal unbond", addr1, addr2, "0.1", true}, + {"negative decimal unbond", addr1, addr2, "-0.1", false}, + {"zero unbond", addr1, addr2, "0.0", false}, + {"invalid decimal", addr1, addr1, "sunny", false}, + {"empty delegator", emptyAddr, addr1, "0.1", false}, + {"empty candidate", addr1, emptyAddr, "0.1", false}, + } + + for _, tc := range tests { + msg := NewMsgUnbond(tc.delegatorAddr, tc.candidateAddr, tc.shares) + assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, + "test: ", tc.name) } } -*/ // TODO introduce with go-amino //func TestSerializeMsg(t *testing.T) { From 1df21e0fb6da11cf2d6a225f6d43de161b7529b1 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 26 Mar 2018 16:48:15 +0200 Subject: [PATCH 46/54] validatebasic tests cleanup --- x/stake/keeper_test.go | 3 +- x/stake/msg.go | 4 ++ x/stake/msg_test.go | 133 +++++++++++++++++++---------------------- x/stake/test_common.go | 61 ++++++++++--------- x/stake/types.go | 25 +++++--- 5 files changed, 119 insertions(+), 107 deletions(-) diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 9a122bb0f1..1f44a3ea9e 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -41,6 +41,7 @@ var ( } ) +/* func TestUpdateVotingPower(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) @@ -195,6 +196,7 @@ func TestValidatorsChanged(t *testing.T) { testRemove(t, candidates[3].validator(), change[3]) testChange(t, candidates[4].validator(), change[4]) } +*/ // XXX BROKEN TEST func TestGetValidators(t *testing.T) { @@ -220,7 +222,6 @@ func TestCandidate(t *testing.T) { bytes.Equal(c1.Address, c2.Address) && c1.Assets.Equal(c2.Assets) && c1.Liabilities.Equal(c2.Liabilities) && - c1.VotingPower.Equal(c2.VotingPower) && c1.Description == c2.Description } diff --git a/x/stake/msg.go b/x/stake/msg.go index 1305e5f01f..28a2edf79d 100644 --- a/x/stake/msg.go +++ b/x/stake/msg.go @@ -11,6 +11,10 @@ import ( // name to idetify transaction types const MsgType = "stake" +// XXX remove: think it makes more sense belonging with the Params so we can +// initialize at genesis - to allow for the same tests we should should make +// the ValidateBasic() function a return from an initializable function +// ValidateBasic(bondDenom string) function const StakingToken = "fermion" //Verify interface at compile time diff --git a/x/stake/msg_test.go b/x/stake/msg_test.go index 4b46e49b93..b3e2bbe3a2 100644 --- a/x/stake/msg_test.go +++ b/x/stake/msg_test.go @@ -3,20 +3,13 @@ package stake import ( "testing" + "github.com/stretchr/testify/assert" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/magiconair/properties/assert" crypto "github.com/tendermint/go-crypto" ) var ( - addr1 = []byte("addr1") - addr2 = []byte("addr2") - addr3 = []byte("addr3") - emptyAddr sdk.Address - - pubkey1 = crypto.GenPrivKeyEd25519().PubKey() - emptyPubkey crypto.PubKey - coinPos = sdk.Coin{"fermion", 1000} coinZero = sdk.Coin{"fermion", 0} coinNeg = sdk.Coin{"fermion", -10000} @@ -25,71 +18,62 @@ var ( coinNegNotAtoms = sdk.Coin{"foo", -10000} ) +// test ValidateBasic for MsgDeclareCandidacy func TestMsgDeclareCandidacy(t *testing.T) { tests := []struct { - name string - moniker string - identity string - website string - details string - candidateAddr sdk.Address - pubkey crypto.PubKey - bond sdk.Coin - expectPass bool + name, moniker, identity, website, details string + candidateAddr sdk.Address + pubkey crypto.PubKey + bond sdk.Coin + expectPass bool }{ - {"basic good", "a", "b", "c", "d", addr1, pubkey1, coinPos, true}, - {"partial description", "", "", "c", "", addr1, pubkey1, coinPos, true}, - {"empty description", "", "", "", "", addr1, pubkey1, coinPos, false}, - {"empty address", "a", "b", "c", "d", emptyAddr, pubkey1, coinPos, false}, - {"empty pubkey", "a", "b", "c", "d", addr1, emptyPubkey, coinPos, true}, - {"empty bond", "a", "b", "c", "d", addr1, pubkey1, coinZero, false}, - {"negative bond", "a", "b", "c", "d", addr1, pubkey1, coinNeg, false}, - {"negative bond", "a", "b", "c", "d", addr1, pubkey1, coinNeg, false}, - {"wrong staking token", "a", "b", "c", "d", addr1, pubkey1, coinPosNotAtoms, false}, + {"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true}, + {"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true}, + {"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false}, + {"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false}, + {"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true}, + {"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false}, + {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, + {"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false}, + {"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false}, } for _, tc := range tests { - description := Description{ - Moniker: tc.moniker, - Identity: tc.identity, - Website: tc.website, - Details: tc.details, - } + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) msg := NewMsgDeclareCandidacy(tc.candidateAddr, tc.pubkey, tc.bond, description) - assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, - "test: ", tc.name) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } } } +// test ValidateBasic for MsgEditCandidacy func TestMsgEditCandidacy(t *testing.T) { tests := []struct { - name string - moniker string - identity string - website string - details string - candidateAddr sdk.Address - expectPass bool + name, moniker, identity, website, details string + candidateAddr sdk.Address + expectPass bool }{ - {"basic good", "a", "b", "c", "d", addr1, true}, - {"partial description", "", "", "c", "", addr1, true}, - {"empty description", "", "", "", "", addr1, false}, + {"basic good", "a", "b", "c", "d", addrs[0], true}, + {"partial description", "", "", "c", "", addrs[0], true}, + {"empty description", "", "", "", "", addrs[0], false}, {"empty address", "a", "b", "c", "d", emptyAddr, false}, } for _, tc := range tests { - description := Description{ - Moniker: tc.moniker, - Identity: tc.identity, - Website: tc.website, - Details: tc.details, - } + description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details) msg := NewMsgEditCandidacy(tc.candidateAddr, description) - assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, - "test: ", tc.name) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } } } +// test ValidateBasic for MsgDelegate func TestMsgDelegate(t *testing.T) { tests := []struct { name string @@ -98,22 +82,26 @@ func TestMsgDelegate(t *testing.T) { bond sdk.Coin expectPass bool }{ - {"basic good", addr1, addr2, coinPos, true}, - {"self bond", addr1, addr1, coinPos, true}, - {"empty delegator", emptyAddr, addr1, coinPos, false}, - {"empty candidate", addr1, emptyAddr, coinPos, false}, - {"empty bond", addr1, addr2, coinZero, false}, - {"negative bond", addr1, addr2, coinNeg, false}, - {"wrong staking token", addr1, addr2, coinPosNotAtoms, false}, + {"basic good", addrs[0], addrs[1], coinPos, true}, + {"self bond", addrs[0], addrs[0], coinPos, true}, + {"empty delegator", emptyAddr, addrs[0], coinPos, false}, + {"empty candidate", addrs[0], emptyAddr, coinPos, false}, + {"empty bond", addrs[0], addrs[1], coinZero, false}, + {"negative bond", addrs[0], addrs[1], coinNeg, false}, + {"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false}, } for _, tc := range tests { msg := NewMsgDelegate(tc.delegatorAddr, tc.candidateAddr, tc.bond) - assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, - "test: ", tc.name) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } } } +// test ValidateBasic for MsgUnbond func TestMsgUnbond(t *testing.T) { tests := []struct { name string @@ -122,19 +110,22 @@ func TestMsgUnbond(t *testing.T) { shares string expectPass bool }{ - {"max unbond", addr1, addr2, "MAX", true}, - {"decimal unbond", addr1, addr2, "0.1", true}, - {"negative decimal unbond", addr1, addr2, "-0.1", false}, - {"zero unbond", addr1, addr2, "0.0", false}, - {"invalid decimal", addr1, addr1, "sunny", false}, - {"empty delegator", emptyAddr, addr1, "0.1", false}, - {"empty candidate", addr1, emptyAddr, "0.1", false}, + {"max unbond", addrs[0], addrs[1], "MAX", true}, + {"decimal unbond", addrs[0], addrs[1], "0.1", true}, + {"negative decimal unbond", addrs[0], addrs[1], "-0.1", false}, + {"zero unbond", addrs[0], addrs[1], "0.0", false}, + {"invalid decimal", addrs[0], addrs[0], "sunny", false}, + {"empty delegator", emptyAddr, addrs[0], "0.1", false}, + {"empty candidate", addrs[0], emptyAddr, "0.1", false}, } for _, tc := range tests { msg := NewMsgUnbond(tc.delegatorAddr, tc.candidateAddr, tc.shares) - assert.Equal(t, tc.expectPass, msg.ValidateBasic() == nil, - "test: ", tc.name) + if tc.expectPass { + assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name) + } else { + assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name) + } } } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index b836194c93..07f53b8315 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -19,6 +19,40 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" ) +// dummy addresses used for testing +var ( + addrs = []sdk.Address{ + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6163"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6164"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6165"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6166"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6167"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6168"), + testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169"), + } + + // dummy pubkeys used for testing + pks = []crypto.PubKey{ + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), + newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), + } + + emptyAddr sdk.Address + emptyPubkey crypto.PubKey +) + +// XXX reference the common declaration of this function func subspace(prefix []byte) (start, end []byte) { end = make([]byte, len(prefix)) copy(end, prefix) @@ -112,19 +146,6 @@ func newPubKey(pk string) (res crypto.PubKey) { return pkEd.Wrap() } -var pks = []crypto.PubKey{ - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"), - newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"), -} - // for incode address generation func testAddr(addr string) sdk.Address { res, err := sdk.GetAddress(addr) @@ -134,20 +155,6 @@ func testAddr(addr string) sdk.Address { return res } -// dummy addresses used for testing -var addrs = []sdk.Address{ - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6163"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6164"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6165"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6166"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6167"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6168"), - testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169"), -} - // XXX TODO remove this dep func candidatesFromAddrs(ctx sdk.Context, keeper Keeper, addrs []crypto.Address, amts []int64) { for i := 0; i < len(amts); i++ { diff --git a/x/stake/types.go b/x/stake/types.go index 0521bece53..ba89c52884 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -106,14 +106,6 @@ type Candidate struct { Description Description `json:"description"` // Description terms for the candidate } -// Description - description fields for a candidate -type Description struct { - Moniker string `json:"moniker"` - Identity string `json:"identity"` - Website string `json:"website"` - Details string `json:"details"` -} - // NewCandidate - initialize a new candidate func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) Candidate { return Candidate{ @@ -126,6 +118,23 @@ func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Descrip } } +// Description - description fields for a candidate +type Description struct { + Moniker string `json:"moniker"` + Identity string `json:"identity"` + Website string `json:"website"` + Details string `json:"details"` +} + +func NewDescription(moniker, identity, website, details string) Description { + return Description{ + Moniker: moniker, + Identity: identity, + Website: website, + Details: details, + } +} + // get the exchange rate of global pool shares over delegator shares func (c Candidate) delegatorShareExRate() sdk.Rat { if c.Liabilities.IsZero() { From e25b78055bf5342698ee7e4eee8eb36bd386120f Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 26 Mar 2018 16:53:55 +0200 Subject: [PATCH 47/54] dep update ... ... --- Gopkg.lock | 5 +---- types/rational.go | 31 +++++++++++++++++++++---------- types/rational_test.go | 14 ++++++++++++++ x/stake/tick.go | 2 +- x/stake/types.go | 2 ++ 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 57f7aaa793..46cb95893f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -145,10 +145,7 @@ [[projects]] name = "github.com/magiconair/properties" - packages = [ - ".", - "assert" - ] + packages = ["."] revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" version = "v1.7.6" diff --git a/types/rational.go b/types/rational.go index b3a914659c..8ebee71407 100644 --- a/types/rational.go +++ b/types/rational.go @@ -1,6 +1,7 @@ package types import ( + "fmt" "math/big" "strconv" "strings" @@ -79,7 +80,7 @@ func NewRat(num int64, denom ...int64) Rat { } } -//NewFromDecimal - create a rational from decimal string or integer string +// create a rational from decimal string or integer string func NewRatFromDecimal(decimalStr string) (f Rat, err Error) { // first extract any negative symbol @@ -148,14 +149,16 @@ func (r Rat) Sub(r2 Rat) Rat { return ToRat(new(big.Rat).Sub(r.GetRat(), r2.G //func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction //func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } // Sub - subtraction -var zero = big.NewInt(0) -var one = big.NewInt(1) -var two = big.NewInt(2) -var five = big.NewInt(5) -var nFive = big.NewInt(-5) -var ten = big.NewInt(10) +var ( + zero = big.NewInt(0) + one = big.NewInt(1) + two = big.NewInt(2) + five = big.NewInt(5) + nFive = big.NewInt(-5) + ten = big.NewInt(10) +) -// EvaluateBig - evaluate the rational using bankers rounding +// evaluate the rational using bankers rounding func (r Rat) EvaluateBig() *big.Int { num := r.GetRat().Num() @@ -185,17 +188,25 @@ func (r Rat) EvaluateBig() *big.Int { return d } -// Evaluate - evaluate the rational using bankers rounding +// evaluate the rational using bankers rounding func (r Rat) Evaluate() int64 { return r.EvaluateBig().Int64() } -// Round - round Rat with the provided precisionFactor +// round Rat with the provided precisionFactor func (r Rat) Round(precisionFactor int64) Rat { rTen := ToRat(new(big.Rat).Mul(r.GetRat(), big.NewRat(precisionFactor, 1))) return ToRat(big.NewRat(rTen.Evaluate(), precisionFactor)) } +// TODO panic if negative or if totalDigits < len(initStr)??? +// evaluate as an integer and return left padded string +func (r Rat) ToLeftPadded(totalDigits int8) string { + intStr := r.EvaluateBig().String() + fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s` + return fmt.Sprintf(fcode, intStr) +} + //___________________________________________________________________________________ // Hack to just use json.Marshal for everything until diff --git a/types/rational_test.go b/types/rational_test.go index 8c85cda06e..c83af3917b 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -192,6 +192,20 @@ func TestRound(t *testing.T) { } } +func TestToLeftPaddedString(t *testing.T) { + tests := []struct { + rat Rat + digits int8 + res string + }{ + {NewRat(100, 3), 8, "00000033"}, + } + + for _, tc := range tests { + assert.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits)) + } +} + //func TestZeroSerializationJSON(t *testing.T) { //r := NewRat(0, 1) //err := r.UnmarshalJSON([]byte(`"0/1"`)) diff --git a/x/stake/tick.go b/x/stake/tick.go index 47be614e07..6aa2da95db 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -37,7 +37,7 @@ func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) { func (k Keeper) processProvisions(ctx sdk.Context) { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx).Round(precision) //TODO make this number a const somewhere? + pool.Inflation = k.nextInflation(ctx).Round(precision) // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term diff --git a/x/stake/types.go b/x/stake/types.go index ba89c52884..2799e1d76b 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -16,6 +16,8 @@ type Params struct { BondDenom string `json:"bond_denom"` // bondable coin denomination } +// XXX do we want to allow for default params even or do we want to enforce that you +// need to be explicit about defining all params in genesis? func defaultParams() Params { return Params{ InflationRateChange: sdk.NewRat(13, 100), From e5199f0c7c4cfbf317504e56c0f87e01d3c6f4a6 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 02:17:11 +0200 Subject: [PATCH 48/54] fix validator keeper functionality, add testing --- types/rational_test.go | 7 +- x/stake/keeper.go | 2 +- x/stake/keeper_keys.go | 9 +- x/stake/keeper_test.go | 332 +++++++++++++++++++---------------------- x/stake/test_common.go | 14 -- 5 files changed, 163 insertions(+), 201 deletions(-) diff --git a/types/rational_test.go b/types/rational_test.go index c83af3917b..5af63e8ec9 100644 --- a/types/rational_test.go +++ b/types/rational_test.go @@ -192,15 +192,18 @@ func TestRound(t *testing.T) { } } -func TestToLeftPaddedString(t *testing.T) { +func TestToLeftPadded(t *testing.T) { tests := []struct { rat Rat digits int8 res string }{ {NewRat(100, 3), 8, "00000033"}, + {NewRat(1, 3), 8, "00000000"}, + {NewRat(100, 2), 8, "00000050"}, + {NewRat(1000, 3), 8, "00000333"}, + {NewRat(1000, 3), 12, "000000000333"}, } - for _, tc := range tests { assert.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits)) } diff --git a/x/stake/keeper.go b/x/stake/keeper.go index 1caaeb63bc..af2015fe81 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -141,7 +141,7 @@ func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) { // add the actual validator power sorted store maxVal := k.GetParams(ctx).MaxValidators - iterator := store.Iterator(subspace(ValidatorsKey)) //smallest to largest + iterator := store.ReverseIterator(subspace(ValidatorsKey)) //smallest to largest validators = make([]Validator, maxVal) i := 0 for ; ; i++ { diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index ae0928bcc6..051994456e 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -20,6 +20,8 @@ var ( DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond ) +const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch + // get the key for the candidate with address func GetCandidateKey(addr sdk.Address) []byte { return append(CandidatesKey, addr.Bytes()...) @@ -27,11 +29,8 @@ func GetCandidateKey(addr sdk.Address) []byte { // get the key for the validator used in the power-store func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte { - b, err := cdc.MarshalBinary(power) - if err != nil { - panic(err) - } - return append(ValidatorsKey, append(b, addr.Bytes()...)...) + powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount)) + return append(ValidatorsKey, append(powerBytes, addr.Bytes()...)...) } // get the key for the accumulated update validators diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 1f44a3ea9e..6e7478957f 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -41,178 +41,7 @@ var ( } ) -/* -func TestUpdateVotingPower(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) - - // initialize some candidates into the state - amts := []int64{400, 200, 100, 10, 1} - candidates := make([]Candidate, 5) - for i := 0; i < 5; i++ { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.NewRat(amts[i]), - Liabilities: sdk.NewRat(amts[i]), - } - keeper.setCandidate(ctx, c) - candidate[i] = c - } - - // test a basic change in voting power - candidates[0].Assets = sdk.NewRat(500) - keeper.setCandidate(ctx, candidate[0]) - validators - - assert.Equal(int64(500), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) - - // test a swap in voting power - candidates[1].Assets = sdk.NewRat(600) - candidates.updateVotingPower(store, p, params) - assert.Equal(int64(600), candidates[0].VotingPower.Evaluate(), "%v", candidates[0]) - assert.Equal(int64(500), candidates[1].VotingPower.Evaluate(), "%v", candidates[1]) - - // test the max validators term - params.MaxValidators = 4 - setParams(store, params) - candidates.updateVotingPower(store, p, params) - assert.Equal(int64(0), candidates[4].VotingPower.Evaluate(), "%v", candidates[4]) -} - -func TestValidatorsChanged(t *testing.T) { - require := require.New(t) - - v1 := (&Candidate{PubKey: pks[0], VotingPower: sdk.NewRat(10)}).validator() - v2 := (&Candidate{PubKey: pks[1], VotingPower: sdk.NewRat(10)}).validator() - v3 := (&Candidate{PubKey: pks[2], VotingPower: sdk.NewRat(10)}).validator() - v4 := (&Candidate{PubKey: pks[3], VotingPower: sdk.NewRat(10)}).validator() - v5 := (&Candidate{PubKey: pks[4], VotingPower: sdk.NewRat(10)}).validator() - - // test from nothing to something - vs1 := []Validator{} - vs2 := []Validator{v1, v2} - changed := vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testChange(t, vs2[0], changed[0]) - testChange(t, vs2[1], changed[1]) - - // test from something to nothing - vs1 = []Validator{v1, v2} - vs2 = []Validator{} - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testRemove(t, vs1[0], changed[0]) - testRemove(t, vs1[1], changed[1]) - - // test identical - vs1 = []Validator{v1, v2, v4} - vs2 = []Validator{v1, v2, v4} - changed = vs1.validatorsUpdated(vs2) - require.ZeroRat(len(changed)) - - // test single value change - vs2[2].VotingPower = sdk.OneRat - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[2], changed[0]) - - // test multiple value change - vs2[0].VotingPower = sdk.NewRat(11) - vs2[2].VotingPower = sdk.NewRat(5) - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testChange(t, vs2[0], changed[0]) - testChange(t, vs2[2], changed[1]) - - // test validator added at the beginning - vs1 = []Validator{v2, v4} - vs2 = []Validator{v2, v4, v1} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[0], changed[0]) - - // test validator added in the middle - vs1 = []Validator{v1, v2, v4} - vs2 = []Validator{v3, v1, v4, v2} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testChange(t, vs2[2], changed[0]) - - // test validator added at the end - vs2 = []Validator{v1, v2, v4, v5} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) //testChange(t, vs2[3], changed[0]) //// test multiple validators added //vs2 = []Validator{v1, v2, v3, v4, v5} //changed = vs1.validatorsUpdated(vs2) //require.Equal(2, len(changed)) //testChange(t, vs2[2], changed[0]) //testChange(t, vs2[4], changed[1]) //// test validator removed at the beginning //vs2 = []Validator{v2, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[0], changed[0]) //// test validator removed in the middle //vs2 = []Validator{v1, v4} //changed = vs1.validatorsUpdated(vs2) //require.Equal(1, len(changed)) //testRemove(t, vs1[1], changed[0]) //// test validator removed at the end - vs2 = []Validator{v1, v2} - changed = vs1.validatorsUpdated(vs2) - require.Equal(1, len(changed)) - testRemove(t, vs1[2], changed[0]) - - // test multiple validators removed - vs2 = []Validator{v1} - changed = vs1.validatorsUpdated(vs2) - require.Equal(2, len(changed)) - testRemove(t, vs1[1], changed[0]) - testRemove(t, vs1[2], changed[1]) - - // test many sdk of changes //vs2 = []Validator{v1, v3, v4, v5} //vs2[2].VotingPower = sdk.NewRat(11) //changed = vs1.validatorsUpdated(vs2) //require.Equal(4, len(changed), "%v", changed) // change 1, remove 1, add 2 //testRemove(t, vs1[1], changed[0]) //testChange(t, vs2[1], changed[1]) //testChange(t, vs2[2], changed[2]) //testChange(t, vs2[3], changed[3]) //} //func TestUpdateValidatorSet(t *testing.T) { //assert, require := assert.New(t), require.New(t) //store := initTestStore(t) //params := GetParams(store) //gs := GetPool(store) //N := 5 - actors := newAddrs(N) - candidates := candidatesFromActors(actors, []int64{400, 200, 100, 10, 1}) - for _, c := range candidates { - setCandidate(store, c) - } - - // they should all already be validators - change, err := UpdateValidatorSet(store, p, params) - require.Nil(err) - require.Equal(0, len(change), "%v", change) // change 1, remove 1, add 2 - - // test the max value and test again - params.MaxValidators = 4 - setParams(store, params) - change, err = UpdateValidatorSet(store, p, params) - require.Nil(err) - require.Equal(1, len(change), "%v", change) - testRemove(t, candidates[4].validator(), change[0]) - candidates = GetCandidates(store) - assert.Equal(int64(0), candidates[4].VotingPower.Evaluate()) - - // mess with the power's of the candidates and test - candidates[0].Assets = sdk.NewRat(10) - candidates[1].Assets = sdk.NewRat(600) - candidates[2].Assets = sdk.NewRat(1000) - candidates[3].Assets = sdk.OneRat - candidates[4].Assets = sdk.NewRat(10) - for _, c := range candidates { - setCandidate(store, c) - } - change, err = UpdateValidatorSet(store, p, params) - require.Nil(err) - require.Equal(5, len(change), "%v", change) // 3 changed, 1 added, 1 removed - candidates = GetCandidates(store) - testChange(t, candidates[0].validator(), change[0]) - testChange(t, candidates[1].validator(), change[1]) - testChange(t, candidates[2].validator(), change[2]) - testRemove(t, candidates[3].validator(), change[3]) - testChange(t, candidates[4].validator(), change[4]) -} -*/ - -// XXX BROKEN TEST -func TestGetValidators(t *testing.T) { - ctx, _, keeper := createTestInput(t, nil, false, 0) - params := keeper.GetParams(ctx) - params.MaxValidators = 2 - keeper.setParams(ctx, params) - candidatesFromAddrs(ctx, keeper, addrs, []int64{0, 0, 0, 400, 200, 0}) // XXX rearrange these something messed is happenning! - - validators := keeper.GetValidators(ctx) - require.Equal(t, 2, len(validators)) - assert.Equal(t, addrs[0], validators[0].Address, "%v", validators) - assert.Equal(t, addrs[1], validators[1].Address, "%v", validators) -} - -// XXX expand to include both liabilities and assets use/test all candidate1 fields +// This function tests GetCandidate, GetCandidates, setCandidate, removeCandidate func TestCandidate(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) @@ -228,29 +57,49 @@ func TestCandidate(t *testing.T) { // check the empty keeper first _, found := keeper.GetCandidate(ctx, addrVal1) assert.False(t, found) - resAddrs := keeper.GetCandidates(ctx, 100) - assert.Zero(t, len(resAddrs)) + 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) - assert.True(t, found) + require.True(t, found) assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1) // modify a records, save, and retrieve candidate1.Liabilities = sdk.NewRat(99) keeper.setCandidate(ctx, candidate1) resCand, found = keeper.GetCandidate(ctx, addrVal1) - assert.True(t, found) + require.True(t, found) assert.True(t, candidatesEqual(candidate1, resCand)) // also test that the address has been added to address list - resAddrs = keeper.GetCandidates(ctx, 100) - require.Equal(t, 1, len(resAddrs)) - assert.Equal(t, addrVal1, resAddrs[0].Address) + resCands = keeper.GetCandidates(ctx, 100) + require.Equal(t, 1, len(resCands)) + assert.Equal(t, addrVal1, resCands[0].Address) + // add other candidates + keeper.setCandidate(ctx, candidate2) + keeper.setCandidate(ctx, candidate3) + resCand, found = keeper.GetCandidate(ctx, addrVal2) + require.True(t, found) + assert.True(t, candidatesEqual(candidate2, resCand), "%v \n %v", resCand, candidate2) + resCand, found = keeper.GetCandidate(ctx, addrVal3) + require.True(t, found) + assert.True(t, candidatesEqual(candidate3, resCand), "%v \n %v", resCand, candidate3) + 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) + + // remove a record + keeper.removeCandidate(ctx, candidate2.Address) + _, found = keeper.GetCandidate(ctx, addrVal2) + assert.False(t, found) } +// tests GetDelegatorBond, GetDelegatorBonds, SetDelegatorBond, removeDelegatorBond func TestBond(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) @@ -315,6 +164,131 @@ func TestBond(t *testing.T) { assert.True(t, bondsEqual(bond2to1, resBonds[0])) assert.True(t, bondsEqual(bond2to2, resBonds[1])) assert.True(t, bondsEqual(bond2to3, resBonds[2])) + + // delete a record + keeper.removeDelegatorBond(ctx, bond2to3) + _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal3) + assert.False(t, found) + resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + require.Equal(t, 2, len(resBonds)) + assert.True(t, bondsEqual(bond2to1, resBonds[0])) + assert.True(t, bondsEqual(bond2to2, resBonds[1])) + + // delete all the records from delegator 2 + keeper.removeDelegatorBond(ctx, bond2to1) + keeper.removeDelegatorBond(ctx, bond2to2) + _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal1) + assert.False(t, found) + _, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal2) + assert.False(t, found) + resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5) + require.Equal(t, 0, len(resBonds)) +} + +// TODO integrate in testing for equal validators, whichever one was a validator +// first remains the validator https://github.com/cosmos/cosmos-sdk/issues/582 +func TestGetValidators(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + // 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++ { + c := Candidate{ + Status: Unbonded, + PubKey: pks[i], + Address: addrs[i], + Assets: sdk.NewRat(amts[i]), + Liabilities: sdk.NewRat(amts[i]), + } + keeper.setCandidate(ctx, c) + candidates[i] = c + } + + // first make sure everything as normal is ordered + validators := keeper.GetValidators(ctx) + require.Equal(t, len(validators), n) + assert.Equal(t, sdk.NewRat(400), validators[0].VotingPower, "%v", validators) + assert.Equal(t, sdk.NewRat(200), validators[1].VotingPower, "%v", validators) + assert.Equal(t, sdk.NewRat(100), validators[2].VotingPower, "%v", validators) + assert.Equal(t, sdk.NewRat(1), validators[3].VotingPower, "%v", validators) + assert.Equal(t, sdk.NewRat(0), validators[4].VotingPower, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators) + assert.Equal(t, candidates[1].Address, validators[2].Address, "%v", validators) + assert.Equal(t, candidates[2].Address, validators[3].Address, "%v", validators) + assert.Equal(t, candidates[0].Address, validators[4].Address, "%v", validators) + + // test a basic increase in voting power + candidates[3].Assets = sdk.NewRat(500) + keeper.setCandidate(ctx, candidates[3]) + validators = keeper.GetValidators(ctx) + require.Equal(t, len(validators), n) + assert.Equal(t, sdk.NewRat(500), validators[0].VotingPower, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + + // test a decrease in voting power + candidates[3].Assets = sdk.NewRat(300) + keeper.setCandidate(ctx, candidates[3]) + validators = keeper.GetValidators(ctx) + require.Equal(t, len(validators), n) + assert.Equal(t, sdk.NewRat(300), validators[0].VotingPower, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators) + + // test a swap in voting power + candidates[0].Assets = sdk.NewRat(600) + keeper.setCandidate(ctx, candidates[0]) + validators = keeper.GetValidators(ctx) + require.Equal(t, len(validators), n) + assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators) + assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) + assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators) + assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators) + + // test the max validators term + params := keeper.GetParams(ctx) + n = 2 + params.MaxValidators = uint16(n) + keeper.setParams(ctx, params) + validators = keeper.GetValidators(ctx) + require.Equal(t, len(validators), n) + assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators) + assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators) + assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators) + 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 +} + +// test if is a validator from the last update +func TestIsRecentValidator(t *testing.T) { + //TODO + + // test that an empty validator set doesn't have any validators + // get the validators for the first time + // test a basic retrieve of something that should be a recent validator + // test a basic retrieve of something that should not be a recent validator + // remove that validator, but don't retrieve the recent validator group + // test that removed validator is not considered a recent validator } func TestParams(t *testing.T) { diff --git a/x/stake/test_common.go b/x/stake/test_common.go index 07f53b8315..aef4255813 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -154,17 +154,3 @@ func testAddr(addr string) sdk.Address { } return res } - -// XXX TODO remove this dep -func candidatesFromAddrs(ctx sdk.Context, keeper Keeper, addrs []crypto.Address, amts []int64) { - for i := 0; i < len(amts); i++ { - c := Candidate{ - Status: Unbonded, - PubKey: pks[i], - Address: addrs[i], - Assets: sdk.NewRat(amts[i]), - Liabilities: sdk.NewRat(amts[i]), - } - keeper.setCandidate(ctx, c) - } -} From cbbba2cf4f2e345fb1e212ed19652dcb5723ca51 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 02:26:52 +0200 Subject: [PATCH 49/54] stake handler nolonger function of keeper --- x/stake/handler.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index bc3f4b9d72..7bf2404fc7 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -56,13 +56,13 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { // NOTE msg already has validate basic run switch msg := msg.(type) { case MsgDeclareCandidacy: - return k.handleMsgDeclareCandidacy(ctx, msg) + return handleMsgDeclareCandidacy(ctx, msg, k) case MsgEditCandidacy: - return k.handleMsgEditCandidacy(ctx, msg) + return handleMsgEditCandidacy(ctx, msg, k) case MsgDelegate: - return k.handleMsgDelegate(ctx, msg) + return handleMsgDelegate(ctx, msg, k) case MsgUnbond: - return k.handleMsgUnbond(ctx, msg) + return handleMsgUnbond(ctx, msg, k) default: return sdk.ErrTxDecode("invalid message parse in staking module").Result() } @@ -85,7 +85,7 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { // These functions assume everything has been authenticated, // now we just perform action and save -func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy) sdk.Result { +func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keeper) sdk.Result { // check to see if the pubkey or sender has been registered before _, found := k.GetCandidate(ctx, msg.CandidateAddr) @@ -106,10 +106,10 @@ func (k Keeper) handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandida // move coins from the msg.Address account to a (self-bond) delegator account // the candidate account and global shares are updated within here - return k.delegateWithCandidate(ctx, msg.CandidateAddr, msg.Bond, candidate).Result() + return delegateWithCandidate(ctx, k, msg.CandidateAddr, msg.Bond, candidate).Result() } -func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sdk.Result { +func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result { // candidate must already be registered candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) @@ -144,7 +144,7 @@ func (k Keeper) handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy) sd return sdk.Result{} } -func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { +func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result { candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) if !found { @@ -158,10 +158,10 @@ func (k Keeper) handleMsgDelegate(ctx sdk.Context, msg MsgDelegate) sdk.Result { GasUsed: GasDelegate, } } - return k.delegateWithCandidate(ctx, msg.DelegatorAddr, msg.Bond, candidate).Result() + return delegateWithCandidate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate).Result() } -func (k Keeper) delegateWithCandidate(ctx sdk.Context, delegatorAddr sdk.Address, +func delegateWithCandidate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address, bondAmt sdk.Coin, candidate Candidate) sdk.Error { if candidate.Status == Revoked { //candidate has been withdrawn @@ -179,7 +179,7 @@ func (k Keeper) delegateWithCandidate(ctx sdk.Context, delegatorAddr sdk.Address } // Account new shares, save - err := k.BondCoins(ctx, existingBond, candidate, bondAmt) + err := BondCoins(ctx, k, existingBond, candidate, bondAmt) if err != nil { return err } @@ -189,7 +189,7 @@ func (k Keeper) delegateWithCandidate(ctx sdk.Context, delegatorAddr sdk.Address } // Perform all the actions required to bond tokens to a delegator bond from their account -func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error { +func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error { _, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount}) if err != nil { @@ -201,7 +201,7 @@ func (k Keeper) BondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candida return nil } -func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { +func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // check if bond has any shares in it unbond bond, found := k.getDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr) @@ -299,7 +299,7 @@ func (k Keeper) handleMsgUnbond(ctx sdk.Context, msg MsgUnbond) sdk.Result { // XXX where this used // Perform all the actions required to bond tokens to a delegator bond from their account -func (k Keeper) UnbondCoins(ctx sdk.Context, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error { +func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error { // subtract bond tokens from delegator bond if bond.Shares.LT(shares) { From b2c5814fd046c49963a62ef3bf9bd24502400300 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 02:54:54 +0200 Subject: [PATCH 50/54] cwgoes comments addressed --- x/stake/handler.go | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index 7bf2404fc7..151bf898c1 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -212,19 +212,29 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { return ErrInsufficientFunds().Result() } - // if shares set to special case Max then we're good - if msg.Shares != "MAX" { - // test getting rational number from decimal provided - shares, err := sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return err.Result() - } + // test getting rational number from decimal provided + shares, err := sdk.NewRatFromDecimal(msg.Shares) + if err != nil { + return err.Result() + } - // test that there are enough shares to unbond + // test that there are enough shares to unbond + if msg.Shares == "MAX" { + if !bond.Shares.GT(sdk.ZeroRat) { + return ErrNotEnoughBondShares(msg.Shares).Result() + } + } else { if !bond.Shares.GT(shares) { return ErrNotEnoughBondShares(msg.Shares).Result() } } + + // get candidate + candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) + if !found { + return ErrNoCandidateForAddress().Result() + } + if ctx.IsCheckTx() { return sdk.Result{ GasUsed: GasUnbond, @@ -244,17 +254,9 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } // subtract bond tokens from delegator bond - if bond.Shares.LT(shares) { // bond shares < msg shares - return ErrInsufficientFunds().Result() - } bond.Shares = bond.Shares.Sub(shares) - // get pubKey candidate - candidate, found := k.GetCandidate(ctx, msg.CandidateAddr) - if !found { - return ErrNoCandidateForAddress().Result() - } - + // remove the bond revokeCandidacy := false if bond.Shares.IsZero() { @@ -265,7 +267,6 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { revokeCandidacy = true } - // remove the bond k.removeDelegatorBond(ctx, bond) } else { k.setDelegatorBond(ctx, bond) @@ -276,7 +277,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) - // lastly if an revoke candidate if necessary + // revoke candidate if necessary if revokeCandidacy { // change the share types to unbonded if they were not already From 18c512e7cadea5ecc094beaa5253e76386b5fb78 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 03:04:28 +0200 Subject: [PATCH 51/54] fixes for SignBuildBroadcast in staking --- Gopkg.lock | 2 +- x/stake/commands/tx.go | 32 ++++++++------------------------ x/stake/handler.go | 7 ------- 3 files changed, 9 insertions(+), 32 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 46cb95893f..267acf32b0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -458,6 +458,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "0eb39694057c8ab8c9ecbaeb25bc43cbf1d2422976a09a67392a62dcef149a7b" + inputs-digest = "cce90fda84a63ae5b41b40f0edc357eec4020d17fdd61585960ad537418749ea" solver-name = "gps-cdcl" solver-version = 1 diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index 4314832888..90b289de79 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -91,13 +91,9 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { } msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) - name, pass, err := getNamePassword() - if err != nil { - return err - } - // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) + name := viper.GetString(client.FlagName) + res, err := builder.SignBuildBroadcast(name, msg, cdc) if err != nil { return err } @@ -132,13 +128,9 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { } msg := stake.NewMsgEditCandidacy(candidateAddr, description) - name, pass, err := getNamePassword() - if err != nil { - return err - } - // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) + name := viper.GetString(client.FlagName) + res, err := builder.SignBuildBroadcast(name, msg, cdc) if err != nil { return err } @@ -172,13 +164,9 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount) - name, pass, err := getNamePassword() - if err != nil { - return err - } - // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) + name := viper.GetString(client.FlagName) + res, err := builder.SignBuildBroadcast(name, msg, cdc) if err != nil { return err } @@ -223,13 +211,9 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr) - name, pass, err := getNamePassword() - if err != nil { - return err - } - // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(name, pass, msg, cdc) + name := viper.GetString(client.FlagName) + res, err := builder.SignBuildBroadcast(name, msg, cdc) if err != nil { return err } diff --git a/x/stake/handler.go b/x/stake/handler.go index 151bf898c1..b52731aed0 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -242,15 +242,8 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } // retrieve the amount of bonds to remove (TODO remove redundancy already serialized) - var shares sdk.Rat - var err sdk.Error if msg.Shares == "MAX" { shares = bond.Shares - } else { - shares, err = sdk.NewRatFromDecimal(msg.Shares) - if err != nil { - return err.Result() - } } // subtract bond tokens from delegator bond From cabdbfd71ef4becce3a8114b0d05f809ef412360 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 03:19:33 +0200 Subject: [PATCH 52/54] revert to develop docs --- docs/spec/README.md | 15 +- docs/spec/staking/definitions and examples.md | 250 +++--- docs/spec/staking/spec-technical.md | 755 +++++++++++------- 3 files changed, 617 insertions(+), 403 deletions(-) diff --git a/docs/spec/README.md b/docs/spec/README.md index 6a507dc031..5f3942ff9b 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -1,13 +1,18 @@ # Cosmos Hub Spec -This directory contains specifications for the application level components of the Cosmos Hub. +This directory contains specifications for the application level components of +the Cosmos Hub. NOTE: the specifications are not yet complete and very much a work in progress. -- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for sending tokens. -- [Staking](staking) - Proof of Stake related specifications including bonding and delegation transactions, inflation, fees, etc. -- [Governance](governance) - Governance related specifications including proposals and voting. -- [Other](other) - Other components of the Cosmos Hub, including the reserve pool, All in Bits vesting, etc. +- [Basecoin](basecoin) - Cosmos SDK related specifications and transactions for + sending tokens. +- [Staking](staking) - Proof of Stake related specifications including bonding + and delegation transactions, inflation, fees, etc. +- [Governance](governance) - Governance related specifications including + proposals and voting. +- [Other](other) - Other components of the Cosmos Hub, including the reserve + pool, All in Bits vesting, etc. The [specification for Tendermint](https://github.com/tendermint/tendermint/tree/develop/docs/specification/new-spec), i.e. the underlying blockchain, can be found elsewhere. diff --git a/docs/spec/staking/definitions and examples.md b/docs/spec/staking/definitions and examples.md index 72dbc3a3d4..ba4c1563e2 100644 --- a/docs/spec/staking/definitions and examples.md +++ b/docs/spec/staking/definitions and examples.md @@ -2,113 +2,183 @@ ## Basic Terms and Definitions -- Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system -- Atom - native token of the Cosmsos Hub -- Atom holder - an entity that holds some amount of Atoms -- Candidate - an Atom holder that is actively involved in Tendermint blockchain protocol (running Tendermint Full Node -TODO: add link to Full Node definition) and is competing with other candidates to be elected as a Validator -(TODO: add link to Validator definition)) -- Validator - a candidate that is currently selected among a set of candidates to be able to sign protocol messages -in the Tendermint consensus protocol -- Delegator - an Atom holder that has bonded any of its Atoms by delegating them to a validator (or a candidate) -- Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms under protocol control). -Atoms are always bonded through a validator (or candidate) process. Bonded atoms can be slashed (burned) in case a -validator process misbehaves (does not behave according to a protocol specification). Atom holder can regain access -to its bonded Atoms (if they are not slashed in the meantime), i.e., they can be moved to its account, -after Unbonding period has expired. -- Unbonding period - a period of time after which Atom holder gains access to its bonded Atoms (they can be withdrawn -to a user account) or they can re-delegate -- Inflationary provisions - inflation is a process of increasing Atom supply. Atoms are being created in the process of -(Cosmos Hub) blocks creation. Owners of bonded atoms are rewarded for securing network with inflationary provisions -proportional to it's bonded Atom share. -- Transaction fees - transaction fee is a fee that is included in the Cosmsos Hub transactions. The fees are collected -by the current validator set and distributed among validators and delegators in proportion to it's bonded Atom share. -- Commission fee - a fee taken from the transaction fees by a validator for it's service +* Cosmsos Hub - a Tendermint-based Proof of Stake blockchain system +* Atom - native token of the Cosmsos Hub +* Atom holder - an entity that holds some amount of Atoms +* Candidate - an Atom holder that is actively involved in the Tendermint + blockchain protocol (running Tendermint Full Node (TODO: add link to Full + Node definition) and is competing with other candidates to be elected as a + validator (TODO: add link to Validator definition)) +* Validator - a candidate that is currently selected among a set of candidates + to be able to sign protocol messages in the Tendermint consensus protocol +* Delegator - an Atom holder that has bonded some of its Atoms by delegating + them to a validator (or a candidate) +* Bonding Atoms - a process of locking Atoms in a bond deposit (putting Atoms + under protocol control). Atoms are always bonded through a validator (or + candidate) process. Bonded atoms can be slashed (burned) in case a validator + process misbehaves (does not behave according to the protocol specification). + Atom holders can regain access to their bonded Atoms if they have not been + slashed by waiting an Unbonding period. +* Unbonding period - a period of time after which Atom holder gains access to + its bonded Atoms (they can be withdrawn to a user account) or they can be + re-delegated. +* Inflationary provisions - inflation is the process of increasing the Atom supply. + Atoms are periodically created on the Cosmos Hub and issued to bonded Atom holders. + The goal of inflation is to incentize most of the Atoms in existence to be bonded. +* Transaction fees - transaction fee is a fee that is included in a Cosmsos Hub + transaction. The fees are collected by the current validator set and + distributed among validators and delegators in proportion to their bonded + Atom share. +* Commission fee - a fee taken from the transaction fees by a validator for + their service ## The pool and the share -At the core of the Staking module is the concept of a pool which denotes collection of Atoms contributed by different -Atom holders. There are two global pools in the Staking module: bonded pool and unbonded pool. Bonded Atoms are part -of the global bonded pool. On the other side, if a candidate or delegator wants to unbond its Atoms, those Atoms are -kept in the unbonding pool for a duration of the unbonding period. In the Staking module, a pool is logical concept, -i.e., there is no pool data structure that would be responsible for managing pool resources. Instead, it is managed -in a distributed way. More precisely, at the global level, for each pool, we track only the total amount of -(bonded or unbonded) Atoms and a current amount of issued shares. A share is a unit of Atom distribution and the -value of the share (share-to-atom exchange rate) is changing during the system execution. The -share-to-atom exchange rate can be computed as: +At the core of the Staking module is the concept of a pool which denotes a +collection of Atoms contributed by different Atom holders. There are two global +pools in the Staking module: the bonded pool and unbonding pool. Bonded Atoms +are part of the global bonded pool. If a candidate or delegator wants to unbond +its Atoms, those Atoms are moved to the the unbonding pool for the duration of +the unbonding period. In the Staking module, a pool is a logical concept, i.e., +there is no pool data structure that would be responsible for managing pool +resources. Instead, it is managed in a distributed way. More precisely, at the +global level, for each pool, we track only the total amount of bonded or unbonded +Atoms and the current amount of issued shares. A share is a unit of Atom distribution +and the value of the share (share-to-atom exchange rate) changes during +system execution. The share-to-atom exchange rate can be computed as: -`share-to-atom-ex-rate = size of the pool / ammount of issued shares` +`share-to-atom-exchange-rate = size of the pool / ammount of issued shares` -Then for each candidate (in a per candidate data structure) we keep track of an amount of shares the candidate is owning -in a pool. At any point in time, the exact amount of Atoms a candidate has in the pool -can be computed as the number of shares it owns multiplied with the share-to-atom exchange rate: +Then for each validator candidate (in a per candidate data structure) we keep track of +the amount of shares the candidate owns in a pool. At any point in time, +the exact amount of Atoms a candidate has in the pool can be computed as the +number of shares it owns multiplied with the current share-to-atom exchange rate: -`candidate-coins = candidate.Shares * share-to-atom-ex-rate` +`candidate-coins = candidate.Shares * share-to-atom-exchange-rate` -The benefit of such accounting of the pool resources is the fact that a modification to the pool because of -bonding/unbonding/slashing/provisioning of atoms affects only global data (size of the pool and the number of shares) -and the related validator/candidate data structure, i.e., the data structure of other validators do not need to be -modified. Let's explain this further with several small examples: +The benefit of such accounting of the pool resources is the fact that a +modification to the pool from bonding/unbonding/slashing/provisioning of +Atoms affects only global data (size of the pool and the number of shares) and +not the related validator/candidate data structure, i.e., the data structure of +other validators do not need to be modified. This has the advantage that +modifying global data is much cheaper computationally than modifying data of +every validator. Let's explain this further with several small examples: -We consider initially 4 validators p1, p2, p3 and p4, and that each validator has bonded 10 Atoms -to a bonded pool. Furthermore, let's assume that we have issued initially 40 shares (note that the initial distribution -of the shares, i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., -share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we have, the size of the pool is 40 Atoms, and -the amount of issued shares is equal to 40. And for each validator we store in their corresponding data structure -that each has 10 shares of the bonded pool. Now lets assume that the validator p4 starts process of unbonding of 5 -shares. Then the total size of the pool is decreased and now it will be 35 shares and the amount of Atoms is 35. -Note that the only change in other data structures needed is reducing the number of shares for a validator p4 from 10 -to 5. +We consider initially 4 validators p1, p2, p3 and p4, and that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 shares (note that the initial distribution of the shares, +i.e., share-to-atom exchange rate can be set to any meaningful value), i.e., +share-to-atom-ex-rate = 1 atom per share. Then at the global pool level we +have, the size of the pool is 40 Atoms, and the amount of issued shares is +equal to 40. And for each validator we store in their corresponding data +structure that each has 10 shares of the bonded pool. Now lets assume that the +validator p4 starts process of unbonding of 5 shares. Then the total size of +the pool is decreased and now it will be 35 shares and the amount of Atoms is +35 . Note that the only change in other data structures needed is reducing the +number of shares for a validator p4 from 10 to 5. -Let's consider now the case where a validator p1 wants to bond 15 more atoms to the pool. Now the size of the pool -is 50, and as the exchange rate hasn't changed (1 share is still worth 1 Atom), we need to create more shares, -i.e. we now have 50 shares in the pool in total. -Validators p2, p3 and p4 still have (correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we -don't need to modify anything in their corresponding data structures. But p1 now has 25 shares, so we update the amount -of shares owned by the p1 in its data structure. Note that apart from the size of the pool that is in Atoms, all other -data structures refer only to shares. +Let's consider now the case where a validator p1 wants to bond 15 more atoms to +the pool. Now the size of the pool is 50, and as the exchange rate hasn't +changed (1 share is still worth 1 Atom), we need to create more shares, i.e. we +now have 50 shares in the pool in total. Validators p2, p3 and p4 still have +(correspondingly) 10, 10 and 5 shares each worth of 1 atom per share, so we +don't need to modify anything in their corresponding data structures. But p1 +now has 25 shares, so we update the amount of shares owned by p1 in its +data structure. Note that apart from the size of the pool that is in Atoms, all +other data structures refer only to shares. -Finally, let's consider what happens when new Atoms are created and added to the pool due to inflation. Let's assume that -the inflation rate is 10 percent and that it is applied to the current state of the pool. This means that 5 Atoms are -created and added to the pool and that each validator now proportionally increase it's Atom count. Let's analyse how this -change is reflected in the data structures. First, the size of the pool is increased and is now 55 atoms. As a share of -each validator in the pool hasn't changed, this means that the total number of shares stay the same (50) and that the -amount of shares of each validator stays the same (correspondingly 25, 10, 10, 5). But the exchange rate has changed and -each share is now worth 55/50 Atoms per share, so each validator has effectively increased amount of Atoms it has. -So validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. +Finally, let's consider what happens when new Atoms are created and added to +the pool due to inflation. Let's assume that the inflation rate is 10 percent +and that it is applied to the current state of the pool. This means that 5 +Atoms are created and added to the pool and that each validator now +proportionally increase it's Atom count. Let's analyse how this change is +reflected in the data structures. First, the size of the pool is increased and +is now 55 atoms. As a share of each validator in the pool hasn't changed, this +means that the total number of shares stay the same (50) and that the amount of +shares of each validator stays the same (correspondingly 25, 10, 10, 5). But +the exchange rate has changed and each share is now worth 55/50 Atoms per +share, so each validator has effectively increased amount of Atoms it has. So +validators now have (correspondingly) 55/2, 55/5, 55/5 and 55/10 Atoms. -The concepts of the pool and its shares is at the core of the accounting in the Staking module. It is used for managing -the global pools (such as bonding and unbonding pool), but also for distribution of Atoms between validator and its -delegators (we will explain this in section X). +The concepts of the pool and its shares is at the core of the accounting in the +Staking module. It is used for managing the global pools (such as bonding and +unbonding pool), but also for distribution of Atoms between validator and its +delegators (we will explain this in section X). #### Delegator shares -A candidate is, depending on it's status, contributing Atoms to either the bonded or unbonded pool, and in return gets -some amount of (global) pool shares. Note that not all those Atoms (and respective shares) are owned by the candidate -as some Atoms could be delegated to a candidate. The mechanism for distribution of Atoms (and shares) between a -candidate and it's delegators is based on a notion of delegator shares. More precisely, every candidate is issuing -(local) delegator shares (`Candidate.IssuedDelegatorShares`) that represents some portion of global shares -managed by the candidate (`Candidate.GlobalStakeShares`). The principle behind managing delegator shares is the same -as described in [Section](#The pool and the share). We now illustrate it with an example. +A candidate is, depending on it's status, contributing Atoms to either the +bonded or unbonding pool, and in return gets some amount of (global) pool +shares. Note that not all those Atoms (and respective shares) are owned by the +candidate as some Atoms could be delegated to a candidate. The mechanism for +distribution of Atoms (and shares) between a candidate and it's delegators is +based on a notion of delegator shares. More precisely, every candidate is +issuing (local) delegator shares (`Candidate.IssuedDelegatorShares`) that +represents some portion of global shares managed by the candidate +(`Candidate.GlobalStakeShares`). The principle behind managing delegator shares +is the same as described in [Section](#The pool and the share). We now +illustrate it with an example. -Lets consider 4 validators p1, p2, p3 and p4, and assume that each validator has bonded 10 Atoms to a bonded pool. -Furthermore, lets assume that we have issued initially 40 global shares, i.e., that `share-to-atom-ex-rate = 1 atom per -share`. So we will `GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the Candidate data structure -of each validator `Candidate.GlobalStakeShares = 10`. Furthermore, each validator issued 10 delegator -shares which are initially owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where +Let's consider 4 validators p1, p2, p3 and p4, and assume that each validator +has bonded 10 Atoms to the bonded pool. Furthermore, let's assume that we have +issued initially 40 global shares, i.e., that +`share-to-atom-exchange-rate = 1 atom per share`. So we will set +`GlobalState.BondedPool = 40` and `GlobalState.BondedShares = 40` and in the +Candidate data structure of each validator `Candidate.GlobalStakeShares = 10`. +Furthermore, each validator issued 10 delegator shares which are initially +owned by itself, i.e., `Candidate.IssuedDelegatorShares = 10`, where `delegator-share-to-global-share-ex-rate = 1 global share per delegator share`. -Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and consider what are the updates we need -to make to the data structures. First, `GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, -for validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to issue also additional delegator shares, -i.e., `Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator shares of validator p1, where -each delegator share is worth 1 global shares, i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due -to inflation. In that case, we only need to update `GlobalState.BondedPool` which is now equal to 50 Atoms as created -Atoms are added to the bonded pool. Note that the amount of global and delegator shares stay the same but they are now -worth more as share-to-atom-ex-rate is now worth 50/45 Atoms per share. Therefore, a delegator d1 now owns - -`delegatorCoins = 10 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 100/9 Atoms` - +Now lets assume that a delegator d1 delegates 5 atoms to a validator p1 and +consider what are the updates we need to make to the data structures. First, +`GlobalState.BondedPool = 45` and `GlobalState.BondedShares = 45`. Then, for +validator p1 we have `Candidate.GlobalStakeShares = 15`, but we also need to +issue also additional delegator shares, i.e., +`Candidate.IssuedDelegatorShares = 15` as the delegator d1 now owns 5 delegator +shares of validator p1, where each delegator share is worth 1 global shares, +i.e, 1 Atom. Lets see now what happens after 5 new Atoms are created due to +inflation. In that case, we only need to update `GlobalState.BondedPool` which +is now equal to 50 Atoms as created Atoms are added to the bonded pool. Note +that the amount of global and delegator shares stay the same but they are now +worth more as share-to-atom-exchange-rate is now worth 50/45 Atoms per share. +Therefore, a delegator d1 now owns: +`delegatorCoins = 5 (delegator shares) * 1 (delegator-share-to-global-share-ex-rate) * 50/45 (share-to-atom-ex-rate) = 5.55 Atoms` + +### Inflation provisions + +Validator provisions are minted on an hourly basis (the first block of a new +hour). The annual target of between 7% and 20%. The long-term target ratio of +bonded tokens to unbonded tokens is 67%. + +The target annual inflation rate is recalculated for each provisions cycle. The +inflation is also subject to a rate change (positive or negative) depending on +the distance from the desired ratio (67%). The maximum rate change possible is +defined to be 13% per year, however the annual inflation is capped as between +7% and 20%. + +```go +inflationRateChange(0) = 0 +GlobalState.Inflation(0) = 0.07 + +bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply +AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 + +annualInflation += AnnualInflationRateChange + +if annualInflation > 0.20 then GlobalState.Inflation = 0.20 +if annualInflation < 0.07 then GlobalState.Inflation = 0.07 + +provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) +``` + +Because the validators hold a relative bonded share (`GlobalStakeShares`), when +more bonded tokens are added proportionally to all validators, the only term +which needs to be updated is the `GlobalState.BondedPool`. So for each +provisions cycle: + +```go +GlobalState.BondedPool += provisionTokensHourly +``` diff --git a/docs/spec/staking/spec-technical.md b/docs/spec/staking/spec-technical.md index e3a528d948..8d9baa7967 100644 --- a/docs/spec/staking/spec-technical.md +++ b/docs/spec/staking/spec-technical.md @@ -2,54 +2,66 @@ ## Overview -The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that serves as a backbone of the Cosmos ecosystem. -It is operated and secured by an open and globally decentralized set of validators. Tendermint consensus is a -Byzantine fault-tolerant distributed protocol that involves all validators in the process of exchanging protocol -messages in the production of each block. To avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up -coins in a bond deposit. Tendermint protocol messages are signed by the validator's private key, and this is a basis for -Tendermint strict accountability that allows punishing misbehaving validators by slashing (burning) their bonded Atoms. -On the other hand, validators are for it's service of securing blockchain network rewarded by the inflationary -provisions and transactions fees. This incentivizes correct behavior of the validators and provide economic security -of the network. +The Cosmos Hub is a Tendermint-based Proof of Stake blockchain system that +serves as a backbone of the Cosmos ecosystem. It is operated and secured by an +open and globally decentralized set of validators. Tendermint consensus is a +Byzantine fault-tolerant distributed protocol that involves all validators in +the process of exchanging protocol messages in the production of each block. To +avoid Nothing-at-Stake problem, a validator in Tendermint needs to lock up +coins in a bond deposit. Tendermint protocol messages are signed by the +validator's private key, and this is a basis for Tendermint strict +accountability that allows punishing misbehaving validators by slashing +(burning) their bonded Atoms. On the other hand, validators are rewarded for +their service of securing blockchain network by the inflationary provisions and +transactions fees. This incentives correct behavior of the validators and +provides the economic security of the network. + +The native token of the Cosmos Hub is called Atom; becoming a validator of the +Cosmos Hub requires holding Atoms. However, not all Atom holders are validators +of the Cosmos Hub. More precisely, there is a selection process that determines +the validator set as a subset of all validator candidates (Atom holders that +wants to become a validator). The other option for Atom holder is to delegate +their atoms to validators, i.e., being a delegator. A delegator is an Atom +holder that has bonded its Atoms by delegating it to a validator (or validator +candidate). By bonding Atoms to secure the network (and taking a risk of being +slashed in case of misbehaviour), a user is rewarded with inflationary +provisions and transaction fees proportional to the amount of its bonded Atoms. +The Cosmos Hub is designed to efficiently facilitate a small numbers of +validators (hundreds), and large numbers of delegators (tens of thousands). +More precisely, it is the role of the Staking module of the Cosmos Hub to +support various staking functionality including validator set selection, +delegating, bonding and withdrawing Atoms, and the distribution of inflationary +provisions and transaction fees. -The native token of the Cosmos Hub is called Atom; becoming a validator of the Cosmos Hub requires holding Atoms. -However, not all Atom holders are validators of the Cosmos Hub. More precisely, there is a selection process that -determines the validator set as a subset of all validator candidates (Atom holder that wants to -become a validator). The other option for Atom holder is to delegate their atoms to validators, i.e., -being a delegator. A delegator is an Atom holder that has bonded its Atoms by delegating it to a validator -(or validator candidate). By bonding Atoms to securing network (and taking a risk of being slashed in case the -validator misbehaves), a user is rewarded with inflationary provisions and transaction fees proportional to the amount -of its bonded Atoms. The Cosmos Hub is designed to efficiently facilitate a small numbers of validators (hundreds), and -large numbers of delegators (tens of thousands). More precisely, it is the role of the Staking module of the Cosmos Hub -to support various staking functionality including validator set selection; delegating, bonding and withdrawing Atoms; -and the distribution of inflationary provisions and transaction fees. - ## State The staking module persists the following information to the store: -- `GlobalState`, describing the global pools and the inflation related fields -- `map[PubKey]Candidate`, a map of validator candidates (including current validators), indexed by public key -- `map[rational.Rat]Candidate`, an ordered map of validator candidates (including current validators), indexed by -shares in the global pool (bonded or unbonded depending on candidate status) -- `map[[]byte]DelegatorBond`, a map of DelegatorBonds (for each delegation to a candidate by a delegator), indexed by -the delegator address and the candidate public key -- `queue[QueueElemUnbondDelegation]`, a queue of unbonding delegations -- `queue[QueueElemReDelegate]`, a queue of re-delegations +* `GlobalState`, describing the global pools and the inflation related fields +* validator candidates (including current validators), indexed by public key and shares in the global pool +(bonded or unbonded depending on candidate status) +* delegator bonds (for each delegation to a candidate by a delegator), indexed by the delegator address and the candidate + public key +* the queue of unbonding delegations +* the queue of re-delegations ### Global State -GlobalState data structure contains total Atoms supply, amount of Atoms in the bonded pool, sum of all shares -distributed for the bonded pool, amount of Atoms in the unbonded pool, sum of all shares distributed for the -unbonded pool, a timestamp of the last processing of inflation, the current annual inflation rate, a timestamp -for the last comission accounting reset, the global fee pool, a pool of reserve taxes collected for the governance use -and an adjustment factor for calculating global feel accum (?). - -``` golang +The GlobalState data structure contains total Atom supply, amount of Atoms in +the bonded pool, sum of all shares distributed for the bonded pool, amount of +Atoms in the unbonded pool, sum of all shares distributed for the unbonded +pool, a timestamp of the last processing of inflation, the current annual +inflation rate, a timestamp for the last comission accounting reset, the global +fee pool, a pool of reserve taxes collected for the governance use and an +adjustment factor for calculating global fee accum. `Params` is global data +structure that stores system parameters and defines overall functioning of the +module. + +``` go type GlobalState struct { TotalSupply int64 // total supply of Atoms BondedPool int64 // reserve of bonded tokens BondedShares rational.Rat // sum of all shares distributed for the BondedPool - UnbondedPool int64 // reserve of unbonded tokens held with candidates + UnbondedPool int64 // reserve of unbonding tokens held with candidates UnbondedShares rational.Rat // sum of all shares distributed for the UnbondedPool InflationLastTime int64 // timestamp of last processing of inflation Inflation rational.Rat // current annual inflation rate @@ -58,19 +70,40 @@ type GlobalState struct { ReservePool coin.Coins // pool of reserve taxes collected on all fees for governance use Adjustment rational.Rat // Adjustment factor for calculating global fee accum } + +type Params struct { + HoldBonded Address // account where all bonded coins are held + HoldUnbonding Address // account where all delegated but unbonding coins are held + + InflationRateChange rational.Rational // maximum annual change in inflation rate + InflationMax rational.Rational // maximum inflation rate + InflationMin rational.Rational // minimum inflation rate + GoalBonded rational.Rational // Goal of percent bonded atoms + ReserveTax rational.Rational // Tax collected on all fees + + MaxVals uint16 // maximum number of validators + AllowedBondDenom string // bondable coin denomination + + // gas costs for txs + GasDeclareCandidacy int64 + GasEditCandidacy int64 + GasDelegate int64 + GasRedelegate int64 + GasUnbond int64 +} ``` ### Candidate -The `Candidate` data structure holds the current state and some historical actions of -validators or candidate-validators. +The `Candidate` data structure holds the current state and some historical +actions of validators or candidate-validators. -``` golang +``` go type Candidate struct { Status CandidateStatus - PubKey crypto.PubKey + ConsensusPubKey crypto.PubKey GovernancePubKey crypto.PubKey - Owner Address + Owner crypto.Address GlobalStakeShares rational.Rat IssuedDelegatorShares rational.Rat RedelegatingShares rational.Rat @@ -83,118 +116,115 @@ type Candidate struct { Adjustment rational.Rat Description Description } -``` -CandidateStatus can be VyingUnbonded, VyingUnbonding, Bonded, KickUnbonding and KickUnbonded. - - -``` golang type Description struct { - Name string - DateBonded string - Identity string - Website string - Details string + Name string + DateBonded string + Identity string + Website string + Details string } ``` Candidate parameters are described: - - Status: signal that the candidate is either vying for validator status, - either unbonded or unbonding, an active validator, or a kicked validator - either unbonding or unbonded. - - PubKey: separated key from the owner of the candidate as is used strictly - for participating in consensus. - - Owner: Address where coins are bonded from and unbonded to - - GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if - `Candidate.Status` is `Bonded`; or shares of `GlobalState.UnbondedPool` otherwise - - IssuedDelegatorShares: Sum of all shares a candidate issued to delegators (which - includes the candidate's self-bond); a delegator share represents their stake in - the Candidate's `GlobalStakeShares` - - RedelegatingShares: The portion of `IssuedDelegatorShares` which are - currently re-delegating to a new validator - - VotingPower: Proportional to the amount of bonded tokens which the validator - has if the candidate is a validator. - - Commission: The commission rate of fees charged to any delegators - - CommissionMax: The maximum commission rate this candidate can charge - each day from the date `GlobalState.DateLastCommissionReset` - - CommissionChangeRate: The maximum daily increase of the candidate commission - - CommissionChangeToday: Counter for the amount of change to commission rate - which has occurred today, reset on the first block of each day (UTC time) - - ProposerRewardPool: reward pool for extra fees collected when this candidate - is the proposer of a block - - Adjustment factor used to passively calculate each validators entitled fees - from `GlobalState.FeePool` - - Description - - Name: moniker - - DateBonded: date determined which the validator was bonded - - Identity: optional field to provide a signature which verifies the - validators identity (ex. UPort or Keybase) - - Website: optional website link - - Details: optional details +* Status: it can be Bonded (active validator), Unbonding (validator candidate) + or Revoked +* ConsensusPubKey: candidate public key that is used strictly for participating in + consensus +* GovernancePubKey: public key used by the validator for governance voting +* Owner: Address that is allowed to unbond coins. +* GlobalStakeShares: Represents shares of `GlobalState.BondedPool` if + `Candidate.Status` is `Bonded`; or shares of `GlobalState.Unbondingt Pool` + otherwise +* IssuedDelegatorShares: Sum of all shares a candidate issued to delegators + (which includes the candidate's self-bond); a delegator share represents + their stake in the Candidate's `GlobalStakeShares` +* RedelegatingShares: The portion of `IssuedDelegatorShares` which are + currently re-delegating to a new validator +* VotingPower: Proportional to the amount of bonded tokens which the validator + has if `Candidate.Status` is `Bonded`; otherwise it is equal to `0` +* Commission: The commission rate of fees charged to any delegators +* CommissionMax: The maximum commission rate this candidate can charge each + day from the date `GlobalState.DateLastCommissionReset` +* CommissionChangeRate: The maximum daily increase of the candidate commission +* CommissionChangeToday: Counter for the amount of change to commission rate + which has occurred today, reset on the first block of each day (UTC time) +* ProposerRewardPool: reward pool for extra fees collected when this candidate + is the proposer of a block +* Adjustment factor used to passively calculate each validators entitled fees + from `GlobalState.FeePool` +* Description + * Name: moniker + * DateBonded: date determined which the validator was bonded + * Identity: optional field to provide a signature which verifies the + validators identity (ex. UPort or Keybase) + * Website: optional website link + * Details: optional details ### DelegatorBond -Atom holders may delegate coins to validators; under this circumstance their -funds are held in a `DelegatorBond` data structure. It is owned by one delegator, and is -associated with the shares for one validator. The sender of the transaction is -considered the owner of the bond. +Atom holders may delegate coins to candidates; under this circumstance their +funds are held in a `DelegatorBond` data structure. It is owned by one +delegator, and is associated with the shares for one candidate. The sender of +the transaction is the owner of the bond. -``` golang +``` go type DelegatorBond struct { - Candidate crypto.PubKey - Shares rational.Rat + Candidate crypto.PubKey + Shares rational.Rat AdjustmentFeePool coin.Coins AdjustmentRewardPool coin.Coins } ``` Description: - - Candidate: the public key of the validator candidate: bonding too - - Shares: the number of delegator shares received from the validator candidate - - AdjustmentFeePool: Adjustment factor used to passively calculate each bonds - entitled fees from `GlobalState.FeePool` - - AdjustmentRewardPool: Adjustment factor used to passively calculate each - bonds entitled fees from `Candidate.ProposerRewardPool`` +* Candidate: the public key of the validator candidate: bonding too +* Shares: the number of delegator shares received from the validator candidate +* AdjustmentFeePool: Adjustment factor used to passively calculate each bonds + entitled fees from `GlobalState.FeePool` +* AdjustmentRewardPool: Adjustment factor used to passively calculate each + bonds entitled fees from `Candidate.ProposerRewardPool` + ### QueueElem -Unbonding and re-delegation process is implemented using the ordered queue data structure. -All queue elements used share a common structure: +The Unbonding and re-delegation process is implemented using the ordered queue +data structure. All queue elements share a common structure: -``` golang +```golang type QueueElem struct { - Candidate crypto.PubKey - InitHeight int64 // when the queue was initiated + Candidate crypto.PubKey + InitTime int64 // when the element was added to the queue } ``` -The queue is ordered so the next to unbond/re-delegate is at the head. Every -tick the head of the queue is checked and if the unbonding period has passed -since `InitHeight`, the final settlement of the unbonding is started or re-delegation is executed, and the element is -pop from the queue. Each `QueueElem` is persisted in the store until it is popped from the queue. +The queue is ordered so the next element to unbond/re-delegate is at the head. +Every tick the head of the queue is checked and if the unbonding period has +passed since `InitTime`, the final settlement of the unbonding is started or +re-delegation is executed, and the element is popped from the queue. Each +`QueueElem` is persisted in the store until it is popped from the queue. ### QueueElemUnbondDelegation -``` golang +QueueElemUnbondDelegation structure is used in the unbonding queue. + +```golang type QueueElemUnbondDelegation struct { - QueueElem - Payout Address // account to pay out to - Shares rational.Rat // amount of delegator shares which are unbonding - StartSlashRatio rational.Rat // candidate slash ratio at start of re-delegation + QueueElem + Payout Address // account to pay out to + Tokens coin.Coins // the value in Atoms of the amount of delegator shares which are unbonding + StartSlashRatio rational.Rat // candidate slash ratio } ``` -In the unbonding queue - the fraction of all historical slashings on -that validator are recorded (`StartSlashRatio`). When this queue reaches maturity -if that total slashing applied is greater on the validator then the -difference (amount that should have been slashed from the first validator) is -assigned to the amount being paid out. ### QueueElemReDelegate -``` golang +QueueElemReDelegate structure is used in the re-delegation queue. + +```golang type QueueElemReDelegate struct { - QueueElem - Payout Address // account to pay out to + QueueElem + Payout Address // account to pay out to Shares rational.Rat // amount of shares which are unbonding NewCandidate crypto.PubKey // validator to bond to after unbond } @@ -203,31 +233,38 @@ type QueueElemReDelegate struct { ### Transaction Overview Available Transactions: - - TxDeclareCandidacy - - TxEditCandidacy - - TxLivelinessCheck - - TxProveLive - - TxDelegate - - TxUnbond - - TxRedelegate +* TxDeclareCandidacy +* TxEditCandidacy +* TxDelegate +* TxUnbond +* TxRedelegate +* TxLivelinessCheck +* TxProveLive ## Transaction processing -In this section we describe the processing of the transactions and the corresponding updates to the global state. -For the following text we will use gs to refer to the GlobalState data structure, candidateMap is a reference to the -map[PubKey]Candidate, delegatorBonds is a reference to map[[]byte]DelegatorBond, unbondDelegationQueue is a -reference to the queue[QueueElemUnbondDelegation] and redelegationQueue is the reference for the -queue[QueueElemReDelegate]. We use tx to denote reference to a transaction that is being processed. +In this section we describe the processing of the transactions and the +corresponding updates to the global state. In the following text we will use +`gs` to refer to the `GlobalState` data structure, `unbondDelegationQueue` is a +reference to the queue of unbond delegations, `reDelegationQueue` is the +reference for the queue of redelegations. We use `tx` to denote a +reference to a transaction that is being processed, and `sender` to denote the +address of the sender of the transaction. We use function +`loadCandidate(store, PubKey)` to obtain a Candidate structure from the store, +and `saveCandidate(store, candidate)` to save it. Similarly, we use +`loadDelegatorBond(store, sender, PubKey)` to load a delegator bond with the +key (sender and PubKey) from the store, and +`saveDelegatorBond(store, sender, bond)` to save it. +`removeDelegatorBond(store, sender, bond)` is used to remove the bond from the +store. ### TxDeclareCandidacy -A validator candidacy can be declared using the `TxDeclareCandidacy` transaction. -During this transaction a self-delegation transaction is executed to bond -tokens which are sent in with the transaction (TODO: What does this mean?). +A validator candidacy is declared using the `TxDeclareCandidacy` transaction. -``` golang +```golang type TxDeclareCandidacy struct { - PubKey crypto.PubKey + ConsensusPubKey crypto.PubKey Amount coin.Coin GovernancePubKey crypto.PubKey Commission rational.Rat @@ -235,28 +272,25 @@ type TxDeclareCandidacy struct { CommissionMaxChange int64 Description Description } -``` -``` declareCandidacy(tx TxDeclareCandidacy): - // create and save the empty candidate candidate = loadCandidate(store, tx.PubKey) - if candidate != nil then return + if candidate != nil return // candidate with that public key already exists candidate = NewCandidate(tx.PubKey) candidate.Status = Unbonded candidate.Owner = sender - init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares,RedelegatingShares and Adjustment to rational.Zero + init candidate VotingPower, GlobalStakeShares, IssuedDelegatorShares, RedelegatingShares and Adjustment to rational.Zero init commision related fields based on the values from tx candidate.ProposerRewardPool = Coin(0) candidate.Description = tx.Description saveCandidate(store, candidate) - // move coins from the sender account to a (self-bond) delegator account - // the candidate account and global shares are updated within here - txDelegate = TxDelegate{tx.BondUpdate} - return delegateWithCandidate(txDelegate, candidate) + txDelegate = TxDelegate(tx.PubKey, tx.Amount) + return delegateWithCandidate(txDelegate, candidate) + +// see delegateWithCandidate function in [TxDelegate](TxDelegate) ``` ### TxEditCandidacy @@ -265,221 +299,326 @@ If either the `Description` (excluding `DateBonded` which is constant), `Commission`, or the `GovernancePubKey` need to be updated, the `TxEditCandidacy` transaction should be sent from the owner account: -``` golang +```golang type TxEditCandidacy struct { GovernancePubKey crypto.PubKey Commission int64 Description Description } -``` -``` editCandidacy(tx TxEditCandidacy): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil or candidate.Status == Unbonded return - if tx.GovernancePubKey != nil then candidate.GovernancePubKey = tx.GovernancePubKey - if tx.Commission >= 0 then candidate.Commission = tx.Commission - if tx.Description != nil then candidate.Description = tx.Description + if candidate == nil or candidate.Status == Revoked return + + if tx.GovernancePubKey != nil candidate.GovernancePubKey = tx.GovernancePubKey + if tx.Commission >= 0 candidate.Commission = tx.Commission + if tx.Description != nil candidate.Description = tx.Description + saveCandidate(store, candidate) return - ``` +``` ### TxDelegate -All bonding, whether self-bonding or delegation, is done via `TxDelegate`. +Delegator bonds are created using the `TxDelegate` transaction. Within this +transaction the delegator provides an amount of coins, and in return receives +some amount of candidate's delegator shares that are assigned to +`DelegatorBond.Shares`. -Delegator bonds are created using the `TxDelegate` transaction. Within this transaction the delegator provides -an amount of coins, and in return receives some amount of candidate's delegator shares that are assigned to -`DelegatorBond.Shares`. The amount of created delegator shares depends on the candidate's -delegator-shares-to-atoms exchange rate and is computed as -`delegator-shares = delegator-coins / delegator-shares-to-atom-ex-rate`. - -``` golang -type TxDelegate struct { - PubKey crypto.PubKey - Amount coin.Coin +```golang +type TxDelegate struct { + PubKey crypto.PubKey + Amount coin.Coin } -``` -``` delegate(tx TxDelegate): candidate = loadCandidate(store, tx.PubKey) - if candidate == nil then return + if candidate == nil return return delegateWithCandidate(tx, candidate) delegateWithCandidate(tx TxDelegate, candidate Candidate): - if candidate.Status == Revoked then return + if candidate.Status == Revoked return - if candidate.Status == Bonded then - poolAccount = address of the bonded pool - else - poolAccount = address of the unbonded pool + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded - // Move coins from the delegator account to the bonded pool account - err = transfer(sender, poolAccount, tx.Amount) - if err != nil then return + err = transfer(sender, poolAccount, tx.Amount) + if err != nil return - // Get or create the delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then - bond = DelegatorBond{tx.PubKey,rational.Zero, Coin(0), Coin(0)} + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then bond = DelegatorBond(tx.PubKey, rational.Zero, Coin(0), Coin(0)) - issuedDelegatorShares = candidate.addTokens(tx.Amount, gs) - bond.Shares = bond.Shares.Add(issuedDelegatorShares) + issuedDelegatorShares = addTokens(tx.Amount, candidate) + bond.Shares += issuedDelegatorShares - saveCandidate(store, candidate) - - store.Set(GetDelegatorBondKey(sender, bond.PubKey), bond) - - saveGlobalState(store, gs) - return + saveCandidate(store, candidate) + saveDelegatorBond(store, sender, bond) + saveGlobalState(store, gs) + return -addTokens(amount int64, gs GlobalState, candidate Candidate): - - // get the exchange rate of global pool shares over delegator shares - if candidate.IssuedDelegatorShares.IsZero() then +addTokens(amount coin.Coin, candidate Candidate): + if candidate.Status == Bonded + gs.BondedPool += amount + issuedShares = amount / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares + else + gs.UnbondedPool += amount + issuedShares = amount / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares += issuedShares + + if candidate.IssuedDelegatorShares.IsZero() exRate = rational.One else - exRate = candiate.GlobalStakeShares.Quo(candidate.IssuedDelegatorShares) - - if candidate.Status == Bonded then - gs.BondedPool += amount - issuedShares = exchangeRate(gs.BondedShares, gs.BondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens - gs.BondedShares = gs.BondedShares.Add(issuedShares) - else - gs.UnbondedPool += amount - issuedShares = exchangeRate(gs.UnbondedShares, gs.UnbondedPool).Inv().Mul(amount) // (tokens/shares)^-1 * tokens - gs.UnbondedShares = gs.UnbondedShares.Add(issuedShares) + exRate = candidate.GlobalStakeShares / candidate.IssuedDelegatorShares - candidate.GlobalStakeShares = candidate.GlobalStakeShares.Add(issuedShares) - - issuedDelegatorShares = exRate.Mul(receivedGlobalShares) - candidate.IssuedDelegatorShares = candidate.IssuedDelegatorShares.Add(issuedDelegatorShares) - return + issuedDelegatorShares = issuedShares / exRate + candidate.IssuedDelegatorShares += issuedDelegatorShares + return issuedDelegatorShares exchangeRate(shares rational.Rat, tokenAmount int64): if shares.IsZero() then return rational.One - return shares.Inv().Mul(tokenAmount) + return tokenAmount / shares ``` ### TxUnbond + Delegator unbonding is defined with the following transaction: -``` golang -type TxUnbond struct { - PubKey crypto.PubKey - Shares rational.Rat +```golang +type TxUnbond struct { + PubKey crypto.PubKey + Shares rational.Rat +} + +unbond(tx TxUnbond): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil return + if bond.Shares < tx.Shares return + + bond.Shares -= tx.Shares + + candidate = loadCandidate(store, tx.PubKey) + + revokeCandidacy = false + if bond.Shares.IsZero() + if sender == candidate.Owner and candidate.Status != Revoked then revokeCandidacy = true then removeDelegatorBond(store, sender, bond) + else + saveDelegatorBond(store, sender, bond) + + if candidate.Status == Bonded + poolAccount = params.HoldBonded + else + poolAccount = params.HoldUnbonded + + returnedCoins = removeShares(candidate, shares) + + unbondDelegationElem = QueueElemUnbondDelegation(tx.PubKey, currentHeight(), sender, returnedCoins, startSlashRatio) + unbondDelegationQueue.add(unbondDelegationElem) + + transfer(poolAccount, unbondingPoolAddress, returnCoins) + + if revokeCandidacy + if candidate.Status == Bonded then bondedToUnbondedPool(candidate) + candidate.Status = Revoked + + if candidate.IssuedDelegatorShares.IsZero() + removeCandidate(store, tx.PubKey) + else + saveCandidate(store, candidate) + + saveGlobalState(store, gs) + return + +removeShares(candidate Candidate, shares rational.Rat): + globalPoolSharesToRemove = delegatorShareExRate(candidate) * shares + + if candidate.Status == Bonded + gs.BondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * globalPoolSharesToRemove + gs.BondedPool -= removedTokens + else + gs.UnbondedShares -= globalPoolSharesToRemove + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * globalPoolSharesToRemove + gs.UnbondedPool -= removedTokens + + candidate.GlobalStakeShares -= removedTokens + candidate.IssuedDelegatorShares -= shares + return returnedCoins + +delegatorShareExRate(candidate Candidate): + if candidate.IssuedDelegatorShares.IsZero() then return rational.One + return candidate.GlobalStakeShares / candidate.IssuedDelegatorShares + +bondedToUnbondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.BondedShares, gs.BondedPool) * candidate.GlobalStakeShares + gs.BondedShares -= candidate.GlobalStakeShares + gs.BondedPool -= removedTokens + + gs.UnbondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.UnbondedShares, gs.UnbondedPool) + gs.UnbondedShares += issuedShares + + candidate.GlobalStakeShares = issuedShares + candidate.Status = Unbonded + + return transfer(address of the bonded pool, address of the unbonded pool, removedTokens) +``` + +### TxRedelegate + +The re-delegation command allows delegators to switch validators while still +receiving equal reward to as if they had never unbonded. + +```golang +type TxRedelegate struct { + PubKeyFrom crypto.PubKey + PubKeyTo crypto.PubKey + Shares rational.Rat +} + +redelegate(tx TxRedelegate): + bond = loadDelegatorBond(store, sender, tx.PubKey) + if bond == nil then return + + if bond.Shares < tx.Shares return + candidate = loadCandidate(store, tx.PubKeyFrom) + if candidate == nil return + + candidate.RedelegatingShares += tx.Shares + reDelegationElem = QueueElemReDelegate(tx.PubKeyFrom, currentHeight(), sender, tx.Shares, tx.PubKeyTo) + redelegationQueue.add(reDelegationElem) + return +``` + +### TxLivelinessCheck + +Liveliness issues are calculated by keeping track of the block precommits in +the block header. A queue is persisted which contains the block headers from +all recent blocks for the duration of the unbonding period. A validator is +defined as having livliness issues if they have not been included in more than +33% of the blocks over: +* The most recent 24 Hours if they have >= 20% of global stake +* The most recent week if they have = 0% of global stake +* Linear interpolation of the above two scenarios + +Liveliness kicks are only checked when a `TxLivelinessCheck` transaction is +submitted. + +```golang +type TxLivelinessCheck struct { + PubKey crypto.PubKey + RewardAccount Addresss } ``` +If the `TxLivelinessCheck` is successful in kicking a validator, 5% of the +liveliness punishment is provided as a reward to `RewardAccount`. + +### TxProveLive + +If the validator was kicked for liveliness issues and is able to regain +liveliness then all delegators in the temporary unbonding pool which have not +transacted to move will be bonded back to the now-live validator and begin to +once again collect provisions and rewards. Regaining liveliness is demonstrated +by sending in a `TxProveLive` transaction: + +```golang +type TxProveLive struct { + PubKey crypto.PubKey +} ``` -unbond(tx TxUnbond): - // get delegator bond - bond = loadDelegatorBond(store, sender, tx.PubKey) - if bond == nil then return +### End of block handling - // subtract bond tokens from delegator bond - if bond.Shares.LT(tx.Shares) return // bond shares < tx shares - - bond.Shares = bond.Shares.Sub(ts.Shares) - - candidate = loadCandidate(store, tx.PubKey) - if candidate == nil return - - revokeCandidacy = false - if bond.Shares.IsZero() { - // if the bond is the owner of the candidate then trigger a revoke candidacy - if sender.Equals(candidate.Owner) and candidate.Status != Revoked then - revokeCandidacy = true - - // remove the bond - removeDelegatorBond(store, sender, tx.PubKey) - else - saveDelegatorBond(store, sender, bond) - - // transfer coins back to account - if candidate.Status == Bonded then - poolAccount = address of the bonded pool - else - poolAccount = address of the unbonded pool - - returnCoins = candidate.removeShares(shares, gs) - // TODO: Shouldn't it be created a queue element in this case? - transfer(poolAccount, sender, returnCoins) - - if revokeCandidacy then - // change the share types to unbonded if they were not already - if candidate.Status == Bonded then - // replace bonded shares with unbonded shares - tokens = gs.removeSharesBonded(candidate.GlobalStakeShares) - candidate.GlobalStakeShares = gs.addTokensUnbonded(tokens) - candidate.Status = Unbonded +```golang +tick(ctx Context): + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + + time = ctx.Time() + if time > gs.InflationLastTime + ProvisionTimeout + gs.InflationLastTime = time + gs.Inflation = nextInflation(hrsPerYr).Round(1000000000) - transfer(address of the bonded pool, address of the unbonded pool, tokens) - // lastly update the status - candidate.Status = Revoked + provisions = gs.Inflation * (gs.TotalSupply / hrsPerYr) + + gs.BondedPool += provisions + gs.TotalSupply += provisions + + saveGlobalState(store, gs) + + if time > unbondDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + transfer(unbondingQueueAddress, elem.Payout, elem.Tokens) + unbondDelegationQueue.remove(elem) + + if time > reDelegationQueue.head().InitTime + UnbondingPeriod + for each element elem in the unbondDelegationQueue where time > elem.InitTime + UnbondingPeriod do + candidate = getCandidate(store, elem.PubKey) + returnedCoins = removeShares(candidate, elem.Shares) + candidate.RedelegatingShares -= elem.Shares + delegateWithCandidate(TxDelegate(elem.NewCandidate, returnedCoins), candidate) + reDelegationQueue.remove(elem) + + return UpdateValidatorSet() - // deduct shares from the candidate and save - if candidate.GlobalStakeShares.IsZero() then - removeCandidate(store, tx.PubKey) - else - saveCandidate(store, candidate) +nextInflation(hrsPerYr rational.Rat): + if gs.TotalSupply > 0 + bondedRatio = gs.BondedPool / gs.TotalSupply + else + bondedRation = 0 + + inflationRateChangePerYear = (1 - bondedRatio / params.GoalBonded) * params.InflationRateChange + inflationRateChange = inflationRateChangePerYear / hrsPerYr - saveGlobalState(store, gs) - return + inflation = gs.Inflation + inflationRateChange + if inflation > params.InflationMax then inflation = params.InflationMax -removeDelegatorBond(candidate Candidate): + if inflation < params.InflationMin then inflation = params.InflationMin + + return inflation - // first remove from the list of bonds - pks = loadDelegatorCandidates(store, sender) - for i, pk := range pks { - if candidate.Equals(pk) { - pks = append(pks[:i], pks[i+1:]...) - } - } - b := wire.BinaryBytes(pks) - store.Set(GetDelegatorBondsKey(delegator), b) +UpdateValidatorSet(): + candidates = loadCandidates(store) - // now remove the actual bond - store.Remove(GetDelegatorBondKey(delegator, candidate)) - //updateDelegatorBonds(store, delegator) -} -``` + v1 = candidates.Validators() + v2 = updateVotingPower(candidates).Validators() -### Inflation provisions + change = v1.validatorsUpdated(v2) // determine all updated validators between two validator sets + return change -Validator provisions are minted on an hourly basis (the first block of a new -hour). The annual target of between 7% and 20%. The long-term target ratio of -bonded tokens to unbonded tokens is 67%. - -The target annual inflation rate is recalculated for each previsions cycle. The -inflation is also subject to a rate change (positive of negative) depending or -the distance from the desired ratio (67%). The maximum rate change possible is -defined to be 13% per year, however the annual inflation is capped as between -7% and 20%. +updateVotingPower(candidates Candidates): + foreach candidate in candidates do + candidate.VotingPower = (candidate.IssuedDelegatorShares - candidate.RedelegatingShares) * delegatorShareExRate(candidate) + + candidates.Sort() + + foreach candidate in candidates do + if candidate is not in the first params.MaxVals + candidate.VotingPower = rational.Zero + if candidate.Status == Bonded then bondedToUnbondedPool(candidate Candidate) + + else if candidate.Status == UnBonded then unbondedToBondedPool(candidate) + + saveCandidate(store, c) + + return candidates + +unbondedToBondedPool(candidate Candidate): + removedTokens = exchangeRate(gs.UnbondedShares, gs.UnbondedPool) * candidate.GlobalStakeShares + gs.UnbondedShares -= candidate.GlobalStakeShares + gs.UnbondedPool -= removedTokens + + gs.BondedPool += removedTokens + issuedShares = removedTokens / exchangeRate(gs.BondedShares, gs.BondedPool) + gs.BondedShares += issuedShares -``` -inflationRateChange(0) = 0 -GlobalState.Inflation(0) = 0.07 - -bondedRatio = GlobalState.BondedPool / GlobalState.TotalSupply -AnnualInflationRateChange = (1 - bondedRatio / 0.67) * 0.13 - -annualInflation += AnnualInflationRateChange - -if annualInflation > 0.20 then GlobalState.Inflation = 0.20 -if annualInflation < 0.07 then GlobalState.Inflation = 0.07 - -provisionTokensHourly = GlobalState.TotalSupply * GlobalState.Inflation / (365.25*24) -``` - -Because the validators hold a relative bonded share (`GlobalStakeShares`), when -more bonded tokens are added proportionally to all validators, the only term -which needs to be updated is the `GlobalState.BondedPool`. So for each previsions -cycle: - -``` -GlobalState.BondedPool += provisionTokensHourly + candidate.GlobalStakeShares = issuedShares + candidate.Status = Bonded + + return transfer(address of the unbonded pool, address of the bonded pool, removedTokens) ``` From 91d38384649d8adb655fe103b4a15f474d789710 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 28 Mar 2018 03:23:33 +0200 Subject: [PATCH 53/54] revert examples/basecoin --- examples/basecoin/x/cool/types.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/basecoin/x/cool/types.go b/examples/basecoin/x/cool/types.go index 76e7bb2920..a3fa6ca48e 100644 --- a/examples/basecoin/x/cool/types.go +++ b/examples/basecoin/x/cool/types.go @@ -34,15 +34,6 @@ func (msg SetTrendMsg) String() string { return fmt.Sprintf("SetTrendMsg{Sender: %v, Cool: %v}", msg.Sender, msg.Cool) } -// Get the bytes for the message signer to sign on -func (msg SetTrendMsg) GetSignBytes() []byte { - b, err := json.Marshal(msg) - if err != nil { - panic(err) - } - return b -} - // Validate Basic is used to quickly disqualify obviously invalid messages quickly func (msg SetTrendMsg) ValidateBasic() sdk.Error { if len(msg.Sender) == 0 { @@ -57,6 +48,15 @@ func (msg SetTrendMsg) ValidateBasic() sdk.Error { return nil } +// Get the bytes for the message signer to sign on +func (msg SetTrendMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + //_______________________________________________________________________ // A message type to quiz how cool you are. these fields are can be entirely From aa4bd0566825cb8b1e949be0a88d719b7d27e86d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 28 Mar 2018 17:08:22 +0200 Subject: [PATCH 54/54] Allow clearing candidacy fields (closes #723) --- x/stake/handler.go | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/x/stake/handler.go b/x/stake/handler.go index b52731aed0..7449141aa6 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -126,19 +126,11 @@ func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk } // XXX move to types - //check and edit any of the editable terms - if msg.Description.Moniker != "" { - candidate.Description.Moniker = msg.Description.Moniker - } - if msg.Description.Identity != "" { - candidate.Description.Identity = msg.Description.Identity - } - if msg.Description.Website != "" { - candidate.Description.Website = msg.Description.Website - } - if msg.Description.Details != "" { - candidate.Description.Details = msg.Description.Details - } + // replace all editable fields (clients should autofill existing values) + candidate.Description.Moniker = msg.Description.Moniker + candidate.Description.Identity = msg.Description.Identity + candidate.Description.Website = msg.Description.Website + candidate.Description.Details = msg.Description.Details k.setCandidate(ctx, candidate) return sdk.Result{}