Merge pull request #540 from cosmos/rigel/staking

Staking Implementation
This commit is contained in:
Ethan Buchman 2018-03-28 13:08:27 -04:00 committed by GitHub
commit 2e5943e1bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 3790 additions and 6 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.DS_Store
*.swp
*.swo
vendor

2
Gopkg.lock generated
View File

@ -458,6 +458,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "0eb39694057c8ab8c9ecbaeb25bc43cbf1d2422976a09a67392a62dcef149a7b"
inputs-digest = "cce90fda84a63ae5b41b40f0edc357eec4020d17fdd61585960ad537418749ea"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -24,7 +24,7 @@ func validatorCommand() *cobra.Command {
return cmd
}
func getValidators(height *int64) ([]byte, error) {
func GetValidators(height *int64) ([]byte, error) {
// get the node
node, err := client.GetNode()
if err != nil {
@ -59,7 +59,7 @@ func printValidators(cmd *cobra.Command, args []string) error {
}
}
output, err := getValidators(height)
output, err := GetValidators(height)
if err != nil {
return err
}
@ -84,7 +84,7 @@ func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
return
}
output, err := getValidators(&height)
output, err := GetValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
@ -100,7 +100,7 @@ func LatestValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(err.Error()))
return
}
output, err := getValidators(&height)
output, err := GetValidators(&height)
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))

3
examples/gaia/README.md Normal file
View File

@ -0,0 +1,3 @@
Gaiad is the abci application, which can be run stand-alone, or in-process with tendermint.
Gaiacli is a client application, which connects to tendermint rpc, and sends transactions and queries the state. It uses light-client proofs to guarantee the results even if it doesn't have 100% trust in the node it connects to.

View File

@ -0,0 +1,131 @@
package main
import "github.com/spf13/cobra"
const (
// these are needed for every init
flagChainID = "chain-id"
flagNode = "node"
// one of the following should be provided to verify the connection
flagGenesis = "genesis"
flagCommit = "commit"
flagValHash = "validator-set"
flagSelect = "select"
flagTags = "tag"
flagAny = "any"
flagBind = "bind"
flagCORS = "cors"
flagTrustNode = "trust-node"
// this is for signing
flagName = "name"
)
var (
statusCmd = &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: todoNotImplemented,
}
)
// AddClientCommands returns a sub-tree of all basic client commands
//
// Call AddGetCommand and AddPostCommand to add custom txs and queries
func AddClientCommands(cmd *cobra.Command) {
cmd.AddCommand(
initClientCommand(),
statusCmd,
blockCommand(),
validatorCommand(),
lineBreak,
txSearchCommand(),
txCommand(),
lineBreak,
)
}
// GetCommands adds common flags to query commands
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().Bool(flagTrustNode, false, "Don't verify proofs for responses")
}
return cmds
}
// PostCommands adds common flags for commands to post tx
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().String(flagName, "", "Name of private key with which to sign")
c.Flags().String(flagPassword, "", "Password to use the named private key")
}
return cmds
}
func initClientCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize light client",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(flagNode, "n", "tcp://localhost:46657", "Node to connect to")
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}
func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block <height>",
Short: "Get verified data for a the block at given height",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
return cmd
}
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: todoNotImplemented,
}
return cmd
}
func serveCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
return cmd
}
func txSearchCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func txCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tx <hash>",
Short: "Matches this txhash over all committed blocks",
RunE: todoNotImplemented,
}
return cmd
}

View File

@ -0,0 +1,77 @@
package main
import "github.com/spf13/cobra"
const (
flagPassword = "password"
flagNewPassword = "new-password"
flagType = "type"
flagSeed = "seed"
flagDryRun = "dry-run"
)
var (
listKeysCmd = &cobra.Command{
Use: "list",
Short: "List all locally availably keys",
RunE: todoNotImplemented,
}
showKeysCmd = &cobra.Command{
Use: "show <name>",
Short: "Show key info for the given name",
RunE: todoNotImplemented,
}
)
// KeyCommands registers a sub-tree of commands to interact with
// local private key storage.
func KeyCommands() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Add or view local private keys",
}
cmd.AddCommand(
addKeyCommand(),
listKeysCmd,
showKeysCmd,
lineBreak,
deleteKeyCommand(),
updateKeyCommand(),
)
return cmd
}
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password to encrypt private key")
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
cmd.Flags().StringP(flagSeed, "s", "", "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
return cmd
}
func updateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Current password to decrypt key")
cmd.Flags().String(flagNewPassword, "", "New password to use to protect key")
return cmd
}
func deleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete the given key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password of existing key to delete")
return cmd
}

View File

@ -0,0 +1,77 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/version"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
)
// gaiacliCmd is the entry point for this binary
var (
gaiacliCmd = &cobra.Command{
Use: "gaiacli",
Short: "Gaia light-client",
}
lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
getAccountCmd = &cobra.Command{
Use: "account <address>",
Short: "Query account balance",
RunE: todoNotImplemented,
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func postSendCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "send",
Short: "Create and sign a send tx",
RunE: todoNotImplemented,
}
cmd.Flags().String(flagTo, "", "Address to send coins")
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
cmd.Flags().String(flagFee, "", "Fee to pay along with transaction")
return cmd
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// generic client commands
AddClientCommands(gaiacliCmd)
// query commands (custom to binary)
gaiacliCmd.AddCommand(
GetCommands(getAccountCmd)...)
// post tx commands (custom to binary)
gaiacliCmd.AddCommand(
PostCommands(postSendCommand())...)
// add proxy, version and key info
gaiacliCmd.AddCommand(
lineBreak,
serveCommand(),
KeyCommands(),
lineBreak,
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiacliCmd, "GA", os.ExpandEnv("$HOME/.gaiacli"))
executor.Execute()
}

View File

@ -0,0 +1,71 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
)
// gaiadCmd is the entry point for this binary
var (
gaiadCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
// defaultOptions sets up the app_options for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, error) {
addr, secret, err := server.GenerateCoinKey()
if err != nil {
return nil, err
}
fmt.Println("Secret phrase to access coins:")
fmt.Println(secret)
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), nil
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
// TODO: set this to something real
app := new(baseapp.BaseApp)
return app, nil
}
func main() {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "main")
gaiadCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiadCmd, "GA", os.ExpandEnv("$HOME/.gaiad"))
executor.Execute()
}

View File

@ -1,6 +1,9 @@
package types
import (
"encoding/hex"
"errors"
crypto "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
@ -8,6 +11,18 @@ import (
// Address in go-crypto style
type Address = cmn.HexBytes
// create an Address from a string
func GetAddress(address string) (addr Address, err error) {
if len(address) == 0 {
return addr, errors.New("must use provide address")
}
bz, err := hex.DecodeString(address)
if err != nil {
return nil, err
}
return Address(bz), nil
}
// Account is a standard account using a sequence number for replay protection
// and a pubkey for authentication.
type Account interface {

237
types/rational.go Normal file
View File

@ -0,0 +1,237 @@
package types
import (
"fmt"
"math/big"
"strconv"
"strings"
)
// "that's one big rat!"
// ______
// / / /\ \____oo
// __ /___...._____ _\o
// __| |_ |_
// Rat - extend big.Rat
// NOTE: never use new(Rat) or else
// we will panic unmarshalling into the
// nil embedded big.Rat
type Rat struct {
Num int64 `json:"num"`
Denom int64 `json:"denom"`
//*big.Rat `json:"rat"`
}
// RatInterface - big Rat with additional functionality
// NOTE: we only have one implementation of this interface
// and don't use it anywhere, but it might come in handy
// if we want to provide Rat types that include
// the units of the value in the type system.
//type RatInterface interface {
//GetRat() *big.Rat
//Num() int64
//Denom() int64
//GT(Rat) bool
//LT(Rat) bool
//Equal(Rat) bool
//IsZero() bool
//Inv() Rat
//Mul(Rat) Rat
//Quo(Rat) Rat
//Add(Rat) Rat
//Sub(Rat) Rat
//Round(int64) Rat
//Evaluate() int64
//}
//var _ Rat = Rat{} // enforce at compile time
// nolint - common values
var (
ZeroRat = NewRat(0) // Rat{big.NewRat(0, 1)}
OneRat = NewRat(1) // Rat{big.NewRat(1, 1)}
)
// New - create a new Rat from integers
//func NewRat(Numerator int64, Denominator ...int64) Rat {
//switch len(Denominator) {
//case 0:
//return Rat{big.NewRat(Numerator, 1)}
//case 1:
//return Rat{big.NewRat(Numerator, Denominator[0])}
//default:
//panic("improper use of New, can only have one denominator")
//}
//}
func NewRat(num int64, denom ...int64) Rat {
switch len(denom) {
case 0:
return Rat{
Num: num,
Denom: 1,
}
case 1:
return Rat{
Num: num,
Denom: denom[0],
}
default:
panic("improper use of New, can only have one denominator")
}
}
// create a rational from decimal string or integer string
func NewRatFromDecimal(decimalStr string) (f Rat, err Error) {
// first extract any negative symbol
neg := false
if string(decimalStr[0]) == "-" {
neg = true
decimalStr = decimalStr[1:]
}
str := strings.Split(decimalStr, ".")
var numStr string
var denom int64 = 1
switch len(str) {
case 1:
if len(str[0]) == 0 {
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0]
case 2:
if len(str[0]) == 0 || len(str[1]) == 0 {
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
numStr = str[0] + str[1]
len := int64(len(str[1]))
denom = new(big.Int).Exp(big.NewInt(10), big.NewInt(len), nil).Int64()
default:
return f, NewError(CodeUnknownRequest, "not a decimal string")
}
num, errConv := strconv.Atoi(numStr)
if errConv != nil {
return f, NewError(CodeUnknownRequest, errConv.Error())
}
if neg {
num *= -1
}
return NewRat(int64(num), denom), nil
}
//nolint
func ToRat(r *big.Rat) Rat { return NewRat(r.Num().Int64(), r.Denom().Int64()) } // GetRat - get big.Rat
func (r Rat) GetRat() *big.Rat { return big.NewRat(r.Num, r.Denom) } // GetRat - get big.Rat
func (r Rat) IsZero() bool { return r.Num == 0 } // IsZero - Is the Rat equal to zero
func (r Rat) Equal(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal
func (r Rat) GT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == 1 } // GT - greater than
func (r Rat) LT(r2 Rat) bool { return r.GetRat().Cmp(r2.GetRat()) == -1 } // LT - less than
func (r Rat) Inv() Rat { return ToRat(new(big.Rat).Inv(r.GetRat())) } // Inv - inverse
func (r Rat) Mul(r2 Rat) Rat { return ToRat(new(big.Rat).Mul(r.GetRat(), r2.GetRat())) } // Mul - multiplication
func (r Rat) Quo(r2 Rat) Rat { return ToRat(new(big.Rat).Quo(r.GetRat(), r2.GetRat())) } // Quo - quotient
func (r Rat) Add(r2 Rat) Rat { return ToRat(new(big.Rat).Add(r.GetRat(), r2.GetRat())) } // Add - addition
func (r Rat) Sub(r2 Rat) Rat { return ToRat(new(big.Rat).Sub(r.GetRat(), r2.GetRat())) } // Sub - subtraction
//func (r Rat) GetRat() *big.Rat { return r.Rat } // GetRat - get big.Rat
//func (r Rat) Num() int64 { return r.Rat.Num().Int64() } // Num - return the numerator
//func (r Rat) Denom() int64 { return r.Rat.Denom().Int64() } // Denom - return the denominator
//func (r Rat) IsZero() bool { return r.Num() == 0 } // IsZero - Is the Rat equal to zero
//func (r Rat) Equal(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 0 } // Equal - rationals are equal
//func (r Rat) GT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == 1 } // GT - greater than
//func (r Rat) LT(r2 Rat) bool { return r.Rat.Cmp(r2.GetRat()) == -1 } // LT - less than
//func (r Rat) Inv() Rat { return Rat{new(big.Rat).Inv(r.Rat)} } // Inv - inverse
//func (r Rat) Mul(r2 Rat) Rat { return Rat{new(big.Rat).Mul(r.Rat, r2.GetRat())} } // Mul - multiplication
//func (r Rat) Quo(r2 Rat) Rat { return Rat{new(big.Rat).Quo(r.Rat, r2.GetRat())} } // Quo - quotient
//func (r Rat) Add(r2 Rat) Rat { return Rat{new(big.Rat).Add(r.Rat, r2.GetRat())} } // Add - addition
//func (r Rat) Sub(r2 Rat) Rat { return Rat{new(big.Rat).Sub(r.Rat, r2.GetRat())} } // Sub - subtraction
//func (r Rat) String() string { return fmt.Sprintf("%v/%v", r.Num(), r.Denom()) } // Sub - subtraction
var (
zero = big.NewInt(0)
one = big.NewInt(1)
two = big.NewInt(2)
five = big.NewInt(5)
nFive = big.NewInt(-5)
ten = big.NewInt(10)
)
// evaluate the rational using bankers rounding
func (r Rat) EvaluateBig() *big.Int {
num := r.GetRat().Num()
denom := r.GetRat().Denom()
d, rem := new(big.Int), new(big.Int)
d.QuoRem(num, denom, rem)
if rem.Cmp(zero) == 0 { // is the remainder zero
return d
}
// evaluate the remainder using bankers rounding
tenNum := new(big.Int).Mul(num, ten)
tenD := new(big.Int).Mul(d, ten)
remainderDigit := new(big.Int).Sub(new(big.Int).Quo(tenNum, denom), tenD) // get the first remainder digit
isFinalDigit := (new(big.Int).Rem(tenNum, denom).Cmp(zero) == 0) // is this the final digit in the remainder?
switch {
case isFinalDigit && (remainderDigit.Cmp(five) == 0 || remainderDigit.Cmp(nFive) == 0):
dRem2 := new(big.Int).Rem(d, two)
return new(big.Int).Add(d, dRem2) // always rounds to the even number
case remainderDigit.Cmp(five) != -1: //remainderDigit >= 5:
d.Add(d, one)
case remainderDigit.Cmp(nFive) != 1: //remainderDigit <= -5:
d.Sub(d, one)
}
return d
}
// evaluate the rational using bankers rounding
func (r Rat) Evaluate() int64 {
return r.EvaluateBig().Int64()
}
// round Rat with the provided precisionFactor
func (r Rat) Round(precisionFactor int64) Rat {
rTen := ToRat(new(big.Rat).Mul(r.GetRat(), big.NewRat(precisionFactor, 1)))
return ToRat(big.NewRat(rTen.Evaluate(), precisionFactor))
}
// TODO panic if negative or if totalDigits < len(initStr)???
// evaluate as an integer and return left padded string
func (r Rat) ToLeftPadded(totalDigits int8) string {
intStr := r.EvaluateBig().String()
fcode := `%0` + strconv.Itoa(int(totalDigits)) + `s`
return fmt.Sprintf(fcode, intStr)
}
//___________________________________________________________________________________
// Hack to just use json.Marshal for everything until
// we update for amino
//type JSONCodec struct{}
//func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) { return json.Marshal(o) }
//func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error { return json.Unmarshal(bz, o) }
// Wraps r.MarshalText() in quotes to make it a valid JSON string.
//func (r Rat) MarshalAmino() (string, error) {
//bz, err := r.MarshalText()
//if err != nil {
//return "", err
//}
//return fmt.Sprintf(`%s`, bz), nil
//}
//// Requires a valid JSON string - strings quotes and calls UnmarshalText
//func (r *Rat) UnmarshalAmino(data string) (err error) {
////quote := []byte(`"`)
////if len(data) < 2 ||
////!bytes.HasPrefix(data, quote) ||
////!bytes.HasSuffix(data, quote) {
////return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string")
////}
////data = bytes.Trim(data, `"`)
//return r.UnmarshalText([]byte(data))
//}

277
types/rational_test.go Normal file
View File

@ -0,0 +1,277 @@
package types
import (
"math/big"
"testing"
wire "github.com/cosmos/cosmos-sdk/wire"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNew(t *testing.T) {
assert.Equal(t, NewRat(1), NewRat(1, 1))
assert.Equal(t, NewRat(100), NewRat(100, 1))
assert.Equal(t, NewRat(-1), NewRat(-1, 1))
assert.Equal(t, NewRat(-100), NewRat(-100, 1))
assert.Equal(t, NewRat(0), NewRat(0, 1))
// do not allow for more than 2 variables
assert.Panics(t, func() { NewRat(1, 1, 1) })
}
func TestNewFromDecimal(t *testing.T) {
tests := []struct {
decimalStr string
expErr bool
exp Rat
}{
{"0", false, NewRat(0)},
{"1", false, NewRat(1)},
{"1.1", false, NewRat(11, 10)},
{"0.75", false, NewRat(3, 4)},
{"0.8", false, NewRat(4, 5)},
{"0.11111", false, NewRat(11111, 100000)},
{".", true, Rat{}},
{".0", true, Rat{}},
{"1.", true, Rat{}},
{"foobar", true, Rat{}},
{"0.foobar", true, Rat{}},
{"0.foobar.", true, Rat{}},
}
for _, tc := range tests {
res, err := NewRatFromDecimal(tc.decimalStr)
if tc.expErr {
assert.NotNil(t, err, tc.decimalStr)
} else {
assert.Nil(t, err)
assert.True(t, res.Equal(tc.exp))
}
// negative tc
res, err = NewRatFromDecimal("-" + tc.decimalStr)
if tc.expErr {
assert.NotNil(t, err, tc.decimalStr)
} else {
assert.Nil(t, err)
assert.True(t, res.Equal(tc.exp.Mul(NewRat(-1))))
}
}
}
func TestEqualities(t *testing.T) {
tests := []struct {
r1, r2 Rat
gt, lt, eq bool
}{
{NewRat(0), NewRat(0), false, false, true},
{NewRat(0, 100), NewRat(0, 10000), false, false, true},
{NewRat(100), NewRat(100), false, false, true},
{NewRat(-100), NewRat(-100), false, false, true},
{NewRat(-100, -1), NewRat(100), false, false, true},
{NewRat(-1, 1), NewRat(1, -1), false, false, true},
{NewRat(1, -1), NewRat(-1, 1), false, false, true},
{NewRat(3, 7), NewRat(3, 7), false, false, true},
{NewRat(0), NewRat(3, 7), false, true, false},
{NewRat(0), NewRat(100), false, true, false},
{NewRat(-1), NewRat(3, 7), false, true, false},
{NewRat(-1), NewRat(100), false, true, false},
{NewRat(1, 7), NewRat(100), false, true, false},
{NewRat(1, 7), NewRat(3, 7), false, true, false},
{NewRat(-3, 7), NewRat(-1, 7), false, true, false},
{NewRat(3, 7), NewRat(0), true, false, false},
{NewRat(100), NewRat(0), true, false, false},
{NewRat(3, 7), NewRat(-1), true, false, false},
{NewRat(100), NewRat(-1), true, false, false},
{NewRat(100), NewRat(1, 7), true, false, false},
{NewRat(3, 7), NewRat(1, 7), true, false, false},
{NewRat(-1, 7), NewRat(-3, 7), true, false, false},
}
for _, tc := range tests {
assert.Equal(t, tc.gt, tc.r1.GT(tc.r2))
assert.Equal(t, tc.lt, tc.r1.LT(tc.r2))
assert.Equal(t, tc.eq, tc.r1.Equal(tc.r2))
}
}
func TestArithmatic(t *testing.T) {
tests := []struct {
r1, r2 Rat
resMul, resDiv, resAdd, resSub Rat
}{
// r1 r2 MUL DIV ADD SUB
{NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0), NewRat(0)},
{NewRat(1), NewRat(0), NewRat(0), NewRat(0), NewRat(1), NewRat(1)},
{NewRat(0), NewRat(1), NewRat(0), NewRat(0), NewRat(1), NewRat(-1)},
{NewRat(0), NewRat(-1), NewRat(0), NewRat(0), NewRat(-1), NewRat(1)},
{NewRat(-1), NewRat(0), NewRat(0), NewRat(0), NewRat(-1), NewRat(-1)},
{NewRat(1), NewRat(1), NewRat(1), NewRat(1), NewRat(2), NewRat(0)},
{NewRat(-1), NewRat(-1), NewRat(1), NewRat(1), NewRat(-2), NewRat(0)},
{NewRat(1), NewRat(-1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(2)},
{NewRat(-1), NewRat(1), NewRat(-1), NewRat(-1), NewRat(0), NewRat(-2)},
{NewRat(3), NewRat(7), NewRat(21), NewRat(3, 7), NewRat(10), NewRat(-4)},
{NewRat(2), NewRat(4), NewRat(8), NewRat(1, 2), NewRat(6), NewRat(-2)},
{NewRat(100), NewRat(100), NewRat(10000), NewRat(1), NewRat(200), NewRat(0)},
{NewRat(3, 2), NewRat(3, 2), NewRat(9, 4), NewRat(1), NewRat(3), NewRat(0)},
{NewRat(3, 7), NewRat(7, 3), NewRat(1), NewRat(9, 49), NewRat(58, 21), NewRat(-40, 21)},
{NewRat(1, 21), NewRat(11, 5), NewRat(11, 105), NewRat(5, 231), NewRat(236, 105), NewRat(-226, 105)},
{NewRat(-21), NewRat(3, 7), NewRat(-9), NewRat(-49), NewRat(-144, 7), NewRat(-150, 7)},
{NewRat(100), NewRat(1, 7), NewRat(100, 7), NewRat(700), NewRat(701, 7), NewRat(699, 7)},
}
for _, tc := range tests {
assert.True(t, tc.resMul.Equal(tc.r1.Mul(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat())
assert.True(t, tc.resAdd.Equal(tc.r1.Add(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat())
assert.True(t, tc.resSub.Equal(tc.r1.Sub(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat())
if tc.r2.Num == 0 { // panic for divide by zero
assert.Panics(t, func() { tc.r1.Quo(tc.r2) })
} else {
assert.True(t, tc.resDiv.Equal(tc.r1.Quo(tc.r2)), "r1 %v, r2 %v", tc.r1.GetRat(), tc.r2.GetRat())
}
}
}
func TestEvaluate(t *testing.T) {
tests := []struct {
r1 Rat
res int64
}{
{NewRat(0), 0},
{NewRat(1), 1},
{NewRat(1, 4), 0},
{NewRat(1, 2), 0},
{NewRat(3, 4), 1},
{NewRat(5, 6), 1},
{NewRat(3, 2), 2},
{NewRat(5, 2), 2},
{NewRat(6, 11), 1}, // 0.545-> 1 even though 5 is first decimal and 1 not even
{NewRat(17, 11), 2}, // 1.545
{NewRat(5, 11), 0},
{NewRat(16, 11), 1},
{NewRat(113, 12), 9},
}
for _, tc := range tests {
assert.Equal(t, tc.res, tc.r1.Evaluate(), "%v", tc.r1)
assert.Equal(t, tc.res*-1, tc.r1.Mul(NewRat(-1)).Evaluate(), "%v", tc.r1.Mul(NewRat(-1)))
}
}
func TestRound(t *testing.T) {
many3 := "333333333333333333333333333333333333333333333"
many7 := "777777777777777777777777777777777777777777777"
big3, worked := new(big.Int).SetString(many3, 10)
require.True(t, worked)
big7, worked := new(big.Int).SetString(many7, 10)
require.True(t, worked)
tests := []struct {
r, res Rat
precFactor int64
}{
{NewRat(333, 777), NewRat(429, 1000), 1000},
{ToRat(new(big.Rat).SetFrac(big3, big7)), NewRat(429, 1000), 1000},
{ToRat(new(big.Rat).SetFrac(big3, big7)), ToRat(big.NewRat(4285714286, 10000000000)), 10000000000},
{NewRat(1, 2), NewRat(1, 2), 1000},
}
for _, tc := range tests {
assert.Equal(t, tc.res, tc.r.Round(tc.precFactor), "%v", tc.r)
negR1, negRes := tc.r.Mul(NewRat(-1)), tc.res.Mul(NewRat(-1))
assert.Equal(t, negRes, negR1.Round(tc.precFactor), "%v", negR1)
}
}
func TestToLeftPadded(t *testing.T) {
tests := []struct {
rat Rat
digits int8
res string
}{
{NewRat(100, 3), 8, "00000033"},
{NewRat(1, 3), 8, "00000000"},
{NewRat(100, 2), 8, "00000050"},
{NewRat(1000, 3), 8, "00000333"},
{NewRat(1000, 3), 12, "000000000333"},
}
for _, tc := range tests {
assert.Equal(t, tc.res, tc.rat.ToLeftPadded(tc.digits))
}
}
//func TestZeroSerializationJSON(t *testing.T) {
//r := NewRat(0, 1)
//err := r.UnmarshalJSON([]byte(`"0/1"`))
//assert.Nil(t, err)
//err = r.UnmarshalJSON([]byte(`"0/0"`))
//assert.NotNil(t, err)
//err = r.UnmarshalJSON([]byte(`"1/0"`))
//assert.NotNil(t, err)
//err = r.UnmarshalJSON([]byte(`"{}"`))
//assert.NotNil(t, err)
//}
//func TestSerializationJSON(t *testing.T) {
//r := NewRat(1, 3)
//bz, err := r.MarshalText()
//require.Nil(t, err)
//r2 := NewRat(0, 1)
//err = r2.UnmarshalText(bz)
//require.Nil(t, err)
//assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
//}
var cdc = wire.NewCodec() //var jsonCdc JSONCodec // TODO wire.Codec
func TestSerializationGoWire(t *testing.T) {
r := NewRat(1, 3)
bz, err := cdc.MarshalBinary(r)
require.Nil(t, err)
//str, err := r.MarshalJSON()
//require.Nil(t, err)
r2 := NewRat(0, 1)
err = cdc.UnmarshalBinary([]byte(bz), &r2)
//panic(fmt.Sprintf("debug bz: %v\n", string(bz)))
require.Nil(t, err)
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
}
type testEmbedStruct struct {
Field1 string `json:"f1"`
Field2 int `json:"f2"`
Field3 Rat `json:"f3"`
}
func TestEmbeddedStructSerializationGoWire(t *testing.T) {
obj := testEmbedStruct{"foo", 10, NewRat(1, 3)}
bz, err := cdc.MarshalBinary(obj)
require.Nil(t, err)
var obj2 testEmbedStruct
obj2.Field3 = NewRat(0, 1) // ... needs to be initialized
err = cdc.UnmarshalBinary(bz, &obj2)
require.Nil(t, err)
assert.Equal(t, obj.Field1, obj2.Field1)
assert.Equal(t, obj.Field2, obj2.Field2)
assert.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2)
}

View File

@ -91,6 +91,7 @@ func NewStdFee(gas int64, amount ...Coin) StdFee {
}
}
// fee bytes for signing later
func (fee StdFee) Bytes() []byte {
// normalize. XXX
// this is a sign of something ugly
@ -147,6 +148,7 @@ type StdSignMsg struct {
// XXX: Alt
}
// get message bytes
func (msg StdSignMsg) Bytes() []byte {
return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg)
}
@ -171,6 +173,7 @@ func NewTestMsg(addrs ...Address) *TestMsg {
}
}
//nolint
func (msg *TestMsg) Type() string { return "TestMsg" }
func (msg *TestMsg) Get(key interface{}) (value interface{}) { return nil }
func (msg *TestMsg) GetSignBytes() []byte {

View File

@ -165,7 +165,6 @@ func (am accountMapper) decodeAccount(bz []byte) sdk.Account {
accI := oldwire.ReadBinary(struct{ sdk.Account }{}, r, len(bz), n, err)
if *err != nil {
panic(*err)
}
acc := accI.(struct{ sdk.Account }).Account

201
x/stake/commands/query.go Normal file
View File

@ -0,0 +1,201 @@
package commands
import (
"encoding/hex"
"encoding/json"
"fmt"
"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/builder"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire" // XXX fix
"github.com/cosmos/cosmos-sdk/x/stake"
)
// XXX remove dependancy
func PrefixedKey(app string, key []byte) []byte {
prefix := append([]byte(app), byte(0))
return append(prefix, key...)
}
//nolint
var (
fsValAddr = flag.NewFlagSet("", flag.ContinueOnError)
fsDelAddr = flag.NewFlagSet("", flag.ContinueOnError)
FlagValidatorAddr = "address"
FlagDelegatorAddr = "delegator-address"
)
func init() {
//Add Flags
fsValAddr.String(FlagValidatorAddr, "", "Address of the validator/candidate")
fsDelAddr.String(FlagDelegatorAddr, "", "Delegator hex address")
}
// create command to query for all candidates
func GetCmdQueryCandidates(cdc *wire.Codec, storeName string) *cobra.Command {
cmd := &cobra.Command{
Use: "candidates",
Short: "Query for the set of validator-candidates pubkeys",
RunE: func(cmd *cobra.Command, args []string) error {
key := PrefixedKey(stake.MsgType, stake.CandidatesKey)
res, err := builder.Query(key, storeName)
if err != nil {
return err
}
// parse out the candidates
candidates := new(stake.Candidates)
err = cdc.UnmarshalJSON(res, candidates)
if err != nil {
return err
}
output, err := json.MarshalIndent(candidates, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
cmd.Flags().AddFlagSet(fsDelAddr)
return cmd
}
// get the command to query a candidate
func GetCmdQueryCandidate(cdc *wire.Codec, storeName string) *cobra.Command {
cmd := &cobra.Command{
Use: "candidate",
Short: "Query a validator-candidate account",
RunE: func(cmd *cobra.Command, args []string) error {
addr, err := sdk.GetAddress(viper.GetString(FlagValidatorAddr))
if err != nil {
return err
}
key := PrefixedKey(stake.MsgType, stake.GetCandidateKey(addr))
res, err := builder.Query(key, storeName)
if err != nil {
return err
}
// parse out the candidate
candidate := new(stake.Candidate)
err = cdc.UnmarshalBinary(res, candidate)
if err != nil {
return err
}
output, err := json.MarshalIndent(candidate, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
cmd.Flags().AddFlagSet(fsValAddr)
return cmd
}
// get the command to query a single delegator bond
func GetCmdQueryDelegatorBond(cdc *wire.Codec, storeName string) *cobra.Command {
cmd := &cobra.Command{
Use: "delegator-bond",
Short: "Query a delegators bond based on address and candidate pubkey",
RunE: func(cmd *cobra.Command, args []string) error {
addr, err := sdk.GetAddress(viper.GetString(FlagValidatorAddr))
if err != nil {
return err
}
bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddr))
if err != nil {
return err
}
delegator := crypto.Address(bz)
key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondKey(delegator, addr, cdc))
res, err := builder.Query(key, storeName)
if err != nil {
return err
}
// parse out the bond
var bond stake.DelegatorBond
err = cdc.UnmarshalBinary(res, bond)
if err != nil {
return err
}
output, err := json.MarshalIndent(bond, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
cmd.Flags().AddFlagSet(fsValAddr)
cmd.Flags().AddFlagSet(fsDelAddr)
return cmd
}
// get the command to query all the candidates bonded to a delegator
func GetCmdQueryDelegatorBonds(cdc *wire.Codec, storeName string) *cobra.Command {
cmd := &cobra.Command{
Use: "delegator-candidates",
Short: "Query all delegators candidates' pubkeys based on address",
RunE: func(cmd *cobra.Command, args []string) error {
bz, err := hex.DecodeString(viper.GetString(FlagDelegatorAddr))
if err != nil {
return err
}
delegator := crypto.Address(bz)
key := PrefixedKey(stake.MsgType, stake.GetDelegatorBondsKey(delegator, cdc))
res, err := builder.Query(key, storeName)
if err != nil {
return err
}
// parse out the candidates list
var candidates []crypto.PubKey
err = cdc.UnmarshalBinary(res, candidates)
if err != nil {
return err
}
output, err := json.MarshalIndent(candidates, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
// TODO output with proofs / machine parseable etc.
},
}
cmd.Flags().AddFlagSet(fsDelAddr)
return cmd
}

255
x/stake/commands/tx.go Normal file
View File

@ -0,0 +1,255 @@
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/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/builder"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/stake"
)
// nolint
const (
FlagAddressDelegator = "addressD"
FlagAddressCandidate = "addressC"
FlagPubKey = "pubkey"
FlagAmount = "amount"
FlagShares = "shares"
FlagMoniker = "moniker"
FlagIdentity = "keybase-sig"
FlagWebsite = "website"
FlagDetails = "details"
)
// common flagsets to add to various functions
var (
fsPk = flag.NewFlagSet("", flag.ContinueOnError)
fsAmount = flag.NewFlagSet("", flag.ContinueOnError)
fsShares = flag.NewFlagSet("", flag.ContinueOnError)
fsCandidate = flag.NewFlagSet("", flag.ContinueOnError)
fsDelegator = flag.NewFlagSet("", flag.ContinueOnError)
)
func init() {
fsPk.String(FlagPubKey, "", "PubKey of the validator-candidate")
fsAmount.String(FlagAmount, "1fermion", "Amount of coins to bond")
fsShares.String(FlagShares, "", "Amount of shares to unbond, either in decimal or keyword MAX (ex. 1.23456789, 99, MAX)")
fsCandidate.String(FlagMoniker, "", "validator-candidate name")
fsCandidate.String(FlagIdentity, "", "optional keybase signature")
fsCandidate.String(FlagWebsite, "", "optional website")
fsCandidate.String(FlagAddressCandidate, "", "hex address of the validator/candidate")
fsDelegator.String(FlagAddressDelegator, "", "hex address of the delegator")
}
//TODO refactor to common functionality
func getNamePassword() (name, passphrase string, err error) {
name = viper.GetString(client.FlagName)
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err = client.GetPassword(prompt, buf)
return
}
//_________________________________________________________________________________________
// create declare candidacy command
func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "declare-candidacy",
Short: "create new validator-candidate account and delegate some coins to it",
RunE: func(cmd *cobra.Command, args []string) error {
amount, err := sdk.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
return err
}
candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate))
if err != nil {
return err
}
pk, err := GetPubKey(viper.GetString(FlagPubKey))
if err != nil {
return err
}
if viper.GetString(FlagMoniker) == "" {
return fmt.Errorf("please enter a moniker for the validator-candidate using --moniker")
}
description := stake.Description{
Moniker: viper.GetString(FlagMoniker),
Identity: viper.GetString(FlagIdentity),
Website: viper.GetString(FlagWebsite),
Details: viper.GetString(FlagDetails),
}
msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsPk)
cmd.Flags().AddFlagSet(fsAmount)
cmd.Flags().AddFlagSet(fsCandidate)
return cmd
}
// create edit candidacy command
func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "edit-candidacy",
Short: "edit and existing validator-candidate account",
RunE: func(cmd *cobra.Command, args []string) error {
candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate))
if err != nil {
return err
}
description := stake.Description{
Moniker: viper.GetString(FlagMoniker),
Identity: viper.GetString(FlagIdentity),
Website: viper.GetString(FlagWebsite),
Details: viper.GetString(FlagDetails),
}
msg := stake.NewMsgEditCandidacy(candidateAddr, description)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsPk)
cmd.Flags().AddFlagSet(fsCandidate)
return cmd
}
// create edit candidacy command
func GetCmdDelegate(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "delegate",
Short: "delegate coins to an existing validator/candidate",
RunE: func(cmd *cobra.Command, args []string) error {
amount, err := sdk.ParseCoin(viper.GetString(FlagAmount))
if err != nil {
return err
}
delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator))
candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate))
if err != nil {
return err
}
msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsPk)
cmd.Flags().AddFlagSet(fsAmount)
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}
// create edit candidacy command
func GetCmdUnbond(cdc *wire.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "unbond",
Short: "unbond coins from a validator/candidate",
RunE: func(cmd *cobra.Command, args []string) error {
// check the shares before broadcasting
sharesStr := viper.GetString(FlagShares)
var shares sdk.Rat
if sharesStr != "MAX" {
var err error
shares, err = sdk.NewRatFromDecimal(sharesStr)
if err != nil {
return err
}
if !shares.GT(sdk.ZeroRat) {
return fmt.Errorf("shares must be positive integer or decimal (ex. 123, 1.23456789)")
}
}
delegatorAddr, err := sdk.GetAddress(viper.GetString(FlagAddressDelegator))
candidateAddr, err := sdk.GetAddress(viper.GetString(FlagAddressCandidate))
if err != nil {
return err
}
msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr)
// build and sign the transaction, then broadcast to Tendermint
name := viper.GetString(client.FlagName)
res, err := builder.SignBuildBroadcast(name, msg, cdc)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
},
}
cmd.Flags().AddFlagSet(fsPk)
cmd.Flags().AddFlagSet(fsShares)
cmd.Flags().AddFlagSet(fsDelegator)
return cmd
}
//______________________________________________________________________________________
// create the pubkey from a pubkey string
// TODO move to a better reusable place
func GetPubKey(pubKeyStr string) (pk crypto.PubKey, err error) {
if len(pubKeyStr) == 0 {
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
}

117
x/stake/errors.go Normal file
View File

@ -0,0 +1,117 @@
// nolint
package stake
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
type CodeType = sdk.CodeType
const (
// Gaia errors reserve 200 ~ 299.
CodeInvalidValidator CodeType = 201
CodeInvalidCandidate CodeType = 202
CodeInvalidBond CodeType = 203
CodeInvalidInput CodeType = 204
CodeUnauthorized CodeType = sdk.CodeUnauthorized
CodeInternal CodeType = sdk.CodeInternal
CodeUnknownRequest CodeType = sdk.CodeUnknownRequest
)
// NOTE: Don't stringer this, we'll put better messages in later.
func codeToDefaultMsg(code CodeType) string {
switch code {
case CodeInvalidValidator:
return "Invalid Validator"
case CodeInvalidCandidate:
return "Invalid Candidate"
case CodeInvalidBond:
return "Invalid Bond"
case CodeInvalidInput:
return "Invalid Input"
case CodeUnauthorized:
return "Unauthorized"
case CodeInternal:
return "Internal Error"
case CodeUnknownRequest:
return "Unknown request"
default:
return sdk.CodeToDefaultMsg(code)
}
}
//----------------------------------------
// Error constructors
func ErrNotEnoughBondShares(shares string) sdk.Error {
return newError(CodeInvalidBond, fmt.Sprintf("not enough shares only have %v", shares))
}
func ErrCandidateEmpty() sdk.Error {
return newError(CodeInvalidValidator, "Cannot bond to an empty candidate")
}
func ErrBadBondingDenom() sdk.Error {
return newError(CodeInvalidBond, "Invalid coin denomination")
}
func ErrBadBondingAmount() sdk.Error {
return newError(CodeInvalidBond, "Amount must be > 0")
}
func ErrNoBondingAcct() sdk.Error {
return newError(CodeInvalidValidator, "No bond account for this (address, validator) pair")
}
func ErrCommissionNegative() sdk.Error {
return newError(CodeInvalidValidator, "Commission must be positive")
}
func ErrCommissionHuge() sdk.Error {
return newError(CodeInvalidValidator, "Commission cannot be more than 100%")
}
func ErrBadValidatorAddr() sdk.Error {
return newError(CodeInvalidValidator, "Validator does not exist for that address")
}
func ErrBadCandidateAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate does not exist for that address")
}
func ErrBadDelegatorAddr() sdk.Error {
return newError(CodeInvalidValidator, "Delegator does not exist for that address")
}
func ErrCandidateExistsAddr() sdk.Error {
return newError(CodeInvalidValidator, "Candidate already exist, cannot re-declare candidacy")
}
func ErrMissingSignature() sdk.Error {
return newError(CodeInvalidValidator, "Missing signature")
}
func ErrBondNotNominated() sdk.Error {
return newError(CodeInvalidValidator, "Cannot bond to non-nominated account")
}
func ErrNoCandidateForAddress() sdk.Error {
return newError(CodeInvalidValidator, "Validator does not exist for that address")
}
func ErrNoDelegatorForAddress() sdk.Error {
return newError(CodeInvalidValidator, "Delegator does not contain validator bond")
}
func ErrInsufficientFunds() sdk.Error {
return newError(CodeInvalidInput, "Insufficient bond shares")
}
func ErrBadShares() sdk.Error {
return newError(CodeInvalidInput, "bad shares provided as input, must be MAX or decimal")
}
func ErrBadRemoveValidator() sdk.Error {
return newError(CodeInvalidValidator, "Error removing validator")
}
//----------------------------------------
// TODO group with code from x/bank/errors.go
func msgOrDefaultMsg(msg string, code CodeType) string {
if msg != "" {
return msg
}
return codeToDefaultMsg(code)
}
func newError(code CodeType, msg string) sdk.Error {
msg = msgOrDefaultMsg(msg, code)
return sdk.NewError(code, msg)
}

304
x/stake/handler.go Normal file
View File

@ -0,0 +1,304 @@
package stake
import (
"bytes"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/bank"
)
//nolint
const (
GasDeclareCandidacy int64 = 20
GasEditCandidacy int64 = 20
GasDelegate int64 = 20
GasUnbond int64 = 20
)
//XXX fix initstater
// separated for testing
//func InitState(ctx sdk.Context, k Keeper, key, value string) sdk.Error {
//params := k.GetParams(ctx)
//switch key {
//case "allowed_bond_denom":
//params.BondDenom = value
//case "max_vals", "gas_bond", "gas_unbond":
//i, err := strconv.Atoi(value)
//if err != nil {
//return sdk.ErrUnknownRequest(fmt.Sprintf("input must be integer, Error: %v", err.Error()))
//}
//switch key {
//case "max_vals":
//if i < 0 {
//return sdk.ErrUnknownRequest("cannot designate negative max validators")
//}
//params.MaxValidators = uint16(i)
//case "gas_bond":
//GasDelegate = int64(i)
//case "gas_unbound":
//GasUnbond = int64(i)
//}
//default:
//return sdk.ErrUnknownRequest(key)
//}
//k.setParams(params)
//return nil
//}
//_______________________________________________________________________
func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
// NOTE msg already has validate basic run
switch msg := msg.(type) {
case MsgDeclareCandidacy:
return handleMsgDeclareCandidacy(ctx, msg, k)
case MsgEditCandidacy:
return handleMsgEditCandidacy(ctx, msg, k)
case MsgDelegate:
return handleMsgDelegate(ctx, msg, k)
case MsgUnbond:
return handleMsgUnbond(ctx, msg, k)
default:
return sdk.ErrTxDecode("invalid message parse in staking module").Result()
}
}
}
//_____________________________________________________________________
// XXX should be send in the msg (init in CLI)
//func getSender() sdk.Address {
//signers := msg.GetSigners()
//if len(signers) != 1 {
//return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result()
//}
//sender := signers[0]
//}
//_____________________________________________________________________
// These functions assume everything has been authenticated,
// now we just perform action and save
func handleMsgDeclareCandidacy(ctx sdk.Context, msg MsgDeclareCandidacy, k Keeper) sdk.Result {
// check to see if the pubkey or sender has been registered before
_, found := k.GetCandidate(ctx, msg.CandidateAddr)
if found {
return ErrCandidateExistsAddr().Result()
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
return ErrBadBondingDenom().Result()
}
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDeclareCandidacy,
}
}
candidate := NewCandidate(msg.CandidateAddr, msg.PubKey, msg.Description)
k.setCandidate(ctx, candidate)
// move coins from the msg.Address account to a (self-bond) delegator account
// the candidate account and global shares are updated within here
return delegateWithCandidate(ctx, k, msg.CandidateAddr, msg.Bond, candidate).Result()
}
func handleMsgEditCandidacy(ctx sdk.Context, msg MsgEditCandidacy, k Keeper) sdk.Result {
// candidate must already be registered
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
if !found {
return ErrBadCandidateAddr().Result()
}
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasEditCandidacy,
}
}
if candidate.Status == Unbonded { //candidate has been withdrawn
return ErrBondNotNominated().Result()
}
// XXX move to types
// replace all editable fields (clients should autofill existing values)
candidate.Description.Moniker = msg.Description.Moniker
candidate.Description.Identity = msg.Description.Identity
candidate.Description.Website = msg.Description.Website
candidate.Description.Details = msg.Description.Details
k.setCandidate(ctx, candidate)
return sdk.Result{}
}
func handleMsgDelegate(ctx sdk.Context, msg MsgDelegate, k Keeper) sdk.Result {
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
if !found {
return ErrBadCandidateAddr().Result()
}
if msg.Bond.Denom != k.GetParams(ctx).BondDenom {
return ErrBadBondingDenom().Result()
}
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasDelegate,
}
}
return delegateWithCandidate(ctx, k, msg.DelegatorAddr, msg.Bond, candidate).Result()
}
func delegateWithCandidate(ctx sdk.Context, k Keeper, delegatorAddr sdk.Address,
bondAmt sdk.Coin, candidate Candidate) sdk.Error {
if candidate.Status == Revoked { //candidate has been withdrawn
return ErrBondNotNominated()
}
// Get or create the delegator bond
existingBond, found := k.getDelegatorBond(ctx, delegatorAddr, candidate.Address)
if !found {
existingBond = DelegatorBond{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidate.Address,
Shares: sdk.ZeroRat,
}
}
// Account new shares, save
err := BondCoins(ctx, k, existingBond, candidate, bondAmt)
if err != nil {
return err
}
k.setDelegatorBond(ctx, existingBond)
k.setCandidate(ctx, candidate)
return nil
}
// Perform all the actions required to bond tokens to a delegator bond from their account
func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, amount sdk.Coin) sdk.Error {
_, err := k.coinKeeper.SubtractCoins(ctx, bond.DelegatorAddr, sdk.Coins{amount})
if err != nil {
return err
}
newShares := k.candidateAddTokens(ctx, candidate, amount.Amount)
bond.Shares = bond.Shares.Add(newShares)
k.setDelegatorBond(ctx, bond)
return nil
}
func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result {
// check if bond has any shares in it unbond
bond, found := k.getDelegatorBond(ctx, msg.DelegatorAddr, msg.CandidateAddr)
if !found {
return ErrNoDelegatorForAddress().Result()
}
if !bond.Shares.GT(sdk.ZeroRat) { // bond shares < msg shares
return ErrInsufficientFunds().Result()
}
// test getting rational number from decimal provided
shares, err := sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return err.Result()
}
// test that there are enough shares to unbond
if msg.Shares == "MAX" {
if !bond.Shares.GT(sdk.ZeroRat) {
return ErrNotEnoughBondShares(msg.Shares).Result()
}
} else {
if !bond.Shares.GT(shares) {
return ErrNotEnoughBondShares(msg.Shares).Result()
}
}
// get candidate
candidate, found := k.GetCandidate(ctx, msg.CandidateAddr)
if !found {
return ErrNoCandidateForAddress().Result()
}
if ctx.IsCheckTx() {
return sdk.Result{
GasUsed: GasUnbond,
}
}
// retrieve the amount of bonds to remove (TODO remove redundancy already serialized)
if msg.Shares == "MAX" {
shares = bond.Shares
}
// subtract bond tokens from delegator bond
bond.Shares = bond.Shares.Sub(shares)
// remove the bond
revokeCandidacy := false
if bond.Shares.IsZero() {
// if the bond is the owner of the candidate then
// trigger a revoke candidacy
if bytes.Equal(bond.DelegatorAddr, candidate.Address) &&
candidate.Status != Revoked {
revokeCandidacy = true
}
k.removeDelegatorBond(ctx, bond)
} else {
k.setDelegatorBond(ctx, bond)
}
// Add the coins
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins)
// revoke candidate if necessary
if revokeCandidacy {
// change the share types to unbonded if they were not already
if candidate.Status == Bonded {
k.bondedToUnbondedPool(ctx, candidate)
}
// lastly update the status
candidate.Status = Revoked
}
// deduct shares from the candidate
if candidate.Liabilities.IsZero() {
k.removeCandidate(ctx, candidate.Address)
} else {
k.setCandidate(ctx, candidate)
}
return sdk.Result{}
}
// XXX where this used
// Perform all the actions required to bond tokens to a delegator bond from their account
func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidate, shares sdk.Rat) sdk.Error {
// subtract bond tokens from delegator bond
if bond.Shares.LT(shares) {
return sdk.ErrInsufficientFunds("") //XXX variables inside
}
bond.Shares = bond.Shares.Sub(shares)
returnAmount := k.candidateRemoveShares(ctx, candidate, shares)
returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}}
_, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins)
if err != nil {
return err
}
return nil
}

248
x/stake/handler_test.go Normal file
View File

@ -0,0 +1,248 @@
package stake
//import (
//"strconv"
//"testing"
//"github.com/stretchr/testify/assert"
//"github.com/stretchr/testify/require"
//crypto "github.com/tendermint/go-crypto"
//sdk "github.com/cosmos/cosmos-sdk/types"
//)
////______________________________________________________________________
//func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy {
//return MsgDeclareCandidacy{
//Description: Description{},
//CandidateAddr: address,
//Bond: sdk.Coin{"fermion", amt},
//PubKey: pubKey,
//}
//}
//func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate {
//return MsgDelegate{
//DelegatorAddr: delegatorAddr,
//CandidateAddr: candidateAddr,
//Bond: sdk.Coin{"fermion", amt},
//}
//}
//func TestDuplicatesMsgDeclareCandidacy(t *testing.T) {
//ctxDeliver, _, keeper := createTestInput(t, addrs[0], false, 1000)
//ctxCheck, _, keeper := createTestInput(t, addrs[0], true, 1000)
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
//// one sender can bond to two different addresses
//msgDeclareCandidacy.Address = addrs[1]
//err := checker.declareCandidacy(msgDeclareCandidacy)
//assert.Nil(t, err, "didn't expected error on checkTx")
//// two addrs cant bond to the same pubkey
//checker.sender = addrs[1]
//msgDeclareCandidacy.Address = addrs[0]
//err = checker.declareCandidacy(msgDeclareCandidacy)
//assert.NotNil(t, err, "expected error on checkTx")
//}
//func TestIncrementsMsgDelegate(t *testing.T) {
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000)
//// first declare candidacy
//bondAmount := int64(10)
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got)
//expectedBond := bondAmount // 1 since we send 1 at the start of loop,
//// just send the same msgbond multiple times
//msgDelegate := newTestMsgDelegate(bondAmount, addrs[0])
//for i := 0; i < 5; i++ {
//got := deliverer.delegate(msgDelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the accounts and the bond account have the appropriate values
//candidates := mapper.GetCandidates()
//expectedBond += bondAmount
////expectedSender := initSender - expectedBond
//gotBonded := candidates[0].Liabilities.Evaluate()
////gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper
//assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded)
////assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix
//}
//}
//func TestIncrementsMsgUnbond(t *testing.T) {
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 0)
//// set initial bond
//initBond := int64(1000)
////accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper
//got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond))
//assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got)
//// just send the same msgunbond multiple times
//// XXX use decimals here
//unbondShares, unbondSharesStr := int64(10), "10"
//msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr)
//nUnbonds := 5
//for i := 0; i < nUnbonds; i++ {
//got := deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the accounts and the bond account have the appropriate values
//candidates := mapper.GetCandidates()
//expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop
////expectedSender := initSender + (initBond - expectedBond)
//gotBonded := candidates[0].Liabilities.Evaluate()
////gotSender := accStore[string(deliverer.sender)] // XXX use storemapper
//assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded)
////assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix
//}
//// these are more than we have bonded now
//errorCases := []int64{
////1<<64 - 1, // more than int64
////1<<63 + 1, // more than int64
//1<<63 - 1,
//1 << 31,
//initBond,
//}
//for _, c := range errorCases {
//unbondShares := strconv.Itoa(int(c))
//msgUndelegate := NewMsgUnbond(addrs[0], unbondShares)
//got = deliverer.unbond(msgUndelegate)
//assert.Error(t, got, "expected unbond msg to fail")
//}
//leftBonded := initBond - unbondShares*int64(nUnbonds)
//// should be unable to unbond one more than we have
//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1))
//got = deliverer.unbond(msgUndelegate)
//assert.Error(t, got, "expected unbond msg to fail")
//// should be able to unbond just what we have
//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)))
//got = deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected unbond msg to pass")
//}
//func TestMultipleMsgDeclareCandidacy(t *testing.T) {
//initSender := int64(1000)
//ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender)
//addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]}
//// bond them all
//for i, addr := range addrs {
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10)
//deliverer.sender = addr
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the account is bonded
//candidates := mapper.GetCandidates()
//require.Equal(t, i, len(candidates))
//val := candidates[i]
//balanceExpd := initSender - 10
//balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins()
//assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates)
//assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities)
//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
//}
//// unbond them all
//for i, addr := range addrs {
//candidatePre := mapper.GetCandidate(addrs[i])
//msgUndelegate := NewMsgUnbond(addrs[i], "10")
//deliverer.sender = addr
//got := deliverer.unbond(msgUndelegate)
//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the account is unbonded
//candidates := mapper.GetCandidates()
//assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates))
//candidatePost := mapper.GetCandidate(addrs[i])
//balanceExpd := initSender
//balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins()
//assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost)
//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot)
//}
//}
//func TestMultipleMsgDelegate(t *testing.T) {
//sender, delegators := addrs[0], addrs[1:]
//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000)
////first make a candidate
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//require.NoError(t, got, "expected msg to be ok, got %v", got)
//// delegate multiple parties
//for i, delegator := range delegators {
//msgDelegate := newTestMsgDelegate(10, sender)
//deliverer.sender = delegator
//got := deliverer.delegate(msgDelegate)
//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the account is bonded
//bond := mapper.getDelegatorBond(delegator, sender)
//assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond)
//}
//// unbond them all
//for i, delegator := range delegators {
//msgUndelegate := NewMsgUnbond(sender, "10")
//deliverer.sender = delegator
//got := deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got)
////Check that the account is unbonded
//bond := mapper.getDelegatorBond(delegator, sender)
//assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond)
//}
//}
//func TestVoidCandidacy(t *testing.T) {
//sender, delegator := addrs[0], addrs[1]
//_, _, _, deliverer := createTestInput(t, addrs[0], false, 1000)
//// create the candidate
//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10)
//got := deliverer.declareCandidacy(msgDeclareCandidacy)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
//// bond a delegator
//msgDelegate := newTestMsgDelegate(10, addrs[0])
//deliverer.sender = delegator
//got = deliverer.delegate(msgDelegate)
//require.NoError(t, got, "expected ok, got %v", got)
//// unbond the candidates bond portion
//msgUndelegate := NewMsgUnbond(addrs[0], "10")
//deliverer.sender = sender
//got = deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
//// test that this pubkey cannot yet be bonded too
//deliverer.sender = delegator
//got = deliverer.delegate(msgDelegate)
//assert.Error(t, got, "expected error, got %v", got)
//// test that the delegator can still withdraw their bonds
//got = deliverer.unbond(msgUndelegate)
//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy")
//// verify that the pubkey can now be reused
//got = deliverer.declareCandidacy(msgDeclareCandidacy)
//assert.NoError(t, got, "expected ok, got %v", got)
//}

300
x/stake/keeper.go Normal file
View File

@ -0,0 +1,300 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// keeper of the staking store
type Keeper struct {
storeKey sdk.StoreKey
cdc *wire.Codec
coinKeeper bank.CoinKeeper
// caches
gs Pool
params Params
}
func NewKeeper(ctx sdk.Context, cdc *wire.Codec, key sdk.StoreKey, ck bank.CoinKeeper) Keeper {
keeper := Keeper{
storeKey: key,
cdc: cdc,
coinKeeper: ck,
}
return keeper
}
//_________________________________________________________________________
// get a single candidate
func (k Keeper) GetCandidate(ctx sdk.Context, addr sdk.Address) (candidate Candidate, found bool) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetCandidateKey(addr))
if b == nil {
return candidate, false
}
err := k.cdc.UnmarshalBinary(b, &candidate)
if err != nil {
panic(err)
}
return candidate, true
}
// Get the set of all candidates, retrieve a maxRetrieve number of records
func (k Keeper) GetCandidates(ctx sdk.Context, maxRetrieve int16) (candidates Candidates) {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(subspace(CandidatesKey))
candidates = make([]Candidate, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
iterator.Close()
break
}
bz := iterator.Value()
var candidate Candidate
err := k.cdc.UnmarshalBinary(bz, &candidate)
if err != nil {
panic(err)
}
candidates[i] = candidate
iterator.Next()
}
return candidates[:i] // trim
}
func (k Keeper) setCandidate(ctx sdk.Context, candidate Candidate) {
store := ctx.KVStore(k.storeKey)
address := candidate.Address
// retreive the old candidate record
oldCandidate, oldFound := k.GetCandidate(ctx, address)
// marshal the candidate record and add to the state
bz, err := k.cdc.MarshalBinary(candidate)
if err != nil {
panic(err)
}
store.Set(GetCandidateKey(candidate.Address), bz)
// mashal the new validator record
validator := Validator{address, candidate.Assets}
bz, err = k.cdc.MarshalBinary(validator)
if err != nil {
panic(err)
}
// update the list ordered by voting power
if oldFound {
store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc))
}
store.Set(GetValidatorKey(address, validator.VotingPower, k.cdc), bz)
// add to the validators to update list if is already a validator
if store.Get(GetRecentValidatorKey(address)) == nil {
return
}
store.Set(GetAccUpdateValidatorKey(validator.Address), bz)
}
func (k Keeper) removeCandidate(ctx sdk.Context, address sdk.Address) {
// first retreive the old candidate record
oldCandidate, found := k.GetCandidate(ctx, address)
if !found {
return
}
// delete the old candidate record
store := ctx.KVStore(k.storeKey)
store.Delete(GetCandidateKey(address))
// delete from recent and power weighted validator groups if the validator
// exists and add validator with zero power to the validator updates
if store.Get(GetRecentValidatorKey(address)) == nil {
return
}
bz, err := k.cdc.MarshalBinary(Validator{address, sdk.ZeroRat})
if err != nil {
panic(err)
}
store.Set(GetAccUpdateValidatorKey(address), bz)
store.Delete(GetRecentValidatorKey(address))
store.Delete(GetValidatorKey(address, oldCandidate.Assets, k.cdc))
}
//___________________________________________________________________________
// get the most recent updated validator set from the Candidates. These bonds
// are already sorted by Assets from the UpdateVotingPower function which
// is the only function which is to modify the Assets
// this function also updaates the most recent validators saved in store
func (k Keeper) GetValidators(ctx sdk.Context) (validators []Validator) {
store := ctx.KVStore(k.storeKey)
// clear the recent validators store
k.deleteSubSpace(store, RecentValidatorsKey)
// add the actual validator power sorted store
maxVal := k.GetParams(ctx).MaxValidators
iterator := store.ReverseIterator(subspace(ValidatorsKey)) //smallest to largest
validators = make([]Validator, maxVal)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxVal-1) {
iterator.Close()
break
}
bz := iterator.Value()
var val Validator
err := k.cdc.UnmarshalBinary(bz, &val)
if err != nil {
panic(err)
}
validators[i] = val
// also add to the recent validators group
store.Set(GetRecentValidatorKey(val.Address), bz)
iterator.Next()
}
return validators[:i] // trim
}
// Is the address provided a part of the most recently saved validator group?
func (k Keeper) IsRecentValidator(ctx sdk.Context, address sdk.Address) bool {
store := ctx.KVStore(k.storeKey)
if store.Get(GetRecentValidatorKey(address)) == nil {
return false
}
return true
}
//_________________________________________________________________________
// Accumulated updates to the validator set
// get the most recently updated validators
func (k Keeper) getAccUpdateValidators(ctx sdk.Context) (updates []Validator) {
store := ctx.KVStore(k.storeKey)
iterator := store.Iterator(subspace(AccUpdateValidatorsKey)) //smallest to largest
for ; iterator.Valid(); iterator.Next() {
valBytes := iterator.Value()
var val Validator
err := k.cdc.UnmarshalBinary(valBytes, &val)
if err != nil {
panic(err)
}
updates = append(updates, val)
}
iterator.Close()
return
}
// remove all validator update entries
func (k Keeper) clearAccUpdateValidators(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
k.deleteSubSpace(store, AccUpdateValidatorsKey)
}
// TODO move to common functionality somewhere
func (k Keeper) deleteSubSpace(store sdk.KVStore, key []byte) {
iterator := store.Iterator(subspace(key))
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key())
}
iterator.Close()
}
//_____________________________________________________________________
func (k Keeper) getDelegatorBond(ctx sdk.Context,
delegatorAddr, candidateAddr sdk.Address) (bond DelegatorBond, found bool) {
store := ctx.KVStore(k.storeKey)
delegatorBytes := store.Get(GetDelegatorBondKey(delegatorAddr, candidateAddr, k.cdc))
if delegatorBytes == nil {
return bond, false
}
err := k.cdc.UnmarshalBinary(delegatorBytes, &bond)
if err != nil {
panic(err)
}
return bond, true
}
// load all bonds of a delegator
func (k Keeper) getDelegatorBonds(ctx sdk.Context, delegator sdk.Address, maxRetrieve int16) (bonds []DelegatorBond) {
store := ctx.KVStore(k.storeKey)
delegatorPrefixKey := GetDelegatorBondsKey(delegator, k.cdc)
iterator := store.Iterator(subspace(delegatorPrefixKey)) //smallest to largest
bonds = make([]DelegatorBond, maxRetrieve)
i := 0
for ; ; i++ {
if !iterator.Valid() || i > int(maxRetrieve-1) {
iterator.Close()
break
}
bondBytes := iterator.Value()
var bond DelegatorBond
err := k.cdc.UnmarshalBinary(bondBytes, &bond)
if err != nil {
panic(err)
}
bonds[i] = bond
iterator.Next()
}
return bonds[:i] // trim
}
func (k Keeper) setDelegatorBond(ctx sdk.Context, bond DelegatorBond) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalBinary(bond)
if err != nil {
panic(err)
}
store.Set(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc), b)
}
func (k Keeper) removeDelegatorBond(ctx sdk.Context, bond DelegatorBond) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorBondKey(bond.DelegatorAddr, bond.CandidateAddr, k.cdc))
}
//_______________________________________________________________________
// load/save the global staking params
func (k Keeper) GetParams(ctx sdk.Context) (params Params) {
// check if cached before anything
if k.params != (Params{}) {
return k.params
}
store := ctx.KVStore(k.storeKey)
b := store.Get(ParamKey)
if b == nil {
k.params = defaultParams()
return k.params
}
err := k.cdc.UnmarshalBinary(b, &params)
if err != nil {
panic(err)
}
return
}
func (k Keeper) setParams(ctx sdk.Context, params Params) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalBinary(params)
if err != nil {
panic(err)
}
store.Set(ParamKey, b)
k.params = Params{} // clear the cache
}

58
x/stake/keeper_keys.go Normal file
View File

@ -0,0 +1,58 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
// TODO remove some of these prefixes once have working multistore
//nolint
var (
// Keys for store prefixes
ParamKey = []byte{0x00} // key for global parameters relating to staking
PoolKey = []byte{0x01} // key for global parameters relating to staking
CandidatesKey = []byte{0x02} // prefix for each key to a candidate
ValidatorsKey = []byte{0x03} // prefix for each key to a validator
AccUpdateValidatorsKey = []byte{0x04} // prefix for each key to a validator which is being updated
RecentValidatorsKey = []byte{0x04} // prefix for each key to the last updated validator group
DelegatorBondKeyPrefix = []byte{0x05} // prefix for each key to a delegator's bond
)
const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch
// get the key for the candidate with address
func GetCandidateKey(addr sdk.Address) []byte {
return append(CandidatesKey, addr.Bytes()...)
}
// get the key for the validator used in the power-store
func GetValidatorKey(addr sdk.Address, power sdk.Rat, cdc *wire.Codec) []byte {
powerBytes := []byte(power.ToLeftPadded(maxDigitsForAccount))
return append(ValidatorsKey, append(powerBytes, addr.Bytes()...)...)
}
// get the key for the accumulated update validators
func GetAccUpdateValidatorKey(addr sdk.Address) []byte {
return append(AccUpdateValidatorsKey, addr.Bytes()...)
}
// get the key for the accumulated update validators
func GetRecentValidatorKey(addr sdk.Address) []byte {
return append(RecentValidatorsKey, addr.Bytes()...)
}
// get the key for delegator bond with candidate
func GetDelegatorBondKey(delegatorAddr, candidateAddr sdk.Address, cdc *wire.Codec) []byte {
return append(GetDelegatorBondsKey(delegatorAddr, cdc), candidateAddr.Bytes()...)
}
// get the prefix for a delegator for all candidates
func GetDelegatorBondsKey(delegatorAddr sdk.Address, cdc *wire.Codec) []byte {
res, err := cdc.MarshalBinary(&delegatorAddr)
if err != nil {
panic(err)
}
return append(DelegatorBondKeyPrefix, res...)
}

307
x/stake/keeper_test.go Normal file
View File

@ -0,0 +1,307 @@
package stake
import (
"bytes"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
addrDel1 = addrs[0]
addrDel2 = addrs[1]
addrVal1 = addrs[2]
addrVal2 = addrs[3]
addrVal3 = addrs[4]
pk1 = crypto.GenPrivKeyEd25519().PubKey()
pk2 = crypto.GenPrivKeyEd25519().PubKey()
pk3 = crypto.GenPrivKeyEd25519().PubKey()
candidate1 = Candidate{
Address: addrVal1,
PubKey: pk1,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
candidate2 = Candidate{
Address: addrVal2,
PubKey: pk2,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
candidate3 = Candidate{
Address: addrVal3,
PubKey: pk3,
Assets: sdk.NewRat(9),
Liabilities: sdk.NewRat(9),
}
)
// This function tests GetCandidate, GetCandidates, setCandidate, removeCandidate
func TestCandidate(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
candidatesEqual := func(c1, c2 Candidate) bool {
return c1.Status == c2.Status &&
c1.PubKey.Equals(c2.PubKey) &&
bytes.Equal(c1.Address, c2.Address) &&
c1.Assets.Equal(c2.Assets) &&
c1.Liabilities.Equal(c2.Liabilities) &&
c1.Description == c2.Description
}
// check the empty keeper first
_, found := keeper.GetCandidate(ctx, addrVal1)
assert.False(t, found)
resCands := keeper.GetCandidates(ctx, 100)
assert.Zero(t, len(resCands))
// set and retrieve a record
keeper.setCandidate(ctx, candidate1)
resCand, found := keeper.GetCandidate(ctx, addrVal1)
require.True(t, found)
assert.True(t, candidatesEqual(candidate1, resCand), "%v \n %v", resCand, candidate1)
// modify a records, save, and retrieve
candidate1.Liabilities = sdk.NewRat(99)
keeper.setCandidate(ctx, candidate1)
resCand, found = keeper.GetCandidate(ctx, addrVal1)
require.True(t, found)
assert.True(t, candidatesEqual(candidate1, resCand))
// also test that the address has been added to address list
resCands = keeper.GetCandidates(ctx, 100)
require.Equal(t, 1, len(resCands))
assert.Equal(t, addrVal1, resCands[0].Address)
// add other candidates
keeper.setCandidate(ctx, candidate2)
keeper.setCandidate(ctx, candidate3)
resCand, found = keeper.GetCandidate(ctx, addrVal2)
require.True(t, found)
assert.True(t, candidatesEqual(candidate2, resCand), "%v \n %v", resCand, candidate2)
resCand, found = keeper.GetCandidate(ctx, addrVal3)
require.True(t, found)
assert.True(t, candidatesEqual(candidate3, resCand), "%v \n %v", resCand, candidate3)
resCands = keeper.GetCandidates(ctx, 100)
require.Equal(t, 3, len(resCands))
assert.True(t, candidatesEqual(candidate1, resCands[0]), "%v \n %v", resCands[0], candidate1)
assert.True(t, candidatesEqual(candidate2, resCands[1]), "%v \n %v", resCands[1], candidate2)
assert.True(t, candidatesEqual(candidate3, resCands[2]), "%v \n %v", resCands[2], candidate3)
// remove a record
keeper.removeCandidate(ctx, candidate2.Address)
_, found = keeper.GetCandidate(ctx, addrVal2)
assert.False(t, found)
}
// tests GetDelegatorBond, GetDelegatorBonds, SetDelegatorBond, removeDelegatorBond
func TestBond(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
// first add a candidate1 to delegate too
keeper.setCandidate(ctx, candidate1)
bond1to1 := DelegatorBond{
DelegatorAddr: addrDel1,
CandidateAddr: addrVal1,
Shares: sdk.NewRat(9),
}
bondsEqual := func(b1, b2 DelegatorBond) bool {
return bytes.Equal(b1.DelegatorAddr, b2.DelegatorAddr) &&
bytes.Equal(b1.CandidateAddr, b2.CandidateAddr) &&
b1.Shares == b2.Shares
}
// check the empty keeper first
_, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
assert.False(t, found)
// set and retrieve a record
keeper.setDelegatorBond(ctx, bond1to1)
resBond, found := keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
assert.True(t, found)
assert.True(t, bondsEqual(bond1to1, resBond))
// modify a records, save, and retrieve
bond1to1.Shares = sdk.NewRat(99)
keeper.setDelegatorBond(ctx, bond1to1)
resBond, found = keeper.getDelegatorBond(ctx, addrDel1, addrVal1)
assert.True(t, found)
assert.True(t, bondsEqual(bond1to1, resBond))
// add some more records
keeper.setCandidate(ctx, candidate2)
keeper.setCandidate(ctx, candidate3)
bond1to2 := DelegatorBond{addrDel1, addrVal2, sdk.NewRat(9)}
bond1to3 := DelegatorBond{addrDel1, addrVal3, sdk.NewRat(9)}
bond2to1 := DelegatorBond{addrDel2, addrVal1, sdk.NewRat(9)}
bond2to2 := DelegatorBond{addrDel2, addrVal2, sdk.NewRat(9)}
bond2to3 := DelegatorBond{addrDel2, addrVal3, sdk.NewRat(9)}
keeper.setDelegatorBond(ctx, bond1to2)
keeper.setDelegatorBond(ctx, bond1to3)
keeper.setDelegatorBond(ctx, bond2to1)
keeper.setDelegatorBond(ctx, bond2to2)
keeper.setDelegatorBond(ctx, bond2to3)
// test all bond retrieve capabilities
resBonds := keeper.getDelegatorBonds(ctx, addrDel1, 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bondsEqual(bond1to1, resBonds[0]))
assert.True(t, bondsEqual(bond1to2, resBonds[1]))
assert.True(t, bondsEqual(bond1to3, resBonds[2]))
resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 3)
require.Equal(t, 3, len(resBonds))
resBonds = keeper.getDelegatorBonds(ctx, addrDel1, 2)
require.Equal(t, 2, len(resBonds))
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
require.Equal(t, 3, len(resBonds))
assert.True(t, bondsEqual(bond2to1, resBonds[0]))
assert.True(t, bondsEqual(bond2to2, resBonds[1]))
assert.True(t, bondsEqual(bond2to3, resBonds[2]))
// delete a record
keeper.removeDelegatorBond(ctx, bond2to3)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal3)
assert.False(t, found)
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
require.Equal(t, 2, len(resBonds))
assert.True(t, bondsEqual(bond2to1, resBonds[0]))
assert.True(t, bondsEqual(bond2to2, resBonds[1]))
// delete all the records from delegator 2
keeper.removeDelegatorBond(ctx, bond2to1)
keeper.removeDelegatorBond(ctx, bond2to2)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal1)
assert.False(t, found)
_, found = keeper.getDelegatorBond(ctx, addrDel2, addrVal2)
assert.False(t, found)
resBonds = keeper.getDelegatorBonds(ctx, addrDel2, 5)
require.Equal(t, 0, len(resBonds))
}
// TODO integrate in testing for equal validators, whichever one was a validator
// first remains the validator https://github.com/cosmos/cosmos-sdk/issues/582
func TestGetValidators(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
// initialize some candidates into the state
amts := []int64{0, 100, 1, 400, 200}
n := len(amts)
candidates := make([]Candidate, n)
for i := 0; i < n; i++ {
c := Candidate{
Status: Unbonded,
PubKey: pks[i],
Address: addrs[i],
Assets: sdk.NewRat(amts[i]),
Liabilities: sdk.NewRat(amts[i]),
}
keeper.setCandidate(ctx, c)
candidates[i] = c
}
// first make sure everything as normal is ordered
validators := keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(400), validators[0].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(200), validators[1].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(100), validators[2].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(1), validators[3].VotingPower, "%v", validators)
assert.Equal(t, sdk.NewRat(0), validators[4].VotingPower, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
assert.Equal(t, candidates[4].Address, validators[1].Address, "%v", validators)
assert.Equal(t, candidates[1].Address, validators[2].Address, "%v", validators)
assert.Equal(t, candidates[2].Address, validators[3].Address, "%v", validators)
assert.Equal(t, candidates[0].Address, validators[4].Address, "%v", validators)
// test a basic increase in voting power
candidates[3].Assets = sdk.NewRat(500)
keeper.setCandidate(ctx, candidates[3])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(500), validators[0].VotingPower, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
// test a decrease in voting power
candidates[3].Assets = sdk.NewRat(300)
keeper.setCandidate(ctx, candidates[3])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(300), validators[0].VotingPower, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[0].Address, "%v", validators)
// test a swap in voting power
candidates[0].Assets = sdk.NewRat(600)
keeper.setCandidate(ctx, candidates[0])
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators)
assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators)
// test the max validators term
params := keeper.GetParams(ctx)
n = 2
params.MaxValidators = uint16(n)
keeper.setParams(ctx, params)
validators = keeper.GetValidators(ctx)
require.Equal(t, len(validators), n)
assert.Equal(t, sdk.NewRat(600), validators[0].VotingPower, "%v", validators)
assert.Equal(t, candidates[0].Address, validators[0].Address, "%v", validators)
assert.Equal(t, sdk.NewRat(300), validators[1].VotingPower, "%v", validators)
assert.Equal(t, candidates[3].Address, validators[1].Address, "%v", validators)
}
// TODO
// test the mechanism which keeps track of a validator set change
func TestGetAccUpdateValidators(t *testing.T) {
//TODO
// test from nothing to something
// test from something to nothing
// test identical
// test single value change
// test multiple value change
// test validator added at the beginning
// test validator added in the middle
// test validator added at the end
// test multiple validators removed
}
// clear the tracked changes to the validator set
func TestClearAccUpdateValidators(t *testing.T) {
//TODO
}
// test if is a validator from the last update
func TestIsRecentValidator(t *testing.T) {
//TODO
// test that an empty validator set doesn't have any validators
// get the validators for the first time
// test a basic retrieve of something that should be a recent validator
// test a basic retrieve of something that should not be a recent validator
// remove that validator, but don't retrieve the recent validator group
// test that removed validator is not considered a recent validator
}
func TestParams(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
expParams := defaultParams()
//check that the empty keeper loads the default
resParams := keeper.GetParams(ctx)
assert.Equal(t, expParams, resParams)
//modify a params, save, and retrieve
expParams.MaxValidators = 777
keeper.setParams(ctx, expParams)
resParams = keeper.GetParams(ctx)
assert.Equal(t, expParams, resParams)
}

227
x/stake/msg.go Normal file
View File

@ -0,0 +1,227 @@
package stake
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// name to idetify transaction types
const MsgType = "stake"
// XXX remove: think it makes more sense belonging with the Params so we can
// initialize at genesis - to allow for the same tests we should should make
// the ValidateBasic() function a return from an initializable function
// ValidateBasic(bondDenom string) function
const StakingToken = "fermion"
//Verify interface at compile time
var _, _, _, _ sdk.Msg = &MsgDeclareCandidacy{}, &MsgEditCandidacy{}, &MsgDelegate{}, &MsgUnbond{}
//______________________________________________________________________
// MsgDeclareCandidacy - struct for unbonding transactions
type MsgDeclareCandidacy struct {
Description
CandidateAddr sdk.Address `json:"address"`
PubKey crypto.PubKey `json:"pubkey"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgDeclareCandidacy(candidateAddr sdk.Address, pubkey crypto.PubKey,
bond sdk.Coin, description Description) MsgDeclareCandidacy {
return MsgDeclareCandidacy{
Description: description,
CandidateAddr: candidateAddr,
PubKey: pubkey,
Bond: bond,
}
}
//nolint
func (msg MsgDeclareCandidacy) Type() string { return MsgType } //TODO update "stake/declarecandidacy"
func (msg MsgDeclareCandidacy) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgDeclareCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} }
func (msg MsgDeclareCandidacy) String() string {
return fmt.Sprintf("CandidateAddr{Address: %v}", msg.CandidateAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgDeclareCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDeclareCandidacy) ValidateBasic() sdk.Error {
if msg.CandidateAddr == nil {
return ErrCandidateEmpty()
}
if msg.Bond.Denom != StakingToken {
return ErrBadBondingDenom()
}
if msg.Bond.Amount <= 0 {
return ErrBadBondingAmount()
// return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String())
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "description must be included")
}
return nil
}
//______________________________________________________________________
// MsgEditCandidacy - struct for editing a candidate
type MsgEditCandidacy struct {
Description
CandidateAddr sdk.Address `json:"address"`
}
func NewMsgEditCandidacy(candidateAddr sdk.Address, description Description) MsgEditCandidacy {
return MsgEditCandidacy{
Description: description,
CandidateAddr: candidateAddr,
}
}
//nolint
func (msg MsgEditCandidacy) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgEditCandidacy) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgEditCandidacy) GetSigners() []sdk.Address { return []sdk.Address{msg.CandidateAddr} }
func (msg MsgEditCandidacy) String() string {
return fmt.Sprintf("CandidateAddr{Address: %v}", msg.CandidateAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgEditCandidacy) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgEditCandidacy) ValidateBasic() sdk.Error {
if msg.CandidateAddr == nil {
return ErrCandidateEmpty()
}
empty := Description{}
if msg.Description == empty {
return newError(CodeInvalidInput, "Transaction must include some information to modify")
}
return nil
}
//______________________________________________________________________
// MsgDelegate - struct for bonding transactions
type MsgDelegate struct {
DelegatorAddr sdk.Address `json:"address"`
CandidateAddr sdk.Address `json:"address"`
Bond sdk.Coin `json:"bond"`
}
func NewMsgDelegate(delegatorAddr, candidateAddr sdk.Address, bond sdk.Coin) MsgDelegate {
return MsgDelegate{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidateAddr,
Bond: bond,
}
}
//nolint
func (msg MsgDelegate) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgDelegate) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgDelegate) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
func (msg MsgDelegate) String() string {
return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgDelegate) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgDelegate) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr()
}
if msg.CandidateAddr == nil {
return ErrBadCandidateAddr()
}
if msg.Bond.Denom != StakingToken {
return ErrBadBondingDenom()
}
if msg.Bond.Amount <= 0 {
return ErrBadBondingAmount()
// return sdk.ErrInvalidCoins(sdk.Coins{msg.Bond}.String())
}
return nil
}
//______________________________________________________________________
// MsgUnbond - struct for unbonding transactions
type MsgUnbond struct {
DelegatorAddr sdk.Address `json:"address"`
CandidateAddr sdk.Address `json:"address"`
Shares string `json:"shares"`
}
func NewMsgUnbond(delegatorAddr, candidateAddr sdk.Address, shares string) MsgUnbond {
return MsgUnbond{
DelegatorAddr: delegatorAddr,
CandidateAddr: candidateAddr,
Shares: shares,
}
}
//nolint
func (msg MsgUnbond) Type() string { return MsgType } //TODO update "stake/msgeditcandidacy"
func (msg MsgUnbond) Get(key interface{}) (value interface{}) { return nil }
func (msg MsgUnbond) GetSigners() []sdk.Address { return []sdk.Address{msg.DelegatorAddr} }
func (msg MsgUnbond) String() string {
return fmt.Sprintf("Addr{Address: %v}", msg.DelegatorAddr) // XXX fix
}
// get the bytes for the message signer to sign on
func (msg MsgUnbond) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// quick validity check
func (msg MsgUnbond) ValidateBasic() sdk.Error {
if msg.DelegatorAddr == nil {
return ErrBadDelegatorAddr()
}
if msg.CandidateAddr == nil {
return ErrBadCandidateAddr()
}
if msg.Shares != "MAX" {
rat, err := sdk.NewRatFromDecimal(msg.Shares)
if err != nil {
return ErrBadShares()
}
if rat.IsZero() || rat.LT(sdk.ZeroRat) {
return ErrBadShares()
}
}
return nil
}

156
x/stake/msg_test.go Normal file
View File

@ -0,0 +1,156 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
var (
coinPos = sdk.Coin{"fermion", 1000}
coinZero = sdk.Coin{"fermion", 0}
coinNeg = sdk.Coin{"fermion", -10000}
coinPosNotAtoms = sdk.Coin{"foo", 10000}
coinZeroNotAtoms = sdk.Coin{"foo", 0}
coinNegNotAtoms = sdk.Coin{"foo", -10000}
)
// test ValidateBasic for MsgDeclareCandidacy
func TestMsgDeclareCandidacy(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
candidateAddr sdk.Address
pubkey crypto.PubKey
bond sdk.Coin
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addrs[0], pks[0], coinPos, true},
{"partial description", "", "", "c", "", addrs[0], pks[0], coinPos, true},
{"empty description", "", "", "", "", addrs[0], pks[0], coinPos, false},
{"empty address", "a", "b", "c", "d", emptyAddr, pks[0], coinPos, false},
{"empty pubkey", "a", "b", "c", "d", addrs[0], emptyPubkey, coinPos, true},
{"empty bond", "a", "b", "c", "d", addrs[0], pks[0], coinZero, false},
{"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false},
{"negative bond", "a", "b", "c", "d", addrs[0], pks[0], coinNeg, false},
{"wrong staking token", "a", "b", "c", "d", addrs[0], pks[0], coinPosNotAtoms, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgDeclareCandidacy(tc.candidateAddr, tc.pubkey, tc.bond, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgEditCandidacy
func TestMsgEditCandidacy(t *testing.T) {
tests := []struct {
name, moniker, identity, website, details string
candidateAddr sdk.Address
expectPass bool
}{
{"basic good", "a", "b", "c", "d", addrs[0], true},
{"partial description", "", "", "c", "", addrs[0], true},
{"empty description", "", "", "", "", addrs[0], false},
{"empty address", "a", "b", "c", "d", emptyAddr, false},
}
for _, tc := range tests {
description := NewDescription(tc.moniker, tc.identity, tc.website, tc.details)
msg := NewMsgEditCandidacy(tc.candidateAddr, description)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgDelegate
func TestMsgDelegate(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
candidateAddr sdk.Address
bond sdk.Coin
expectPass bool
}{
{"basic good", addrs[0], addrs[1], coinPos, true},
{"self bond", addrs[0], addrs[0], coinPos, true},
{"empty delegator", emptyAddr, addrs[0], coinPos, false},
{"empty candidate", addrs[0], emptyAddr, coinPos, false},
{"empty bond", addrs[0], addrs[1], coinZero, false},
{"negative bond", addrs[0], addrs[1], coinNeg, false},
{"wrong staking token", addrs[0], addrs[1], coinPosNotAtoms, false},
}
for _, tc := range tests {
msg := NewMsgDelegate(tc.delegatorAddr, tc.candidateAddr, tc.bond)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// test ValidateBasic for MsgUnbond
func TestMsgUnbond(t *testing.T) {
tests := []struct {
name string
delegatorAddr sdk.Address
candidateAddr sdk.Address
shares string
expectPass bool
}{
{"max unbond", addrs[0], addrs[1], "MAX", true},
{"decimal unbond", addrs[0], addrs[1], "0.1", true},
{"negative decimal unbond", addrs[0], addrs[1], "-0.1", false},
{"zero unbond", addrs[0], addrs[1], "0.0", false},
{"invalid decimal", addrs[0], addrs[0], "sunny", false},
{"empty delegator", emptyAddr, addrs[0], "0.1", false},
{"empty candidate", addrs[0], emptyAddr, "0.1", false},
}
for _, tc := range tests {
msg := NewMsgUnbond(tc.delegatorAddr, tc.candidateAddr, tc.shares)
if tc.expectPass {
assert.Nil(t, msg.ValidateBasic(), "test: %v", tc.name)
} else {
assert.NotNil(t, msg.ValidateBasic(), "test: %v", tc.name)
}
}
}
// TODO introduce with go-amino
//func TestSerializeMsg(t *testing.T) {
//// make sure all types construct properly
//bondAmt := 1234321
//bond := sdk.Coin{Denom: "atom", Amount: int64(bondAmt)}
//tests := []struct {
//tx sdk.Msg
//}{
//{NewMsgDeclareCandidacy(addrs[0], pks[0], bond, Description{})},
//{NewMsgEditCandidacy(addrs[0], Description{})},
//{NewMsgDelegate(addrs[0], addrs[1], bond)},
//{NewMsgUnbond(addrs[0], addrs[1], strconv.Itoa(bondAmt))},
//}
//for i, tc := range tests {
//var tx sdk.Tx
//bs := wire.BinaryBytes(tc.tx)
//err := wire.ReadBinaryBytes(bs, &tx)
//if assert.NoError(t, err, "%d", i) {
//assert.Equal(t, tc.tx, tx, "%d", i)
//}
//}
//}

135
x/stake/pool.go Normal file
View File

@ -0,0 +1,135 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// load/save the global staking state
func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) {
// check if cached before anything
if k.gs != (Pool{}) {
return k.gs
}
store := ctx.KVStore(k.storeKey)
b := store.Get(PoolKey)
if b == nil {
return initialPool()
}
err := k.cdc.UnmarshalBinary(b, &gs)
if err != nil {
panic(err) // This error should never occur big problem if does
}
return
}
func (k Keeper) setPool(ctx sdk.Context, p Pool) {
store := ctx.KVStore(k.storeKey)
b, err := k.cdc.MarshalBinary(p)
if err != nil {
panic(err)
}
store.Set(PoolKey, b)
k.gs = Pool{} // clear the cache
}
//_______________________________________________________________________
//TODO make these next two functions more efficient should be reading and writting to state ye know
// move a candidates asset pool from bonded to unbonded pool
func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) {
// replace bonded shares with unbonded shares
tokens := k.removeSharesBonded(ctx, candidate.Assets)
candidate.Assets = k.addTokensUnbonded(ctx, tokens)
candidate.Status = Unbonded
k.setCandidate(ctx, candidate)
}
// move a candidates asset pool from unbonded to bonded pool
func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) {
// replace unbonded shares with bonded shares
tokens := k.removeSharesUnbonded(ctx, candidate.Assets)
candidate.Assets = k.addTokensBonded(ctx, tokens)
candidate.Status = Bonded
k.setCandidate(ctx, candidate)
}
//_______________________________________________________________________
func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) {
p := k.GetPool(ctx)
issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
p.BondedPool += amount
p.BondedShares = p.BondedShares.Add(issuedShares)
k.setPool(ctx, p)
return
}
func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) {
p := k.GetPool(ctx)
removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.BondedShares = p.BondedShares.Sub(shares)
p.BondedPool -= removedTokens
k.setPool(ctx, p)
return
}
func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) {
p := k.GetPool(ctx)
issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens
p.UnbondedShares = p.UnbondedShares.Add(issuedShares)
p.UnbondedPool += amount
k.setPool(ctx, p)
return
}
func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) {
p := k.GetPool(ctx)
removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares
p.UnbondedShares = p.UnbondedShares.Sub(shares)
p.UnbondedPool -= removedTokens
k.setPool(ctx, p)
return
}
//_______________________________________________________________________
// add tokens to a candidate
func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) {
p := k.GetPool(ctx)
exRate := candidate.delegatorShareExRate()
var receivedGlobalShares sdk.Rat
if candidate.Status == Bonded {
receivedGlobalShares = k.addTokensBonded(ctx, amount)
} else {
receivedGlobalShares = k.addTokensUnbonded(ctx, amount)
}
candidate.Assets = candidate.Assets.Add(receivedGlobalShares)
issuedDelegatorShares = exRate.Mul(receivedGlobalShares)
candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares)
k.setPool(ctx, p) // TODO cache Pool?
return
}
// remove shares from a candidate
func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) {
p := k.GetPool(ctx)
//exRate := candidate.delegatorShareExRate() //XXX make sure not used
globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares)
if candidate.Status == Bonded {
createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove)
} else {
createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove)
}
candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove)
candidate.Liabilities = candidate.Liabilities.Sub(shares)
k.setPool(ctx, p) // TODO cache Pool?
return
}

22
x/stake/pool_test.go Normal file
View File

@ -0,0 +1,22 @@
package stake
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPool(t *testing.T) {
ctx, _, keeper := createTestInput(t, nil, false, 0)
expPool := initialPool()
//check that the empty keeper loads the default
resPool := keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
//modify a params, save, and retrieve
expPool.TotalSupply = 777
keeper.setPool(ctx, expPool)
resPool = keeper.GetPool(ctx)
assert.Equal(t, expPool, resPool)
}

156
x/stake/test_common.go Normal file
View File

@ -0,0 +1,156 @@
package stake
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
crypto "github.com/tendermint/go-crypto"
oldwire "github.com/tendermint/go-wire"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
)
// dummy addresses used for testing
var (
addrs = []sdk.Address{
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6160"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6161"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6162"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6163"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6164"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6165"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6166"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6167"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6168"),
testAddr("A58856F0FD53BF058B4909A21AEC019107BA6169"),
}
// dummy pubkeys used for testing
pks = []crypto.PubKey{
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB50"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB51"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB52"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB53"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB54"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB55"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB56"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB57"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB58"),
newPubKey("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AFB59"),
}
emptyAddr sdk.Address
emptyPubkey crypto.PubKey
)
// XXX reference the common declaration of this function
func subspace(prefix []byte) (start, end []byte) {
end = make([]byte, len(prefix))
copy(end, prefix)
end[len(end)-1]++
return prefix, end
}
// custom tx codec
// TODO: use new go-wire
func makeTestCodec() *wire.Codec {
const msgTypeSend = 0x1
const msgTypeIssue = 0x2
const msgTypeDeclareCandidacy = 0x3
const msgTypeEditCandidacy = 0x4
const msgTypeDelegate = 0x5
const msgTypeUnbond = 0x6
var _ = oldwire.RegisterInterface(
struct{ sdk.Msg }{},
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue},
oldwire.ConcreteType{MsgDeclareCandidacy{}, msgTypeDeclareCandidacy},
oldwire.ConcreteType{MsgEditCandidacy{}, msgTypeEditCandidacy},
oldwire.ConcreteType{MsgDelegate{}, msgTypeDelegate},
oldwire.ConcreteType{MsgUnbond{}, msgTypeUnbond},
)
const accTypeApp = 0x1
var _ = oldwire.RegisterInterface(
struct{ sdk.Account }{},
oldwire.ConcreteType{&types.AppAccount{}, accTypeApp},
)
cdc := wire.NewCodec()
// cdc.RegisterInterface((*sdk.Msg)(nil), nil)
// bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types.
// crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
return cdc
}
func paramsNoInflation() Params {
return Params{
InflationRateChange: sdk.ZeroRat,
InflationMax: sdk.ZeroRat,
InflationMin: sdk.ZeroRat,
GoalBonded: sdk.NewRat(67, 100),
MaxValidators: 100,
BondDenom: "fermion",
}
}
// hogpodge of all sorts of input required for testing
func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins int64) (sdk.Context, sdk.AccountMapper, Keeper) {
db := dbm.NewMemDB()
keyStake := sdk.NewKVStoreKey("stake")
keyMain := keyStake //sdk.NewKVStoreKey("main") //TODO fix multistore
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db)
err := ms.LoadLatestVersion()
require.Nil(t, err)
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil)
cdc := makeTestCodec()
accountMapper := auth.NewAccountMapperSealed(
keyMain, // target store
&auth.BaseAccount{}, // prototype
)
ck := bank.NewCoinKeeper(accountMapper)
keeper := NewKeeper(ctx, cdc, keyStake, ck)
//params := paramsNoInflation()
params := keeper.GetParams(ctx)
// fill all the addresses with some coins
for _, addr := range addrs {
ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}})
}
return ctx, accountMapper, keeper
}
func newPubKey(pk string) (res crypto.PubKey) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
panic(err)
}
//res, err = crypto.PubKeyFromBytes(pkBytes)
var pkEd crypto.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
return pkEd.Wrap()
}
// for incode address generation
func testAddr(addr string) sdk.Address {
res, err := sdk.GetAddress(addr)
if err != nil {
panic(err)
}
return res
}

79
x/stake/tick.go Normal file
View File

@ -0,0 +1,79 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
)
const (
hrsPerYear = 8766 // as defined by a julian year of 365.25 days
precision = 1000000000
)
var hrsPerYrRat = sdk.NewRat(hrsPerYear) // as defined by a julian year of 365.25 days
// Tick - called at the end of every block
func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) {
// retrieve params
p := k.GetPool(ctx)
height := ctx.BlockHeight()
// Process Validator Provisions
// XXX right now just process every 5 blocks, in new SDK make hourly
if p.InflationLastTime+5 <= height {
p.InflationLastTime = height
k.processProvisions(ctx)
}
newVals := k.GetValidators(ctx)
// XXX determine change from old validators, set to change
_ = newVals
return change, nil
}
// process provisions for an hour period
func (k Keeper) processProvisions(ctx sdk.Context) {
pool := k.GetPool(ctx)
pool.Inflation = k.nextInflation(ctx).Round(precision)
// Because the validators hold a relative bonded share (`GlobalStakeShare`), when
// more bonded tokens are added proportionally to all validators the only term
// which needs to be updated is the `BondedPool`. So for each previsions cycle:
provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate()
pool.BondedPool += provisions
pool.TotalSupply += provisions
// save the params
k.setPool(ctx, pool)
}
// get the next inflation rate for the hour
func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) {
params := k.GetParams(ctx)
pool := k.GetPool(ctx)
// The target annual inflation rate is recalculated for each previsions cycle. The
// inflation is also subject to a rate change (positive of negative) depending or
// the distance from the desired ratio (67%). The maximum rate change possible is
// defined to be 13% per year, however the annual inflation is capped as between
// 7% and 20%.
// (1 - bondedRatio/GoalBonded) * InflationRateChange
inflationRateChangePerYear := sdk.OneRat.Sub(pool.bondedRatio().Quo(params.GoalBonded)).Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYrRat)
// increase the new annual inflation for this next cycle
inflation = pool.Inflation.Add(inflationRateChange)
if inflation.GT(params.InflationMax) {
inflation = params.InflationMax
}
if inflation.LT(params.InflationMin) {
inflation = params.InflationMin
}
return
}

116
x/stake/tick_test.go Normal file
View File

@ -0,0 +1,116 @@
package stake
//import (
//"testing"
//sdk "github.com/cosmos/cosmos-sdk/types"
//"github.com/stretchr/testify/assert"
//)
//func TestGetInflation(t *testing.T) {
//ctx, _, keeper := createTestInput(t, nil, false, 0)
//params := defaultParams()
//keeper.setParams(ctx, params)
//gs := keeper.GetPool(ctx)
//// Governing Mechanism:
//// bondedRatio = BondedPool / TotalSupply
//// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange
//tests := []struct {
//setBondedPool, setTotalSupply int64
//setInflation, expectedChange sdk.Rat
//}{
//// with 0% bonded atom supply the inflation should increase by InflationRateChange
//{0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYr)},
//// 100% bonded, starting at 20% inflation and being reduced
//{1, 1, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
//// 50% bonded, starting at 10% inflation and being increased
//{1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
//// test 7% minimum stop (testing with 100% bonded)
//{1, 1, sdk.NewRat(7, 100), sdk.ZeroRat},
//{1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)},
//// test 20% maximum stop (testing with 0% bonded)
//{0, 0, sdk.NewRat(20, 100), sdk.ZeroRat},
//{0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)},
//// perfect balance shouldn't change inflation
//{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat},
//}
//for _, tc := range tests {
//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply
//gs.Inflation = tc.setInflation
//inflation := nextInflation(gs, params)
//diffInflation := inflation.Sub(tc.setInflation)
//assert.True(t, diffInflation.Equal(tc.expectedChange),
//"%v, %v", diffInflation, tc.expectedChange)
//}
//}
//func TestProcessProvisions(t *testing.T) {
//ctx, _, keeper := createTestInput(t, nil, false, 0)
//params := defaultParams()
//keeper.setParams(ctx, params)
//gs := keeper.GetPool(ctx)
//// create some candidates some bonded, some unbonded
//candidates := candidatesFromAddrsEmpty(addrs)
//for i, candidate := range candidates {
//if i < 5 {
//candidate.Status = Bonded
//}
//mintedTokens := int64((i + 1) * 10000000)
//gs.TotalSupply += mintedTokens
//keeper.candidateAddTokens(ctx, candidate, mintedTokens)
//keeper.setCandidate(ctx, candidate)
//}
//var totalSupply int64 = 550000000
//var bondedShares int64 = 150000000
//var unbondedShares int64 = 400000000
//// initial bonded ratio ~ 27%
//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio())
//// Supplies
//assert.Equal(t, totalSupply, p.TotalSupply)
//assert.Equal(t, bondedShares, p.BondedPool)
//assert.Equal(t, unbondedShares, p.UnbondedPool)
//// test the value of candidate shares
//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate())
//initialSupply := p.TotalSupply
//initialUnbonded := p.TotalSupply - p.BondedPool
//// process the provisions a year
//for hr := 0; hr < 8766; hr++ {
//expInflation := nextInflation(gs, params).Round(1000000000)
//expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate()
//startBondedPool := p.BondedPool
//startTotalSupply := p.TotalSupply
//processProvisions(ctx, keeper, p, params)
//assert.Equal(t, startBondedPool+expProvisions, p.BondedPool)
//assert.Equal(t, startTotalSupply+expProvisions, p.TotalSupply)
//}
//assert.NotEqual(t, initialSupply, p.TotalSupply)
//assert.Equal(t, initialUnbonded, p.UnbondedPool)
////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, p.TotalSupply-gs.BondedPool))
//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders
//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio())
//// global supply
//assert.Equal(t, int64(611813022), p.TotalSupply)
//assert.Equal(t, int64(211813022), p.BondedPool)
//assert.Equal(t, unbondedShares, p.UnbondedPool)
//// test the value of candidate shares
//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate())
//}

197
x/stake/types.go Normal file
View File

@ -0,0 +1,197 @@
package stake
import (
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// Params defines the high level settings for staking
type Params struct {
InflationRateChange sdk.Rat `json:"inflation_rate_change"` // maximum annual change in inflation rate
InflationMax sdk.Rat `json:"inflation_max"` // maximum inflation rate
InflationMin sdk.Rat `json:"inflation_min"` // minimum inflation rate
GoalBonded sdk.Rat `json:"goal_bonded"` // Goal of percent bonded atoms
MaxValidators uint16 `json:"max_validators"` // maximum number of validators
BondDenom string `json:"bond_denom"` // bondable coin denomination
}
// XXX do we want to allow for default params even or do we want to enforce that you
// need to be explicit about defining all params in genesis?
func defaultParams() Params {
return Params{
InflationRateChange: sdk.NewRat(13, 100),
InflationMax: sdk.NewRat(20, 100),
InflationMin: sdk.NewRat(7, 100),
GoalBonded: sdk.NewRat(67, 100),
MaxValidators: 100,
BondDenom: "fermion",
}
}
//_________________________________________________________________________
// Pool - dynamic parameters of the current state
type Pool struct {
TotalSupply int64 `json:"total_supply"` // total supply of all tokens
BondedShares sdk.Rat `json:"bonded_shares"` // sum of all shares distributed for the Bonded Pool
UnbondedShares sdk.Rat `json:"unbonded_shares"` // sum of all shares distributed for the Unbonded Pool
BondedPool int64 `json:"bonded_pool"` // reserve of bonded tokens
UnbondedPool int64 `json:"unbonded_pool"` // reserve of unbonded tokens held with candidates
InflationLastTime int64 `json:"inflation_last_time"` // block which the last inflation was processed // TODO make time
Inflation sdk.Rat `json:"inflation"` // current annual inflation rate
}
// XXX define globalstate interface?
func initialPool() Pool {
return Pool{
TotalSupply: 0,
BondedShares: sdk.ZeroRat,
UnbondedShares: sdk.ZeroRat,
BondedPool: 0,
UnbondedPool: 0,
InflationLastTime: 0,
Inflation: sdk.NewRat(7, 100),
}
}
// get the bond ratio of the global state
func (p Pool) bondedRatio() sdk.Rat {
if p.TotalSupply > 0 {
return sdk.NewRat(p.BondedPool, p.TotalSupply)
}
return sdk.ZeroRat
}
// get the exchange rate of bonded token per issued share
func (p Pool) bondedShareExRate() sdk.Rat {
if p.BondedShares.IsZero() {
return sdk.OneRat
}
return sdk.NewRat(p.BondedPool).Quo(p.BondedShares)
}
// get the exchange rate of unbonded tokens held in candidates per issued share
func (p Pool) unbondedShareExRate() sdk.Rat {
if p.UnbondedShares.IsZero() {
return sdk.OneRat
}
return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares)
}
//_______________________________________________________________________________________________________
// CandidateStatus - status of a validator-candidate
type CandidateStatus byte
const (
// nolint
Bonded CandidateStatus = 0x00
Unbonded CandidateStatus = 0x01
Revoked CandidateStatus = 0x02
)
// Candidate defines the total amount of bond shares and their exchange rate to
// coins. Accumulation of interest is modelled as an in increase in the
// exchange rate, and slashing as a decrease. When coins are delegated to this
// candidate, the candidate is credited with a DelegatorBond whose number of
// bond shares is based on the amount of coins delegated divided by the current
// exchange rate. Voting power can be calculated as total bonds multiplied by
// exchange rate.
type Candidate struct {
Status CandidateStatus `json:"status"` // Bonded status
Address sdk.Address `json:"owner"` // Sender of BondTx - UnbondTx returns here
PubKey crypto.PubKey `json:"pub_key"` // Pubkey of candidate
Assets sdk.Rat `json:"assets"` // total shares of a global hold pools
Liabilities sdk.Rat `json:"liabilities"` // total shares issued to a candidate's delegators
Description Description `json:"description"` // Description terms for the candidate
}
// NewCandidate - initialize a new candidate
func NewCandidate(address sdk.Address, pubKey crypto.PubKey, description Description) Candidate {
return Candidate{
Status: Unbonded,
Address: address,
PubKey: pubKey,
Assets: sdk.ZeroRat,
Liabilities: sdk.ZeroRat,
Description: description,
}
}
// Description - description fields for a candidate
type Description struct {
Moniker string `json:"moniker"`
Identity string `json:"identity"`
Website string `json:"website"`
Details string `json:"details"`
}
func NewDescription(moniker, identity, website, details string) Description {
return Description{
Moniker: moniker,
Identity: identity,
Website: website,
Details: details,
}
}
// get the exchange rate of global pool shares over delegator shares
func (c Candidate) delegatorShareExRate() sdk.Rat {
if c.Liabilities.IsZero() {
return sdk.OneRat
}
return c.Assets.Quo(c.Liabilities)
}
// Validator returns a copy of the Candidate as a Validator.
// Should only be called when the Candidate qualifies as a validator.
func (c Candidate) validator() Validator {
return Validator{
Address: c.Address, // XXX !!!
VotingPower: c.Assets,
}
}
//XXX updateDescription function
//XXX enforce limit to number of description characters
//______________________________________________________________________
// Validator is one of the top Candidates
type Validator struct {
Address sdk.Address `json:"address"` // Address of validator
VotingPower sdk.Rat `json:"voting_power"` // Voting power if considered a validator
}
// ABCIValidator - Get the validator from a bond value
/* TODO
func (v Validator) ABCIValidator() (*abci.Validator, error) {
pkBytes, err := wire.MarshalBinary(v.PubKey)
if err != nil {
return nil, err
}
return &abci.Validator{
PubKey: pkBytes,
Power: v.VotingPower.Evaluate(),
}, nil
}
*/
//_________________________________________________________________________
// Candidates - list of Candidates
type Candidates []Candidate
//_________________________________________________________________________
// DelegatorBond represents the bond with tokens held by an account. It is
// owned by one delegator, and is associated with the voting power of one
// pubKey.
// TODO better way of managing space
type DelegatorBond struct {
DelegatorAddr sdk.Address `json:"delegatoraddr"`
CandidateAddr sdk.Address `json:"candidate_addr"`
Shares sdk.Rat `json:"shares"`
}

3
x/stake/types_test.go Normal file
View File

@ -0,0 +1,3 @@
package stake
// XXX test global state functions, candidate exchange rate functions etc.

12
x/stake/wire.go Normal file
View File

@ -0,0 +1,12 @@
package stake
import (
"github.com/cosmos/cosmos-sdk/wire"
)
// XXX complete
func RegisterWire(cdc *wire.Codec) {
// TODO include option to always include prefix bytes.
//cdc.RegisterConcrete(SendMsg{}, "cosmos-sdk/SendMsg", nil)
//cdc.RegisterConcrete(IssueMsg{}, "cosmos-sdk/IssueMsg", nil)
}