working gaia store
This commit is contained in:
parent
266a8392d3
commit
c2ddc582c4
@ -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,
|
||||
)
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
@ -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) &&
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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())
|
||||
|
||||
}
|
||||
147
x/stake/tx.go
147
x/stake/tx.go
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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.
|
||||
Loading…
Reference in New Issue
Block a user