commit
797aec2e71
|
@ -1,22 +1,23 @@
|
|||
# OS
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode
|
||||
|
||||
# Build
|
||||
vendor
|
||||
merkleeyes.db
|
||||
build
|
||||
docs/guide/*.sh
|
||||
tools/bin/*
|
||||
examples/build/*
|
||||
examples/basecoin/glide.lock
|
||||
docs/_build
|
||||
|
||||
# Data - ideally these don't exist
|
||||
examples/basecoin/app/data
|
||||
baseapp/data/*
|
||||
docs/_build
|
||||
.DS_Store
|
||||
|
||||
# Testing
|
||||
coverage.txt
|
||||
profile.out
|
||||
.vscode
|
||||
coverage.txt
|
||||
profile.out
|
||||
client/lcd/keys.db/
|
||||
|
||||
### Vagrant ###
|
||||
.vagrant/
|
||||
|
|
34
CHANGELOG.md
34
CHANGELOG.md
|
@ -1,5 +1,37 @@
|
|||
# Changelog
|
||||
|
||||
## 0.13.0 (April 2, 2018)
|
||||
|
||||
BREAKING CHANGES
|
||||
|
||||
* [basecoin] Remove cool/sketchy modules -> moved to new `democoin`
|
||||
* [basecoin] NewBasecoinApp takes a `map[string]dbm.DB` as temporary measure
|
||||
to allow mounting multiple stores with their own DB until they can share one
|
||||
* [staking] Renamed to `simplestake`
|
||||
* [builder] Functions don't take `passphrase` as argument
|
||||
* [server] GenAppState returns generated seed and address
|
||||
* [basecoind] `init` command outputs JSON of everything necessary for testnet
|
||||
* [basecoind] `basecoin.db -> data/basecoin.db`
|
||||
* [basecli] `data/keys.db -> keys/keys.db`
|
||||
|
||||
FEATURES
|
||||
|
||||
* [types] `Coin` supports direct arithmetic operations
|
||||
* [basecoind] Add `show_validator` and `show_node_id` commands
|
||||
* [staking] Initial merge of full staking module!
|
||||
* [democoin] New example application to demo custom modules
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
* [makefile] `make install`
|
||||
* [testing] Use `/tmp` for directories so they don't get left in the repo
|
||||
|
||||
BUG FIXES
|
||||
|
||||
* [basecoin] Allow app to be restarted
|
||||
* [makefile] Fix build on Windows
|
||||
* [basecli] Get confirmation before overriding key with same name
|
||||
|
||||
## 0.12.0 (March 27 2018)
|
||||
|
||||
BREAKING CHANGES
|
||||
|
@ -26,7 +58,7 @@ FEATURES
|
|||
* [types] StdFee, and StdTx takes the StdFee
|
||||
* [specs] Progression of MVPs for IBC
|
||||
* [x/ibc] Initial shell of IBC functionality (no proofs)
|
||||
* [x/staking] Simple staking module with bonding/unbonding
|
||||
* [x/simplestake] Simple staking module with bonding/unbonding
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
|
|
|
@ -458,6 +458,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "0eb39694057c8ab8c9ecbaeb25bc43cbf1d2422976a09a67392a62dcef149a7b"
|
||||
inputs-digest = "ed1f3f7f1728cd02945f90ca780e9bdc982573a36a5cc8d7e9f19fb40ba2ca19"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
24
Makefile
24
Makefile
|
@ -1,5 +1,6 @@
|
|||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=`git rev-parse --short HEAD`"
|
||||
COMMIT_HASH := $(shell git rev-parse --short HEAD)
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}"
|
||||
|
||||
all: check_tools get_vendor_deps build test
|
||||
|
||||
|
@ -17,9 +18,25 @@ gaia:
|
|||
go build $(BUILD_FLAGS) -o build/gaiacli ./examples/gaia/gaiacli
|
||||
|
||||
build:
|
||||
@rm -rf examples/basecoin/vendor/
|
||||
@rm -rf $(shell pwd)/examples/basecoin/vendor/
|
||||
@rm -rf $(shell pwd)/examples/democoin/vendor/
|
||||
ifeq ($(OS),Windows_NT)
|
||||
go build $(BUILD_FLAGS) -o build/basecoind.exe ./examples/basecoin/cmd/basecoind
|
||||
go build $(BUILD_FLAGS) -o build/basecli.exe ./examples/basecoin/cmd/basecli
|
||||
go build $(BUILD_FLAGS) -o build/democoind.exe ./examples/democoin/cmd/democoind
|
||||
go build $(BUILD_FLAGS) -o build/democli.exe ./examples/democoin/cmd/democli
|
||||
else
|
||||
go build $(BUILD_FLAGS) -o build/basecoind ./examples/basecoin/cmd/basecoind
|
||||
go build $(BUILD_FLAGS) -o build/basecli ./examples/basecoin/cmd/basecli
|
||||
go build $(BUILD_FLAGS) -o build/democoind ./examples/democoin/cmd/democoind
|
||||
go build $(BUILD_FLAGS) -o build/democli ./examples/democoin/cmd/democli
|
||||
endif
|
||||
|
||||
install:
|
||||
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecoind
|
||||
go install $(BUILD_FLAGS) ./examples/basecoin/cmd/basecli
|
||||
go install $(BUILD_FLAGS) ./examples/democoin/cmd/democoind
|
||||
go install $(BUILD_FLAGS) ./examples/democoin/cmd/democli
|
||||
|
||||
dist:
|
||||
@bash publish/dist.sh
|
||||
|
@ -68,13 +85,12 @@ test: test_unit # test_cli
|
|||
|
||||
test_unit:
|
||||
@rm -rf examples/basecoin/vendor/
|
||||
@rm -rf examples/democoin/vendor/
|
||||
@go test $(PACKAGES)
|
||||
|
||||
test_cover:
|
||||
@rm -rf examples/basecoin/vendor/
|
||||
@rm -rf client/lcd/keys.db ~/.tendermint_test
|
||||
@bash tests/test_cover.sh
|
||||
@rm -rf client/lcd/keys.db ~/.tendermint_test
|
||||
|
||||
benchmark:
|
||||
@go test -bench=. $(PACKAGES)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package baseapp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
|
@ -54,6 +55,7 @@ type BaseApp struct {
|
|||
var _ abci.Application = (*BaseApp)(nil)
|
||||
|
||||
// Create and name new BaseApp
|
||||
// NOTE: The db is used to store the version number for now.
|
||||
func NewBaseApp(name string, logger log.Logger, db dbm.DB) *BaseApp {
|
||||
return &BaseApp{
|
||||
Logger: logger,
|
||||
|
@ -70,12 +72,18 @@ func (app *BaseApp) Name() string {
|
|||
}
|
||||
|
||||
// Mount a store to the provided key in the BaseApp multistore
|
||||
// Broken until #532 is implemented.
|
||||
func (app *BaseApp) MountStoresIAVL(keys ...*sdk.KVStoreKey) {
|
||||
for _, key := range keys {
|
||||
app.MountStore(key, sdk.StoreTypeIAVL)
|
||||
}
|
||||
}
|
||||
|
||||
// Mount a store to the provided key in the BaseApp multistore
|
||||
func (app *BaseApp) MountStoreWithDB(key sdk.StoreKey, typ sdk.StoreType, db dbm.DB) {
|
||||
app.cms.MountStoreWithDB(key, typ, db)
|
||||
}
|
||||
|
||||
// Mount a store to the provided key in the BaseApp multistore
|
||||
func (app *BaseApp) MountStore(key sdk.StoreKey, typ sdk.StoreType) {
|
||||
app.cms.MountStoreWithDB(key, typ, app.db)
|
||||
|
@ -233,6 +241,14 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
|
|||
app.setDeliverState(abci.Header{})
|
||||
app.initChainer(app.deliverState.ctx, req) // no error
|
||||
|
||||
// Initialize module genesis state
|
||||
genesisState := new(map[string]json.RawMessage)
|
||||
err := json.Unmarshal(req.AppStateBytes, genesisState)
|
||||
if err != nil {
|
||||
// TODO Return something intelligent
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// NOTE: we don't commit, but BeginBlock for block 1
|
||||
// starts from this deliverState
|
||||
|
||||
|
|
|
@ -35,12 +35,15 @@ func TestMountStores(t *testing.T) {
|
|||
|
||||
// make some cap keys
|
||||
capKey1 := sdk.NewKVStoreKey("key1")
|
||||
db1 := dbm.NewMemDB()
|
||||
capKey2 := sdk.NewKVStoreKey("key2")
|
||||
db2 := dbm.NewMemDB()
|
||||
|
||||
// no stores are mounted
|
||||
assert.Panics(t, func() { app.LoadLatestVersion(capKey1) })
|
||||
|
||||
app.MountStoresIAVL(capKey1, capKey2)
|
||||
app.MountStoreWithDB(capKey1, sdk.StoreTypeIAVL, db1)
|
||||
app.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db2)
|
||||
|
||||
// stores are mounted
|
||||
err := app.LoadLatestVersion(capKey1)
|
||||
|
@ -126,7 +129,6 @@ func TestTxDecoder(t *testing.T) {
|
|||
|
||||
// Test that Info returns the latest committed state.
|
||||
func TestInfo(t *testing.T) {
|
||||
|
||||
app := newBaseApp(t.Name())
|
||||
|
||||
// ----- test an empty response -------
|
||||
|
@ -145,17 +147,19 @@ func TestInfo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestInitChainer(t *testing.T) {
|
||||
logger := defaultLogger()
|
||||
db := dbm.NewMemDB()
|
||||
name := t.Name()
|
||||
db := dbm.NewMemDB()
|
||||
logger := defaultLogger()
|
||||
app := NewBaseApp(name, logger, db)
|
||||
|
||||
// make cap keys and mount the stores
|
||||
// NOTE/TODO: mounting multiple stores is broken
|
||||
// see https://github.com/cosmos/cosmos-sdk/issues/532
|
||||
capKey := sdk.NewKVStoreKey("main")
|
||||
// capKey2 := sdk.NewKVStoreKey("key2")
|
||||
app.MountStoresIAVL(capKey) // , capKey2)
|
||||
db1 := dbm.NewMemDB()
|
||||
capKey2 := sdk.NewKVStoreKey("key2")
|
||||
db2 := dbm.NewMemDB()
|
||||
app.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db1)
|
||||
app.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db2)
|
||||
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -180,16 +184,15 @@ func TestInitChainer(t *testing.T) {
|
|||
|
||||
// set initChainer and try again - should see the value
|
||||
app.SetInitChainer(initChainer)
|
||||
app.InitChain(abci.RequestInitChain{})
|
||||
app.InitChain(abci.RequestInitChain{AppStateBytes: []byte("{}")}) // must have valid JSON genesis file, even if empty
|
||||
app.Commit()
|
||||
res = app.Query(query)
|
||||
assert.Equal(t, value, res.Value)
|
||||
|
||||
// reload app
|
||||
app = NewBaseApp(name, logger, db)
|
||||
capKey = sdk.NewKVStoreKey("main")
|
||||
// capKey2 = sdk.NewKVStoreKey("key2") // TODO
|
||||
app.MountStoresIAVL(capKey) //, capKey2)
|
||||
app.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db1)
|
||||
app.MountStoreWithDB(capKey2, sdk.StoreTypeIAVL, db2)
|
||||
err = app.LoadLatestVersion(capKey) // needed to make stores non-nil
|
||||
assert.Nil(t, err)
|
||||
app.SetInitChainer(initChainer)
|
||||
|
|
|
@ -12,7 +12,7 @@ type Router interface {
|
|||
Route(path string) (h sdk.Handler)
|
||||
}
|
||||
|
||||
// map a transaction type to a handler
|
||||
// map a transaction type to a handler and an initgenesis function
|
||||
type route struct {
|
||||
r string
|
||||
h sdk.Handler
|
||||
|
|
|
@ -124,7 +124,12 @@ func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte
|
|||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func SignBuildBroadcast(name string, passphrase string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
func SignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
passphrase, err := GetPassphraseFromStdin(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txBytes, err := SignAndBuild(name, passphrase, msg, cdc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -132,3 +137,10 @@ func SignBuildBroadcast(name string, passphrase string, msg sdk.Msg, cdc *wire.C
|
|||
|
||||
return BroadcastTx(txBytes)
|
||||
}
|
||||
|
||||
// get passphrase from std input
|
||||
func GetPassphraseFromStdin(name string) (pass string, err error) {
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
return client.GetPassword(prompt, buf)
|
||||
}
|
||||
|
|
|
@ -73,6 +73,28 @@ func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error)
|
|||
return pass, nil
|
||||
}
|
||||
|
||||
// GetConfirmation will request user give the confirmation from stdin.
|
||||
// "y", "Y", "yes", "YES", and "Yes" all count as confirmations.
|
||||
// If the input is not recognized, it will ask again.
|
||||
func GetConfirmation(prompt string, buf *bufio.Reader) (bool, error) {
|
||||
for {
|
||||
if inputIsTty() {
|
||||
fmt.Print(fmt.Sprintf("%s [y/n]:", prompt))
|
||||
}
|
||||
response, err := readLineFromBuf(buf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
response = strings.ToLower(strings.TrimSpace(response))
|
||||
if response == "y" || response == "yes" {
|
||||
return true, nil
|
||||
} else if response == "n" || response == "no" {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inputIsTty returns true iff we have an interactive prompt,
|
||||
// where we can disable echo and request to repeat the password.
|
||||
// If false, we can optimize for piped input from another command
|
||||
|
|
|
@ -60,6 +60,16 @@ func runAddCmd(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := kb.Get(name)
|
||||
if err == nil {
|
||||
// account exists, ask for user confirmation
|
||||
if response, err := client.GetConfirmation(
|
||||
fmt.Sprintf("override the existing name %s", name), buf); err != nil || !response {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pass, err = client.GetCheckPassword(
|
||||
"Enter a passphrase for your key:",
|
||||
"Repeat the passphrase:", buf)
|
||||
|
|
|
@ -2,6 +2,7 @@ package keys
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
|
@ -32,7 +33,7 @@ type KeyOutput struct {
|
|||
func GetKeyBase() (keys.Keybase, error) {
|
||||
if keybase == nil {
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
db, err := dbm.NewGoLevelDB(KeyDBName, rootDir)
|
||||
db, err := dbm.NewGoLevelDB(KeyDBName, filepath.Join(rootDir, "keys"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
tmp-base*
|
|
@ -25,6 +25,7 @@ import (
|
|||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
tmrpc "github.com/tendermint/tendermint/rpc/lib/server"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
|
@ -157,7 +158,7 @@ func TestNodeStatus(t *testing.T) {
|
|||
|
||||
func TestBlock(t *testing.T) {
|
||||
|
||||
time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks
|
||||
waitForHeight(2)
|
||||
|
||||
var resultBlock ctypes.ResultBlock
|
||||
|
||||
|
@ -221,8 +222,7 @@ func TestCoinSend(t *testing.T) {
|
|||
|
||||
// create TX
|
||||
receiveAddr, resultTx := doSend(t, port, seed)
|
||||
|
||||
time.Sleep(time.Second * 2) // T
|
||||
waitForHeight(resultTx.Height + 1)
|
||||
|
||||
// check if tx was commited
|
||||
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
|
||||
|
@ -252,6 +252,32 @@ func TestCoinSend(t *testing.T) {
|
|||
assert.Equal(t, int64(1), mycoins.Amount)
|
||||
}
|
||||
|
||||
func TestIBCTransfer(t *testing.T) {
|
||||
|
||||
// create TX
|
||||
resultTx := doIBCTransfer(t, port, seed)
|
||||
|
||||
waitForHeight(resultTx.Height + 1)
|
||||
|
||||
// check if tx was commited
|
||||
assert.Equal(t, uint32(0), resultTx.CheckTx.Code)
|
||||
assert.Equal(t, uint32(0), resultTx.DeliverTx.Code)
|
||||
|
||||
// query sender
|
||||
res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var m auth.BaseAccount
|
||||
err := json.Unmarshal([]byte(body), &m)
|
||||
require.Nil(t, err)
|
||||
coins := m.Coins
|
||||
mycoins := coins[0]
|
||||
assert.Equal(t, coinDenom, mycoins.Denom)
|
||||
assert.Equal(t, coinAmount-2, mycoins.Amount)
|
||||
|
||||
// TODO: query ibc egress packet state
|
||||
}
|
||||
|
||||
func TestTxs(t *testing.T) {
|
||||
|
||||
// TODO: re-enable once we can get txs by tag
|
||||
|
@ -269,7 +295,7 @@ func TestTxs(t *testing.T) {
|
|||
// create TX
|
||||
_, resultTx := doSend(t, port, seed)
|
||||
|
||||
time.Sleep(time.Second * 2) // TO
|
||||
waitForHeight(resultTx.Height + 1)
|
||||
|
||||
// check if tx is findable
|
||||
res, body := request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil)
|
||||
|
@ -294,6 +320,7 @@ func TestTxs(t *testing.T) {
|
|||
// strt TM and the LCD in process, listening on their respective sockets
|
||||
func startTMAndLCD() (*nm.Node, net.Listener, error) {
|
||||
|
||||
viper.Set(cli.HomeFlag, os.TempDir())
|
||||
kb, err := keys.GetKeyBase() // dbm.NewMemDB()) // :(
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -315,7 +342,13 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) {
|
|||
logger = log.NewFilter(logger, log.AllowError())
|
||||
privValidatorFile := config.PrivValidatorFile()
|
||||
privVal := tmtypes.LoadOrGenPrivValidatorFS(privValidatorFile)
|
||||
app := bapp.NewBasecoinApp(logger, dbm.NewMemDB())
|
||||
dbs := map[string]dbm.DB{
|
||||
"main": dbm.NewMemDB(),
|
||||
"acc": dbm.NewMemDB(),
|
||||
"ibc": dbm.NewMemDB(),
|
||||
"staking": dbm.NewMemDB(),
|
||||
}
|
||||
app := bapp.NewBasecoinApp(logger, dbs)
|
||||
|
||||
genesisFile := config.GenesisFile()
|
||||
genDoc, err := tmtypes.GenesisDocFromFile(genesisFile)
|
||||
|
@ -324,8 +357,8 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) {
|
|||
}
|
||||
|
||||
coins := sdk.Coins{{coinDenom, coinAmount}}
|
||||
appState := btypes.GenesisState{
|
||||
Accounts: []*btypes.GenesisAccount{
|
||||
appState := map[string]interface{}{
|
||||
"accounts": []*btypes.GenesisAccount{
|
||||
{
|
||||
Name: "tester",
|
||||
Address: pubKey.Address(),
|
||||
|
@ -358,7 +391,7 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) {
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 2)
|
||||
waitForStart()
|
||||
|
||||
return node, lcd, nil
|
||||
}
|
||||
|
@ -408,6 +441,7 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res
|
|||
require.Nil(t, err)
|
||||
|
||||
output, err := ioutil.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
require.Nil(t, err)
|
||||
|
||||
return res, string(output)
|
||||
|
@ -427,8 +461,6 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype
|
|||
acc := auth.BaseAccount{}
|
||||
err = json.Unmarshal([]byte(body), &acc)
|
||||
require.Nil(t, err)
|
||||
fmt.Println("BODY", body)
|
||||
fmt.Println("ACC", acc)
|
||||
sequence := acc.Sequence
|
||||
|
||||
// send
|
||||
|
@ -441,3 +473,99 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype
|
|||
|
||||
return receiveAddr, resultTx
|
||||
}
|
||||
|
||||
func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) {
|
||||
|
||||
// create receive address
|
||||
kb := client.MockKeyBase()
|
||||
receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519"))
|
||||
require.Nil(t, err)
|
||||
receiveAddr := receiveInfo.PubKey.Address().String()
|
||||
|
||||
// get the account to get the sequence
|
||||
res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil)
|
||||
// require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
acc := auth.BaseAccount{}
|
||||
err = json.Unmarshal([]byte(body), &acc)
|
||||
require.Nil(t, err)
|
||||
sequence := acc.Sequence
|
||||
|
||||
// send
|
||||
jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom))
|
||||
res, body = request(t, port, "POST", "/ibc/testchain/"+receiveAddr+"/send", jsonStr)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
err = json.Unmarshal([]byte(body), &resultTx)
|
||||
require.Nil(t, err)
|
||||
|
||||
return resultTx
|
||||
}
|
||||
|
||||
func waitForHeight(height int64) {
|
||||
for {
|
||||
var resultBlock ctypes.ResultBlock
|
||||
|
||||
url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest")
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
|
||||
err = json.Unmarshal([]byte(body), &resultBlock)
|
||||
if err != nil {
|
||||
fmt.Println("RES", res)
|
||||
fmt.Println("BODY", string(body))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resultBlock.Block.Height >= height {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
|
||||
// wait for 2 blocks
|
||||
func waitForStart() {
|
||||
waitHeight := int64(2)
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
var resultBlock ctypes.ResultBlock
|
||||
|
||||
url := fmt.Sprintf("http://localhost:%v%v", port, "/blocks/latest")
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// waiting for server to start ...
|
||||
if res.StatusCode != http.StatusOK {
|
||||
res.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
res.Body.Close()
|
||||
|
||||
err = json.Unmarshal([]byte(body), &resultBlock)
|
||||
if err != nil {
|
||||
fmt.Println("RES", res)
|
||||
fmt.Println("BODY", string(body))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resultBlock.Block.Height >= waitHeight {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth/rest"
|
||||
bank "github.com/cosmos/cosmos-sdk/x/bank/rest"
|
||||
ibc "github.com/cosmos/cosmos-sdk/x/ibc/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -78,5 +79,6 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
|||
tx.RegisterRoutes(r, cdc)
|
||||
auth.RegisterRoutes(r, cdc, "main")
|
||||
bank.RegisterRoutes(r, cdc, kb)
|
||||
ibc.RegisterRoutes(r, cdc, kb)
|
||||
return r
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -15,11 +15,9 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/simplestake"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/sketchy"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -33,6 +31,7 @@ type BasecoinApp struct {
|
|||
|
||||
// keys to access the substores
|
||||
capKeyMainStore *sdk.KVStoreKey
|
||||
capKeyAccountStore *sdk.KVStoreKey
|
||||
capKeyIBCStore *sdk.KVStoreKey
|
||||
capKeyStakingStore *sdk.KVStoreKey
|
||||
|
||||
|
@ -40,14 +39,15 @@ type BasecoinApp struct {
|
|||
accountMapper sdk.AccountMapper
|
||||
}
|
||||
|
||||
func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
|
||||
func NewBasecoinApp(logger log.Logger, dbs map[string]dbm.DB) *BasecoinApp {
|
||||
// create your application object
|
||||
var app = &BasecoinApp{
|
||||
BaseApp: bam.NewBaseApp(appName, logger, db),
|
||||
BaseApp: bam.NewBaseApp(appName, logger, dbs["main"]),
|
||||
cdc: MakeCodec(),
|
||||
capKeyMainStore: sdk.NewKVStoreKey("main"),
|
||||
capKeyAccountStore: sdk.NewKVStoreKey("acc"),
|
||||
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
|
||||
capKeyStakingStore: sdk.NewKVStoreKey("staking"),
|
||||
capKeyStakingStore: sdk.NewKVStoreKey("stake"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
|
@ -58,20 +58,22 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
|
|||
|
||||
// add handlers
|
||||
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
|
||||
coolMapper := cool.NewMapper(app.capKeyMainStore)
|
||||
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
|
||||
stakingMapper := staking.NewMapper(app.capKeyStakingStore)
|
||||
stakeKeeper := simplestake.NewKeeper(app.capKeyStakingStore, coinKeeper)
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(coinKeeper)).
|
||||
AddRoute("cool", cool.NewHandler(coinKeeper, coolMapper)).
|
||||
AddRoute("sketchy", sketchy.NewHandler()).
|
||||
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
|
||||
AddRoute("staking", staking.NewHandler(stakingMapper, coinKeeper))
|
||||
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper))
|
||||
|
||||
// initialize BaseApp
|
||||
app.SetTxDecoder(app.txDecoder)
|
||||
app.SetInitChainer(app.initChainer)
|
||||
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore, app.capKeyStakingStore)
|
||||
app.MountStoreWithDB(app.capKeyMainStore, sdk.StoreTypeIAVL, dbs["main"])
|
||||
app.MountStoreWithDB(app.capKeyAccountStore, sdk.StoreTypeIAVL, dbs["acc"])
|
||||
app.MountStoreWithDB(app.capKeyIBCStore, sdk.StoreTypeIAVL, dbs["ibc"])
|
||||
app.MountStoreWithDB(app.capKeyStakingStore, sdk.StoreTypeIAVL, dbs["staking"])
|
||||
// NOTE: Broken until #532 lands
|
||||
//app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore, app.capKeyStakingStore)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper))
|
||||
err := app.LoadLatestVersion(app.capKeyMainStore)
|
||||
if err != nil {
|
||||
|
@ -96,12 +98,10 @@ func MakeCodec() *wire.Codec {
|
|||
struct{ sdk.Msg }{},
|
||||
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
|
||||
oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue},
|
||||
oldwire.ConcreteType{cool.QuizMsg{}, msgTypeQuiz},
|
||||
oldwire.ConcreteType{cool.SetTrendMsg{}, msgTypeSetTrend},
|
||||
oldwire.ConcreteType{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg},
|
||||
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
|
||||
oldwire.ConcreteType{staking.BondMsg{}, msgTypeBondMsg},
|
||||
oldwire.ConcreteType{staking.UnbondMsg{}, msgTypeUnbondMsg},
|
||||
oldwire.ConcreteType{simplestake.BondMsg{}, msgTypeBondMsg},
|
||||
oldwire.ConcreteType{simplestake.UnbondMsg{}, msgTypeUnbondMsg},
|
||||
)
|
||||
|
||||
const accTypeApp = 0x1
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
|
@ -26,50 +25,93 @@ import (
|
|||
var (
|
||||
chainID = "" // TODO
|
||||
|
||||
priv1 = crypto.GenPrivKeyEd25519()
|
||||
addr1 = priv1.PubKey().Address()
|
||||
addr2 = crypto.GenPrivKeyEd25519().PubKey().Address()
|
||||
coins = sdk.Coins{{"foocoin", 10}}
|
||||
fee = sdk.StdFee{
|
||||
accName = "foobart"
|
||||
|
||||
priv1 = crypto.GenPrivKeyEd25519()
|
||||
addr1 = priv1.PubKey().Address()
|
||||
priv2 = crypto.GenPrivKeyEd25519()
|
||||
addr2 = priv2.PubKey().Address()
|
||||
addr3 = crypto.GenPrivKeyEd25519().PubKey().Address()
|
||||
priv4 = crypto.GenPrivKeyEd25519()
|
||||
addr4 = priv4.PubKey().Address()
|
||||
coins = sdk.Coins{{"foocoin", 10}}
|
||||
halfCoins = sdk.Coins{{"foocoin", 5}}
|
||||
fee = sdk.StdFee{
|
||||
sdk.Coins{{"foocoin", 0}},
|
||||
0,
|
||||
}
|
||||
|
||||
sendMsg = bank.SendMsg{
|
||||
sendMsg1 = bank.SendMsg{
|
||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
||||
}
|
||||
|
||||
quizMsg1 = cool.QuizMsg{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "icecold",
|
||||
sendMsg2 = bank.SendMsg{
|
||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||
Outputs: []bank.Output{
|
||||
bank.NewOutput(addr2, halfCoins),
|
||||
bank.NewOutput(addr3, halfCoins),
|
||||
},
|
||||
}
|
||||
|
||||
quizMsg2 = cool.QuizMsg{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "badvibesonly",
|
||||
sendMsg3 = bank.SendMsg{
|
||||
Inputs: []bank.Input{
|
||||
bank.NewInput(addr1, coins),
|
||||
bank.NewInput(addr4, coins),
|
||||
},
|
||||
Outputs: []bank.Output{
|
||||
bank.NewOutput(addr2, coins),
|
||||
bank.NewOutput(addr3, coins),
|
||||
},
|
||||
}
|
||||
|
||||
setTrendMsg1 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "icecold",
|
||||
}
|
||||
|
||||
setTrendMsg2 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "badvibesonly",
|
||||
}
|
||||
|
||||
setTrendMsg3 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "warmandkind",
|
||||
sendMsg4 = bank.SendMsg{
|
||||
Inputs: []bank.Input{
|
||||
bank.NewInput(addr2, coins),
|
||||
},
|
||||
Outputs: []bank.Output{
|
||||
bank.NewOutput(addr1, coins),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func newBasecoinApp() *BasecoinApp {
|
||||
func loggerAndDBs() (log.Logger, map[string]dbm.DB) {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
db := dbm.NewMemDB()
|
||||
return NewBasecoinApp(logger, db)
|
||||
dbs := map[string]dbm.DB{
|
||||
"main": dbm.NewMemDB(),
|
||||
"acc": dbm.NewMemDB(),
|
||||
"ibc": dbm.NewMemDB(),
|
||||
"staking": dbm.NewMemDB(),
|
||||
}
|
||||
return logger, dbs
|
||||
}
|
||||
|
||||
func newBasecoinApp() *BasecoinApp {
|
||||
logger, dbs := loggerAndDBs()
|
||||
return NewBasecoinApp(logger, dbs)
|
||||
}
|
||||
|
||||
func setGenesisAccounts(bapp *BasecoinApp, accs ...auth.BaseAccount) error {
|
||||
genaccs := make([]*types.GenesisAccount, len(accs))
|
||||
for i, acc := range accs {
|
||||
genaccs[i] = types.NewGenesisAccount(&types.AppAccount{acc, accName})
|
||||
}
|
||||
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: genaccs,
|
||||
}
|
||||
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Initialize the chain
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
@ -80,41 +122,18 @@ func TestMsgs(t *testing.T) {
|
|||
msgs := []struct {
|
||||
msg sdk.Msg
|
||||
}{
|
||||
{sendMsg},
|
||||
{quizMsg1},
|
||||
{setTrendMsg1},
|
||||
{sendMsg1},
|
||||
}
|
||||
|
||||
sequences := []int64{0}
|
||||
for i, m := range msgs {
|
||||
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, m.msg))
|
||||
tx := sdk.NewStdTx(m.msg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: sig,
|
||||
}})
|
||||
|
||||
// just marshal/unmarshal!
|
||||
cdc := MakeCodec()
|
||||
txBytes, err := cdc.MarshalBinary(tx)
|
||||
require.NoError(t, err, "i: %v", i)
|
||||
|
||||
// Run a Check
|
||||
cres := bapp.CheckTx(txBytes)
|
||||
assert.Equal(t, sdk.CodeUnknownAddress,
|
||||
sdk.CodeType(cres.Code), "i: %v, log: %v", i, cres.Log)
|
||||
|
||||
// Simulate a Block
|
||||
bapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
dres := bapp.DeliverTx(txBytes)
|
||||
assert.Equal(t, sdk.CodeUnknownAddress,
|
||||
sdk.CodeType(dres.Code), "i: %v, log: %v", i, dres.Log)
|
||||
// Run a CheckDeliver
|
||||
SignCheckDeliver(t, bapp, m.msg, []int64{int64(i)}, false, priv1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesis(t *testing.T) {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
db := dbm.NewMemDB()
|
||||
bapp := NewBasecoinApp(logger, db)
|
||||
logger, dbs := loggerAndDBs()
|
||||
bapp := NewBasecoinApp(logger, dbs)
|
||||
|
||||
// Construct some genesis bytes to reflect basecoin/types/AppAccount
|
||||
pk := crypto.GenPrivKeyEd25519().PubKey()
|
||||
|
@ -127,28 +146,19 @@ func TestGenesis(t *testing.T) {
|
|||
}
|
||||
acc := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc),
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
err = setGenesisAccounts(bapp, baseAcc)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// A checkTx context
|
||||
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
|
||||
assert.Equal(t, acc, res1)
|
||||
/*
|
||||
// reload app and ensure the account is still there
|
||||
bapp = NewBasecoinApp(logger, db)
|
||||
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
|
||||
assert.Equal(t, acc, res1)
|
||||
*/
|
||||
|
||||
// reload app and ensure the account is still there
|
||||
bapp = NewBasecoinApp(logger, dbs)
|
||||
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
|
||||
assert.Equal(t, acc, res1)
|
||||
}
|
||||
|
||||
func TestSendMsgWithAccounts(t *testing.T) {
|
||||
|
@ -162,66 +172,124 @@ func TestSendMsgWithAccounts(t *testing.T) {
|
|||
Address: addr1,
|
||||
Coins: coins,
|
||||
}
|
||||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
// Construct genesis state
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
require.Nil(t, err)
|
||||
|
||||
// Initialize the chain
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
err = setGenesisAccounts(bapp, baseAcc)
|
||||
assert.Nil(t, err)
|
||||
// A checkTx context (true)
|
||||
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
assert.Equal(t, acc1, res1)
|
||||
assert.Equal(t, baseAcc, res1.(*types.AppAccount).BaseAccount)
|
||||
|
||||
// Sign the tx
|
||||
sequences := []int64{0}
|
||||
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, sendMsg))
|
||||
tx := sdk.NewStdTx(sendMsg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: sig,
|
||||
}})
|
||||
|
||||
// Run a Check
|
||||
res := bapp.Check(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
|
||||
// Simulate a Block
|
||||
bapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
// Run a CheckDeliver
|
||||
SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1)
|
||||
|
||||
// Check balances
|
||||
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
|
||||
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
|
||||
res3 := bapp.accountMapper.GetAccount(ctxDeliver, addr2)
|
||||
assert.Equal(t, fmt.Sprintf("%v", res2.GetCoins()), "67foocoin")
|
||||
assert.Equal(t, fmt.Sprintf("%v", res3.GetCoins()), "10foocoin")
|
||||
CheckBalance(t, bapp, addr1, "67foocoin")
|
||||
CheckBalance(t, bapp, addr2, "10foocoin")
|
||||
|
||||
// Delivering again should cause replay error
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeInvalidSequence, res.Code, res.Log)
|
||||
SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, false, priv1)
|
||||
|
||||
// bumping the txnonce number without resigning should be an auth error
|
||||
tx := genTx(sendMsg1, []int64{0}, priv1)
|
||||
tx.Signatures[0].Sequence = 1
|
||||
res = bapp.Deliver(tx)
|
||||
res := bapp.Deliver(tx)
|
||||
|
||||
assert.Equal(t, sdk.CodeUnauthorized, res.Code, res.Log)
|
||||
|
||||
// resigning the tx with the bumped sequence should work
|
||||
sequences = []int64{1}
|
||||
sig = priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, tx.Msg))
|
||||
tx.Signatures[0].Signature = sig
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
SignCheckDeliver(t, bapp, sendMsg1, []int64{1}, true, priv1)
|
||||
}
|
||||
|
||||
func TestSendMsgMultipleOut(t *testing.T) {
|
||||
bapp := newBasecoinApp()
|
||||
|
||||
genCoins, err := sdk.ParseCoins("42foocoin")
|
||||
require.Nil(t, err)
|
||||
|
||||
acc1 := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
acc2 := auth.BaseAccount{
|
||||
Address: addr2,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
err = setGenesisAccounts(bapp, acc1, acc2)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Simulate a Block
|
||||
SignCheckDeliver(t, bapp, sendMsg2, []int64{0}, true, priv1)
|
||||
|
||||
// Check balances
|
||||
CheckBalance(t, bapp, addr1, "32foocoin")
|
||||
CheckBalance(t, bapp, addr2, "47foocoin")
|
||||
CheckBalance(t, bapp, addr3, "5foocoin")
|
||||
}
|
||||
|
||||
func TestSengMsgMultipleInOut(t *testing.T) {
|
||||
bapp := newBasecoinApp()
|
||||
|
||||
genCoins, err := sdk.ParseCoins("42foocoin")
|
||||
require.Nil(t, err)
|
||||
|
||||
acc1 := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
acc2 := auth.BaseAccount{
|
||||
Address: addr2,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
acc4 := auth.BaseAccount{
|
||||
Address: addr4,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
err = setGenesisAccounts(bapp, acc1, acc2, acc4)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// CheckDeliver
|
||||
SignCheckDeliver(t, bapp, sendMsg3, []int64{0, 0}, true, priv1, priv4)
|
||||
|
||||
// Check balances
|
||||
CheckBalance(t, bapp, addr1, "32foocoin")
|
||||
CheckBalance(t, bapp, addr4, "32foocoin")
|
||||
CheckBalance(t, bapp, addr2, "52foocoin")
|
||||
CheckBalance(t, bapp, addr3, "10foocoin")
|
||||
}
|
||||
|
||||
func TestSendMsgDependent(t *testing.T) {
|
||||
bapp := newBasecoinApp()
|
||||
|
||||
genCoins, err := sdk.ParseCoins("42foocoin")
|
||||
require.Nil(t, err)
|
||||
|
||||
acc1 := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: genCoins,
|
||||
}
|
||||
|
||||
err = setGenesisAccounts(bapp, acc1)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// CheckDeliver
|
||||
SignCheckDeliver(t, bapp, sendMsg1, []int64{0}, true, priv1)
|
||||
|
||||
// Check balances
|
||||
CheckBalance(t, bapp, addr1, "32foocoin")
|
||||
CheckBalance(t, bapp, addr2, "10foocoin")
|
||||
|
||||
// Simulate a Block
|
||||
SignCheckDeliver(t, bapp, sendMsg4, []int64{0}, true, priv2)
|
||||
|
||||
// Check balances
|
||||
CheckBalance(t, bapp, addr1, "42foocoin")
|
||||
}
|
||||
|
||||
func TestQuizMsg(t *testing.T) {
|
||||
|
@ -237,8 +305,8 @@ func TestQuizMsg(t *testing.T) {
|
|||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
// Construct genesis state
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: []*types.GenesisAccount{
|
||||
genesisState := map[string]interface{}{
|
||||
"accounts": []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
}
|
||||
|
@ -255,45 +323,22 @@ func TestQuizMsg(t *testing.T) {
|
|||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
assert.Equal(t, acc1, res1)
|
||||
|
||||
// Set the trend, submit a really cool quiz and check for reward
|
||||
SignCheckDeliver(t, bapp, setTrendMsg1, 0, true)
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 1, true)
|
||||
CheckBalance(t, bapp, "69icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg2, 2, true) // result without reward
|
||||
CheckBalance(t, bapp, "69icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 3, true)
|
||||
CheckBalance(t, bapp, "138icecold")
|
||||
SignCheckDeliver(t, bapp, setTrendMsg2, 4, true) // reset the trend
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 5, true) // the same answer will nolonger do!
|
||||
CheckBalance(t, bapp, "138icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg2, 6, true) // earlier answer now relavent again
|
||||
CheckBalance(t, bapp, "69badvibesonly,138icecold")
|
||||
SignCheckDeliver(t, bapp, setTrendMsg3, 7, false) // expect to fail to set the trend to something which is not cool
|
||||
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
func TestIBCMsgs(t *testing.T) {
|
||||
bapp := newBasecoinApp()
|
||||
|
||||
sourceChain := "source-chain"
|
||||
destChain := "dest-chain"
|
||||
|
||||
vals := []abci.Validator{}
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: coins,
|
||||
}
|
||||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
genesisState := types.GenesisState{
|
||||
Accounts: []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
require.Nil(t, err)
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
err := setGenesisAccounts(bapp, baseAcc)
|
||||
assert.Nil(t, err)
|
||||
// A checkTx context (true)
|
||||
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
|
@ -317,23 +362,32 @@ func TestHandler(t *testing.T) {
|
|||
Sequence: 0,
|
||||
}
|
||||
|
||||
SignCheckDeliver(t, bapp, transferMsg, 0, true)
|
||||
CheckBalance(t, bapp, "")
|
||||
SignCheckDeliver(t, bapp, transferMsg, 1, false)
|
||||
SignCheckDeliver(t, bapp, receiveMsg, 2, true)
|
||||
CheckBalance(t, bapp, "10foocoin")
|
||||
SignCheckDeliver(t, bapp, receiveMsg, 3, false)
|
||||
SignCheckDeliver(t, bapp, transferMsg, []int64{0}, true, priv1)
|
||||
CheckBalance(t, bapp, addr1, "")
|
||||
SignCheckDeliver(t, bapp, transferMsg, []int64{1}, false, priv1)
|
||||
SignCheckDeliver(t, bapp, receiveMsg, []int64{2}, true, priv1)
|
||||
CheckBalance(t, bapp, addr1, "10foocoin")
|
||||
SignCheckDeliver(t, bapp, receiveMsg, []int64{3}, false, priv1)
|
||||
}
|
||||
|
||||
func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, expPass bool) {
|
||||
func genTx(msg sdk.Msg, seq []int64, priv ...crypto.PrivKeyEd25519) sdk.StdTx {
|
||||
sigs := make([]sdk.StdSignature, len(priv))
|
||||
for i, p := range priv {
|
||||
sigs[i] = sdk.StdSignature{
|
||||
PubKey: p.PubKey(),
|
||||
Signature: p.Sign(sdk.StdSignBytes(chainID, seq, fee, msg)),
|
||||
Sequence: seq[i],
|
||||
}
|
||||
}
|
||||
|
||||
return sdk.NewStdTx(msg, fee, sigs)
|
||||
|
||||
}
|
||||
|
||||
func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) {
|
||||
|
||||
// Sign the tx
|
||||
tx := sdk.NewStdTx(msg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: priv1.Sign(sdk.StdSignBytes(chainID, []int64{seq}, fee, msg)),
|
||||
Sequence: seq,
|
||||
}})
|
||||
|
||||
tx := genTx(msg, seq, priv...)
|
||||
// Run a Check
|
||||
res := bapp.Check(tx)
|
||||
if expPass {
|
||||
|
@ -350,10 +404,12 @@ func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, e
|
|||
} else {
|
||||
require.NotEqual(t, sdk.CodeOK, res.Code, res.Log)
|
||||
}
|
||||
bapp.EndBlock(abci.RequestEndBlock{})
|
||||
//bapp.Commit()
|
||||
}
|
||||
|
||||
func CheckBalance(t *testing.T, bapp *BasecoinApp, balExpected string) {
|
||||
func CheckBalance(t *testing.T, bapp *BasecoinApp, addr sdk.Address, balExpected string) {
|
||||
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
|
||||
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
|
||||
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr)
|
||||
assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins()))
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
@ -13,12 +14,11 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
|
||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/commands"
|
||||
stakingcmd "github.com/cosmos/cosmos-sdk/x/staking/commands"
|
||||
simplestakingcmd "github.com/cosmos/cosmos-sdk/x/simplestake/commands"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
|
||||
|
@ -62,14 +62,6 @@ func main() {
|
|||
client.PostCommands(
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
)...)
|
||||
basecliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
coolcmd.QuizTxCmd(cdc),
|
||||
)...)
|
||||
basecliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
coolcmd.SetTrendTxCmd(cdc),
|
||||
)...)
|
||||
basecliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCTransferCmd(cdc),
|
||||
|
@ -77,11 +69,11 @@ func main() {
|
|||
basecliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCRelayCmd(cdc),
|
||||
stakingcmd.BondTxCmd(cdc),
|
||||
simplestakingcmd.BondTxCmd(cdc),
|
||||
)...)
|
||||
basecliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
stakingcmd.UnbondTxCmd(cdc),
|
||||
simplestakingcmd.UnbondTxCmd(cdc),
|
||||
)...)
|
||||
|
||||
// add proxy, version and key info
|
||||
|
|
|
@ -4,11 +4,13 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
|
@ -27,14 +29,11 @@ var (
|
|||
|
||||
// defaultOptions sets up the app_options for the
|
||||
// default genesis file
|
||||
func defaultOptions(args []string) (json.RawMessage, error) {
|
||||
func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
|
||||
addr, secret, err := server.GenerateCoinKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", nil, err
|
||||
}
|
||||
fmt.Println("Secret phrase to access coins:")
|
||||
fmt.Println(secret)
|
||||
|
||||
opts := fmt.Sprintf(`{
|
||||
"accounts": [{
|
||||
"address": "%s",
|
||||
|
@ -46,15 +45,33 @@ func defaultOptions(args []string) (json.RawMessage, error) {
|
|||
]
|
||||
}]
|
||||
}`, addr)
|
||||
return json.RawMessage(opts), nil
|
||||
return json.RawMessage(opts), secret, addr, nil
|
||||
}
|
||||
|
||||
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
|
||||
db, err := dbm.NewGoLevelDB("basecoin", rootDir)
|
||||
dbMain, err := dbm.NewGoLevelDB("basecoin", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bapp := app.NewBasecoinApp(logger, db)
|
||||
dbAcc, err := dbm.NewGoLevelDB("basecoin-acc", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbIBC, err := dbm.NewGoLevelDB("basecoin-ibc", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbStaking, err := dbm.NewGoLevelDB("basecoin-staking", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbs := map[string]dbm.DB{
|
||||
"main": dbMain,
|
||||
"acc": dbAcc,
|
||||
"ibc": dbIBC,
|
||||
"staking": dbStaking,
|
||||
}
|
||||
bapp := app.NewBasecoinApp(logger, dbs)
|
||||
return bapp, nil
|
||||
}
|
||||
|
||||
|
@ -68,6 +85,7 @@ func main() {
|
|||
server.StartCmd(generateApp, logger),
|
||||
server.UnsafeResetAllCmd(logger),
|
||||
server.ShowNodeIdCmd(logger),
|
||||
server.ShowValidatorCmd(logger),
|
||||
version.VersionCmd,
|
||||
)
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package cool
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// This Cool Mapper handlers sets/gets of custom variables for your module
|
||||
type Mapper struct {
|
||||
key sdk.StoreKey // The (unexposed) key used to access the store from the Context.
|
||||
}
|
||||
|
||||
func NewMapper(key sdk.StoreKey) Mapper {
|
||||
return Mapper{key}
|
||||
}
|
||||
|
||||
// Key to knowing the trend on the streets!
|
||||
var trendKey = []byte("TrendKey")
|
||||
|
||||
// Implements sdk.AccountMapper.
|
||||
func (am Mapper) GetTrend(ctx sdk.Context) string {
|
||||
store := ctx.KVStore(am.key)
|
||||
bz := store.Get(trendKey)
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// Implements sdk.AccountMapper.
|
||||
func (am Mapper) SetTrend(ctx sdk.Context, newTrend string) {
|
||||
store := ctx.KVStore(am.key)
|
||||
store.Set(trendKey, []byte(newTrend))
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
Cosmos-SDK Democoin (template)
|
||||
License: Apache2.0
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 All in Bits, Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,22 @@
|
|||
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
|
||||
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/examples/democoin/version.GitCommit=`git rev-parse --short HEAD`"
|
||||
|
||||
all: get_tools get_vendor_deps build test
|
||||
|
||||
get_tools:
|
||||
go get github.com/golang/dep/cmd/dep
|
||||
|
||||
build:
|
||||
go build $(BUILD_FLAGS) -o build/democoin ./cmd/...
|
||||
|
||||
get_vendor_deps:
|
||||
@rm -rf vendor/
|
||||
@dep ensure
|
||||
|
||||
test:
|
||||
@go test $(PACKAGES)
|
||||
|
||||
benchmark:
|
||||
@go test -bench=. $(PACKAGES)
|
||||
|
||||
.PHONY: all build test benchmark
|
|
@ -0,0 +1,70 @@
|
|||
# Democoin
|
||||
|
||||
This is the "Democoin" example application built on the Cosmos-Sdk. This
|
||||
"Democoin" is not affiliated with [Coinbase](http://www.getdemocoin.com/), nor
|
||||
the [stable coin](http://www.getdemocoin.com/).
|
||||
|
||||
Assuming you've run `make get_tools && make get_vendor_deps` from the root of
|
||||
this repository, run `make build` here to build the `democoind` and `basecli`
|
||||
binaries.
|
||||
|
||||
If you want to create a new application, start by copying the Democoin app.
|
||||
|
||||
|
||||
# Building your own Blockchain
|
||||
|
||||
Democoin is the equivalent of an ERC20 token contract for blockchains. In order
|
||||
to deploy your own application all you need to do is clone `examples/democoin`
|
||||
and run it. Now you are already running your own blockchain. In the following
|
||||
I will explain how to add functionality to your blockchain. This is akin to
|
||||
defining your own vesting schedule within a contract or setting a specific
|
||||
multisig. You are just extending the base layer with extra functionality here
|
||||
and there.
|
||||
|
||||
## Structure of Democoin
|
||||
|
||||
Democoin is build with the cosmos-sdk. It is a sample application that works
|
||||
with any engine that implements the ABCI protocol. Democoin defines multiple
|
||||
unique modules as well as uses modules directly from the sdk. If you want
|
||||
to modify Democoin, you either remove or add modules according to your wishes.
|
||||
|
||||
|
||||
## Modules
|
||||
|
||||
A module is a fundamental unit in the cosmos-sdk. A module defines its own
|
||||
transaction, handles its own state as well as its own state transition logic.
|
||||
Globally, in the `app/app.go` file you just have to define a key for that
|
||||
module to access some parts of the state, as well as initialise the module
|
||||
object and finally add it to the transaction router. The router ensures that
|
||||
every module only gets its own messages.
|
||||
|
||||
|
||||
## Transactions
|
||||
|
||||
A user can send a transaction to the running blockchain application. This
|
||||
transaction can be of any of the ones that are supported by any of the
|
||||
registered modules.
|
||||
|
||||
### CheckTx
|
||||
|
||||
Once a user has submitted their transaction to the engine,
|
||||
the engine will first run `checkTx` to confirm that it is a valid transaction.
|
||||
The module has to define a handler that knows how to handle every transaction
|
||||
type. The corresponding handler gets invoked with the checkTx flag set to true.
|
||||
This means that the handler shouldn't do any expensive operations, but it can
|
||||
and should write to the checkTx state.
|
||||
|
||||
### DeliverTx
|
||||
|
||||
The engine calls `deliverTx` when a new block has been agreed upon in
|
||||
consensus. Again, the corresponding module will have its handler invoked
|
||||
and the state and context is passed in. During deliverTx execution the
|
||||
transaction needs to be processed fully and the results are written to the
|
||||
application state.
|
||||
|
||||
|
||||
## CLI
|
||||
|
||||
The cosmos-sdk contains a number of helper libraries in `clients/` to build cli
|
||||
and RPC interfaces for your specific application.
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
oldwire "github.com/tendermint/go-wire"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
bam "github.com/cosmos/cosmos-sdk/baseapp"
|
||||
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"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
"github.com/cosmos/cosmos-sdk/x/simplestake"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/x/sketchy"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "DemocoinApp"
|
||||
)
|
||||
|
||||
// Extended ABCI application
|
||||
type DemocoinApp struct {
|
||||
*bam.BaseApp
|
||||
cdc *wire.Codec
|
||||
|
||||
// keys to access the substores
|
||||
capKeyMainStore *sdk.KVStoreKey
|
||||
capKeyAccountStore *sdk.KVStoreKey
|
||||
capKeyIBCStore *sdk.KVStoreKey
|
||||
capKeyStakingStore *sdk.KVStoreKey
|
||||
|
||||
// Manage getting and setting accounts
|
||||
accountMapper sdk.AccountMapper
|
||||
}
|
||||
|
||||
func NewDemocoinApp(logger log.Logger, dbs map[string]dbm.DB) *DemocoinApp {
|
||||
// create your application object
|
||||
var app = &DemocoinApp{
|
||||
BaseApp: bam.NewBaseApp(appName, logger, dbs["main"]),
|
||||
cdc: MakeCodec(),
|
||||
capKeyMainStore: sdk.NewKVStoreKey("main"),
|
||||
capKeyAccountStore: sdk.NewKVStoreKey("acc"),
|
||||
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
|
||||
capKeyStakingStore: sdk.NewKVStoreKey("stake"),
|
||||
}
|
||||
|
||||
// define the accountMapper
|
||||
app.accountMapper = auth.NewAccountMapperSealed(
|
||||
app.capKeyMainStore, // target store
|
||||
&types.AppAccount{}, // prototype
|
||||
)
|
||||
|
||||
// add handlers
|
||||
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
|
||||
coolKeeper := cool.NewKeeper(app.capKeyMainStore, coinKeeper)
|
||||
ibcMapper := ibc.NewIBCMapper(app.cdc, app.capKeyIBCStore)
|
||||
stakeKeeper := simplestake.NewKeeper(app.capKeyStakingStore, coinKeeper)
|
||||
app.Router().
|
||||
AddRoute("bank", bank.NewHandler(coinKeeper)).
|
||||
AddRoute("cool", cool.NewHandler(coolKeeper)).
|
||||
AddRoute("sketchy", sketchy.NewHandler()).
|
||||
AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper)).
|
||||
AddRoute("simplestake", simplestake.NewHandler(stakeKeeper))
|
||||
|
||||
// initialize BaseApp
|
||||
app.SetTxDecoder(app.txDecoder)
|
||||
app.SetInitChainer(app.initChainerFn(coolKeeper))
|
||||
app.MountStoreWithDB(app.capKeyMainStore, sdk.StoreTypeIAVL, dbs["main"])
|
||||
app.MountStoreWithDB(app.capKeyAccountStore, sdk.StoreTypeIAVL, dbs["acc"])
|
||||
app.MountStoreWithDB(app.capKeyIBCStore, sdk.StoreTypeIAVL, dbs["ibc"])
|
||||
app.MountStoreWithDB(app.capKeyStakingStore, sdk.StoreTypeIAVL, dbs["staking"])
|
||||
// NOTE: Broken until #532 lands
|
||||
//app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore, app.capKeyStakingStore)
|
||||
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper))
|
||||
err := app.LoadLatestVersion(app.capKeyMainStore)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
// custom tx codec
|
||||
// TODO: use new go-wire
|
||||
func MakeCodec() *wire.Codec {
|
||||
const msgTypeSend = 0x1
|
||||
const msgTypeIssue = 0x2
|
||||
const msgTypeQuiz = 0x3
|
||||
const msgTypeSetTrend = 0x4
|
||||
const msgTypeIBCTransferMsg = 0x5
|
||||
const msgTypeIBCReceiveMsg = 0x6
|
||||
const msgTypeBondMsg = 0x7
|
||||
const msgTypeUnbondMsg = 0x8
|
||||
var _ = oldwire.RegisterInterface(
|
||||
struct{ sdk.Msg }{},
|
||||
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
|
||||
oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue},
|
||||
oldwire.ConcreteType{cool.QuizMsg{}, msgTypeQuiz},
|
||||
oldwire.ConcreteType{cool.SetTrendMsg{}, msgTypeSetTrend},
|
||||
oldwire.ConcreteType{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg},
|
||||
oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
|
||||
oldwire.ConcreteType{simplestake.BondMsg{}, msgTypeBondMsg},
|
||||
oldwire.ConcreteType{simplestake.UnbondMsg{}, msgTypeUnbondMsg},
|
||||
)
|
||||
|
||||
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.
|
||||
// ibc.RegisterWire(cdc) // Register ibc.[IBCTransferMsg, IBCReceiveMsg] types.
|
||||
return cdc
|
||||
}
|
||||
|
||||
// custom logic for transaction decoding
|
||||
func (app *DemocoinApp) txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||
var tx = sdk.StdTx{}
|
||||
|
||||
if len(txBytes) == 0 {
|
||||
return nil, sdk.ErrTxDecode("txBytes are empty")
|
||||
}
|
||||
|
||||
// StdTx.Msg is an interface. The concrete types
|
||||
// are registered by MakeTxCodec in bank.RegisterWire.
|
||||
err := app.cdc.UnmarshalBinary(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrTxDecode("").TraceCause(err, "")
|
||||
}
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// custom logic for democoin initialization
|
||||
func (app *DemocoinApp) initChainerFn(coolKeeper cool.Keeper) sdk.InitChainer {
|
||||
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
stateJSON := req.AppStateBytes
|
||||
|
||||
genesisState := new(types.GenesisState)
|
||||
err := json.Unmarshal(stateJSON, genesisState)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
for _, gacc := range genesisState.Accounts {
|
||||
acc, err := gacc.ToAppAccount()
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
app.accountMapper.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
// Application specific genesis handling
|
||||
err = coolKeeper.InitGenesis(ctx, stateJSON)
|
||||
if err != nil {
|
||||
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
|
||||
// return sdk.ErrGenesisParse("").TraceCause(err, "")
|
||||
}
|
||||
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,382 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
// Construct some global addrs and txs for tests.
|
||||
var (
|
||||
chainID = "" // TODO
|
||||
|
||||
priv1 = crypto.GenPrivKeyEd25519()
|
||||
addr1 = priv1.PubKey().Address()
|
||||
addr2 = crypto.GenPrivKeyEd25519().PubKey().Address()
|
||||
coins = sdk.Coins{{"foocoin", 10}}
|
||||
fee = sdk.StdFee{
|
||||
sdk.Coins{{"foocoin", 0}},
|
||||
0,
|
||||
}
|
||||
|
||||
sendMsg = bank.SendMsg{
|
||||
Inputs: []bank.Input{bank.NewInput(addr1, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(addr2, coins)},
|
||||
}
|
||||
|
||||
quizMsg1 = cool.QuizMsg{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "icecold",
|
||||
}
|
||||
|
||||
quizMsg2 = cool.QuizMsg{
|
||||
Sender: addr1,
|
||||
CoolAnswer: "badvibesonly",
|
||||
}
|
||||
|
||||
setTrendMsg1 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "icecold",
|
||||
}
|
||||
|
||||
setTrendMsg2 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "badvibesonly",
|
||||
}
|
||||
|
||||
setTrendMsg3 = cool.SetTrendMsg{
|
||||
Sender: addr1,
|
||||
Cool: "warmandkind",
|
||||
}
|
||||
)
|
||||
|
||||
func loggerAndDBs() (log.Logger, map[string]dbm.DB) {
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
|
||||
dbs := map[string]dbm.DB{
|
||||
"main": dbm.NewMemDB(),
|
||||
"acc": dbm.NewMemDB(),
|
||||
"ibc": dbm.NewMemDB(),
|
||||
"staking": dbm.NewMemDB(),
|
||||
}
|
||||
return logger, dbs
|
||||
}
|
||||
|
||||
func newDemocoinApp() *DemocoinApp {
|
||||
logger, dbs := loggerAndDBs()
|
||||
return NewDemocoinApp(logger, dbs)
|
||||
}
|
||||
|
||||
//_______________________________________________________________________
|
||||
|
||||
func TestMsgs(t *testing.T) {
|
||||
bapp := newDemocoinApp()
|
||||
|
||||
msgs := []struct {
|
||||
msg sdk.Msg
|
||||
}{
|
||||
{sendMsg},
|
||||
{quizMsg1},
|
||||
{setTrendMsg1},
|
||||
}
|
||||
|
||||
sequences := []int64{0}
|
||||
for i, m := range msgs {
|
||||
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, m.msg))
|
||||
tx := sdk.NewStdTx(m.msg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: sig,
|
||||
}})
|
||||
|
||||
// just marshal/unmarshal!
|
||||
cdc := MakeCodec()
|
||||
txBytes, err := cdc.MarshalBinary(tx)
|
||||
require.NoError(t, err, "i: %v", i)
|
||||
|
||||
// Run a Check
|
||||
cres := bapp.CheckTx(txBytes)
|
||||
assert.Equal(t, sdk.CodeUnknownAddress,
|
||||
sdk.CodeType(cres.Code), "i: %v, log: %v", i, cres.Log)
|
||||
|
||||
// Simulate a Block
|
||||
bapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
dres := bapp.DeliverTx(txBytes)
|
||||
assert.Equal(t, sdk.CodeUnknownAddress,
|
||||
sdk.CodeType(dres.Code), "i: %v, log: %v", i, dres.Log)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesis(t *testing.T) {
|
||||
logger, dbs := loggerAndDBs()
|
||||
bapp := NewDemocoinApp(logger, dbs)
|
||||
|
||||
// Construct some genesis bytes to reflect democoin/types/AppAccount
|
||||
pk := crypto.GenPrivKeyEd25519().PubKey()
|
||||
addr := pk.Address()
|
||||
coins, err := sdk.ParseCoins("77foocoin,99barcoin")
|
||||
require.Nil(t, err)
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr,
|
||||
Coins: coins,
|
||||
}
|
||||
acc := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
genesisState := map[string]interface{}{
|
||||
"accounts": []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc),
|
||||
},
|
||||
"cool": map[string]string{
|
||||
"trend": "ice-cold",
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
// A checkTx context
|
||||
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
|
||||
assert.Equal(t, acc, res1)
|
||||
|
||||
// reload app and ensure the account is still there
|
||||
bapp = NewDemocoinApp(logger, dbs)
|
||||
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
|
||||
assert.Equal(t, acc, res1)
|
||||
}
|
||||
|
||||
func TestSendMsgWithAccounts(t *testing.T) {
|
||||
bapp := newDemocoinApp()
|
||||
|
||||
// Construct some genesis bytes to reflect democoin/types/AppAccount
|
||||
// Give 77 foocoin to the first key
|
||||
coins, err := sdk.ParseCoins("77foocoin")
|
||||
require.Nil(t, err)
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: coins,
|
||||
}
|
||||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
// Construct genesis state
|
||||
genesisState := map[string]interface{}{
|
||||
"accounts": []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
"cool": map[string]string{
|
||||
"trend": "ice-cold",
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
require.Nil(t, err)
|
||||
|
||||
// Initialize the chain
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
// A checkTx context (true)
|
||||
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
assert.Equal(t, acc1, res1)
|
||||
|
||||
// Sign the tx
|
||||
sequences := []int64{0}
|
||||
sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, sendMsg))
|
||||
tx := sdk.NewStdTx(sendMsg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: sig,
|
||||
}})
|
||||
|
||||
// Run a Check
|
||||
res := bapp.Check(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
|
||||
// Simulate a Block
|
||||
bapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
|
||||
// Check balances
|
||||
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
|
||||
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
|
||||
res3 := bapp.accountMapper.GetAccount(ctxDeliver, addr2)
|
||||
assert.Equal(t, fmt.Sprintf("%v", res2.GetCoins()), "67foocoin")
|
||||
assert.Equal(t, fmt.Sprintf("%v", res3.GetCoins()), "10foocoin")
|
||||
|
||||
// Delivering again should cause replay error
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeInvalidSequence, res.Code, res.Log)
|
||||
|
||||
// bumping the txnonce number without resigning should be an auth error
|
||||
tx.Signatures[0].Sequence = 1
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeUnauthorized, res.Code, res.Log)
|
||||
|
||||
// resigning the tx with the bumped sequence should work
|
||||
sequences = []int64{1}
|
||||
sig = priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, tx.Msg))
|
||||
tx.Signatures[0].Signature = sig
|
||||
res = bapp.Deliver(tx)
|
||||
assert.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
}
|
||||
|
||||
func TestQuizMsg(t *testing.T) {
|
||||
bapp := newDemocoinApp()
|
||||
|
||||
// Construct genesis state
|
||||
// Construct some genesis bytes to reflect democoin/types/AppAccount
|
||||
coins := sdk.Coins{}
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: coins,
|
||||
}
|
||||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
|
||||
// Construct genesis state
|
||||
genesisState := map[string]interface{}{
|
||||
"accounts": []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
"cool": map[string]string{
|
||||
"trend": "ice-cold",
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
require.Nil(t, err)
|
||||
|
||||
// Initialize the chain (nil)
|
||||
vals := []abci.Validator{}
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
// A checkTx context (true)
|
||||
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
assert.Equal(t, acc1, res1)
|
||||
|
||||
// Set the trend, submit a really cool quiz and check for reward
|
||||
SignCheckDeliver(t, bapp, setTrendMsg1, 0, true)
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 1, true)
|
||||
CheckBalance(t, bapp, "69icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg2, 2, false) // result without reward
|
||||
CheckBalance(t, bapp, "69icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 3, true)
|
||||
CheckBalance(t, bapp, "138icecold")
|
||||
SignCheckDeliver(t, bapp, setTrendMsg2, 4, true) // reset the trend
|
||||
SignCheckDeliver(t, bapp, quizMsg1, 5, false) // the same answer will nolonger do!
|
||||
CheckBalance(t, bapp, "138icecold")
|
||||
SignCheckDeliver(t, bapp, quizMsg2, 6, true) // earlier answer now relavent again
|
||||
CheckBalance(t, bapp, "69badvibesonly,138icecold")
|
||||
SignCheckDeliver(t, bapp, setTrendMsg3, 7, false) // expect to fail to set the trend to something which is not cool
|
||||
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
bapp := newDemocoinApp()
|
||||
|
||||
sourceChain := "source-chain"
|
||||
destChain := "dest-chain"
|
||||
|
||||
vals := []abci.Validator{}
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: addr1,
|
||||
Coins: coins,
|
||||
}
|
||||
acc1 := &types.AppAccount{baseAcc, "foobart"}
|
||||
genesisState := map[string]interface{}{
|
||||
"accounts": []*types.GenesisAccount{
|
||||
types.NewGenesisAccount(acc1),
|
||||
},
|
||||
"cool": map[string]string{
|
||||
"trend": "ice-cold",
|
||||
},
|
||||
}
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", "\t")
|
||||
require.Nil(t, err)
|
||||
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
|
||||
bapp.Commit()
|
||||
|
||||
// A checkTx context (true)
|
||||
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})
|
||||
res1 := bapp.accountMapper.GetAccount(ctxCheck, addr1)
|
||||
assert.Equal(t, acc1, res1)
|
||||
|
||||
packet := ibc.IBCPacket{
|
||||
SrcAddr: addr1,
|
||||
DestAddr: addr1,
|
||||
Coins: coins,
|
||||
SrcChain: sourceChain,
|
||||
DestChain: destChain,
|
||||
}
|
||||
|
||||
transferMsg := ibc.IBCTransferMsg{
|
||||
IBCPacket: packet,
|
||||
}
|
||||
|
||||
receiveMsg := ibc.IBCReceiveMsg{
|
||||
IBCPacket: packet,
|
||||
Relayer: addr1,
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
SignCheckDeliver(t, bapp, transferMsg, 0, true)
|
||||
CheckBalance(t, bapp, "")
|
||||
SignCheckDeliver(t, bapp, transferMsg, 1, false)
|
||||
SignCheckDeliver(t, bapp, receiveMsg, 2, true)
|
||||
CheckBalance(t, bapp, "10foocoin")
|
||||
SignCheckDeliver(t, bapp, receiveMsg, 3, false)
|
||||
}
|
||||
|
||||
// TODO describe the use of this function
|
||||
func SignCheckDeliver(t *testing.T, bapp *DemocoinApp, msg sdk.Msg, seq int64, expPass bool) {
|
||||
|
||||
// Sign the tx
|
||||
tx := sdk.NewStdTx(msg, fee, []sdk.StdSignature{{
|
||||
PubKey: priv1.PubKey(),
|
||||
Signature: priv1.Sign(sdk.StdSignBytes(chainID, []int64{seq}, fee, msg)),
|
||||
Sequence: seq,
|
||||
}})
|
||||
|
||||
// Run a Check
|
||||
res := bapp.Check(tx)
|
||||
if expPass {
|
||||
require.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
} else {
|
||||
require.NotEqual(t, sdk.CodeOK, res.Code, res.Log)
|
||||
}
|
||||
|
||||
// Simulate a Block
|
||||
bapp.BeginBlock(abci.RequestBeginBlock{})
|
||||
res = bapp.Deliver(tx)
|
||||
if expPass {
|
||||
require.Equal(t, sdk.CodeOK, res.Code, res.Log)
|
||||
} else {
|
||||
require.NotEqual(t, sdk.CodeOK, res.Code, res.Log)
|
||||
}
|
||||
bapp.EndBlock(abci.RequestEndBlock{})
|
||||
//bapp.Commit()
|
||||
}
|
||||
|
||||
func CheckBalance(t *testing.T, bapp *DemocoinApp, balExpected string) {
|
||||
ctxDeliver := bapp.BaseApp.NewContext(false, abci.Header{})
|
||||
res2 := bapp.accountMapper.GetAccount(ctxDeliver, addr1)
|
||||
assert.Equal(t, balExpected, fmt.Sprintf("%v", res2.GetCoins()))
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
|
||||
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
|
||||
ibccmd "github.com/cosmos/cosmos-sdk/x/ibc/commands"
|
||||
simplestakingcmd "github.com/cosmos/cosmos-sdk/x/simplestake/commands"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/types"
|
||||
)
|
||||
|
||||
// gaiacliCmd is the entry point for this binary
|
||||
var (
|
||||
democliCmd = &cobra.Command{
|
||||
Use: "democli",
|
||||
Short: "Democoin light-client",
|
||||
}
|
||||
)
|
||||
|
||||
func todoNotImplemented(_ *cobra.Command, _ []string) error {
|
||||
return errors.New("TODO: Command not yet implemented")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// disable sorting
|
||||
cobra.EnableCommandSorting = false
|
||||
|
||||
// get the codec
|
||||
cdc := app.MakeCodec()
|
||||
|
||||
// TODO: setup keybase, viper object, etc. to be passed into
|
||||
// the below functions and eliminate global vars, like we do
|
||||
// with the cdc
|
||||
|
||||
// add standard rpc, and tx commands
|
||||
rpc.AddCommands(democliCmd)
|
||||
democliCmd.AddCommand(client.LineBreak)
|
||||
tx.AddCommands(democliCmd, cdc)
|
||||
democliCmd.AddCommand(client.LineBreak)
|
||||
|
||||
// add query/post commands (custom to binary)
|
||||
democliCmd.AddCommand(
|
||||
client.GetCommands(
|
||||
authcmd.GetAccountCmd("main", cdc, types.GetAccountDecoder(cdc)),
|
||||
)...)
|
||||
democliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
bankcmd.SendTxCmd(cdc),
|
||||
)...)
|
||||
democliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCTransferCmd(cdc),
|
||||
)...)
|
||||
democliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
ibccmd.IBCRelayCmd(cdc),
|
||||
simplestakingcmd.BondTxCmd(cdc),
|
||||
)...)
|
||||
democliCmd.AddCommand(
|
||||
client.PostCommands(
|
||||
simplestakingcmd.UnbondTxCmd(cdc),
|
||||
)...)
|
||||
|
||||
// add proxy, version and key info
|
||||
democliCmd.AddCommand(
|
||||
client.LineBreak,
|
||||
lcd.ServeCommand(cdc),
|
||||
keys.Commands(),
|
||||
client.LineBreak,
|
||||
version.VersionCmd,
|
||||
)
|
||||
|
||||
// prepare and add flags
|
||||
executor := cli.PrepareMainCmd(democliCmd, "BC", os.ExpandEnv("$HOME/.democli"))
|
||||
executor.Execute()
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/app"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
)
|
||||
|
||||
// democoindCmd is the entry point for this binary
|
||||
var (
|
||||
democoindCmd = &cobra.Command{
|
||||
Use: "democoind",
|
||||
Short: "Gaia Daemon (server)",
|
||||
}
|
||||
)
|
||||
|
||||
// defaultOptions sets up the app_options for the
|
||||
// default genesis file
|
||||
func defaultOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
|
||||
addr, secret, err := server.GenerateCoinKey()
|
||||
if err != nil {
|
||||
return nil, "", 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, nil
|
||||
}
|
||||
|
||||
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
|
||||
dbMain, err := dbm.NewGoLevelDB("democoin", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbAcc, err := dbm.NewGoLevelDB("democoin-acc", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbIBC, err := dbm.NewGoLevelDB("democoin-ibc", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbStaking, err := dbm.NewGoLevelDB("democoin-staking", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbs := map[string]dbm.DB{
|
||||
"main": dbMain,
|
||||
"acc": dbAcc,
|
||||
"ibc": dbIBC,
|
||||
"staking": dbStaking,
|
||||
}
|
||||
bapp := app.NewDemocoinApp(logger, dbs)
|
||||
return bapp, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// TODO: set logger through CLI
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
|
||||
With("module", "main")
|
||||
|
||||
democoindCmd.AddCommand(
|
||||
server.InitCmd(defaultOptions, logger),
|
||||
server.StartCmd(generateApp, logger),
|
||||
server.UnsafeResetAllCmd(logger),
|
||||
server.ShowNodeIdCmd(logger),
|
||||
server.ShowValidatorCmd(logger),
|
||||
version.VersionCmd,
|
||||
)
|
||||
|
||||
// prepare and add flags
|
||||
rootDir := os.ExpandEnv("$HOME/.democoind")
|
||||
executor := cli.PrepareBaseCmd(democoindCmd, "BC", rootDir)
|
||||
executor.Execute()
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
var _ sdk.Account = (*AppAccount)(nil)
|
||||
|
||||
// Custom extensions for this application. This is just an example of
|
||||
// extending auth.BaseAccount with custom fields.
|
||||
//
|
||||
// This is compatible with the stock auth.AccountStore, since
|
||||
// auth.AccountStore uses the flexible go-wire library.
|
||||
type AppAccount struct {
|
||||
auth.BaseAccount
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (acc AppAccount) GetName() string { return acc.Name }
|
||||
func (acc *AppAccount) SetName(name string) { acc.Name = name }
|
||||
|
||||
// Get the AccountDecoder function for the custom AppAccount
|
||||
func GetAccountDecoder(cdc *wire.Codec) sdk.AccountDecoder {
|
||||
return func(accBytes []byte) (res sdk.Account, err error) {
|
||||
if len(accBytes) == 0 {
|
||||
return nil, sdk.ErrTxDecode("accBytes are empty")
|
||||
}
|
||||
acct := new(AppAccount)
|
||||
err = cdc.UnmarshalBinary(accBytes, &acct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return acct, err
|
||||
}
|
||||
}
|
||||
|
||||
//___________________________________________________________________________________
|
||||
|
||||
// State to Unmarshal
|
||||
type GenesisState struct {
|
||||
Accounts []*GenesisAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
// GenesisAccount doesn't need pubkey or sequence
|
||||
type GenesisAccount struct {
|
||||
Name string `json:"name"`
|
||||
Address sdk.Address `json:"address"`
|
||||
Coins sdk.Coins `json:"coins"`
|
||||
}
|
||||
|
||||
func NewGenesisAccount(aa *AppAccount) *GenesisAccount {
|
||||
return &GenesisAccount{
|
||||
Name: aa.Name,
|
||||
Address: aa.Address,
|
||||
Coins: aa.Coins,
|
||||
}
|
||||
}
|
||||
|
||||
// convert GenesisAccount to AppAccount
|
||||
func (ga *GenesisAccount) ToAppAccount() (acc *AppAccount, err error) {
|
||||
baseAcc := auth.BaseAccount{
|
||||
Address: ga.Address,
|
||||
Coins: ga.Coins,
|
||||
}
|
||||
return &AppAccount{
|
||||
BaseAccount: baseAcc,
|
||||
Name: ga.Name,
|
||||
}, nil
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/client/builder"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
|
||||
"github.com/cosmos/cosmos-sdk/examples/democoin/x/cool"
|
||||
)
|
||||
|
||||
// take the coolness quiz transaction
|
||||
|
@ -36,16 +36,8 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command {
|
|||
// get account name
|
||||
name := viper.GetString(client.FlagName)
|
||||
|
||||
// get password
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc)
|
||||
res, err := builder.SignBuildBroadcast(name, msg, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,19 +67,11 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command {
|
|||
// get account name
|
||||
name := viper.GetString(client.FlagName)
|
||||
|
||||
// get password
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create the message
|
||||
msg := cool.NewSetTrendMsg(from, args[0])
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc)
|
||||
res, err := builder.SignBuildBroadcast(name, msg, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Cool module reserves error 400-499 lawl
|
||||
CodeIncorrectCoolAnswer sdk.CodeType = 400
|
||||
)
|
||||
|
||||
// ErrIncorrectCoolAnswer - Error returned upon an incorrect guess
|
||||
func ErrIncorrectCoolAnswer(answer string) sdk.Error {
|
||||
return sdk.NewError(CodeIncorrectCoolAnswer, fmt.Sprintf("Incorrect cool answer: %v", answer))
|
||||
}
|
|
@ -5,7 +5,6 @@ import (
|
|||
"reflect"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// This is just an example to demonstrate a functional custom module
|
||||
|
@ -18,14 +17,14 @@ import (
|
|||
//| $$$$$$$| $$$$$$/| $$$$$$/| $$$$$$$
|
||||
// \_______/ \______/ \______/ |______/
|
||||
|
||||
// Handle all "coolmodule" type objects
|
||||
func NewHandler(ck bank.CoinKeeper, cm Mapper) sdk.Handler {
|
||||
// NewHandler returns a handler for "cool" type messages.
|
||||
func NewHandler(k Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case SetTrendMsg:
|
||||
return handleSetTrendMsg(ctx, cm, msg)
|
||||
return handleSetTrendMsg(ctx, k, msg)
|
||||
case QuizMsg:
|
||||
return handleQuizMsg(ctx, ck, cm, msg)
|
||||
return handleQuizMsg(ctx, k, msg)
|
||||
default:
|
||||
errMsg := fmt.Sprintf("Unrecognized cool Msg type: %v", reflect.TypeOf(msg).Name())
|
||||
return sdk.ErrUnknownRequest(errMsg).Result()
|
||||
|
@ -34,22 +33,29 @@ func NewHandler(ck bank.CoinKeeper, cm Mapper) sdk.Handler {
|
|||
}
|
||||
|
||||
// Handle QuizMsg This is the engine of your module
|
||||
func handleSetTrendMsg(ctx sdk.Context, cm Mapper, msg SetTrendMsg) sdk.Result {
|
||||
cm.SetTrend(ctx, msg.Cool)
|
||||
func handleSetTrendMsg(ctx sdk.Context, k Keeper, msg SetTrendMsg) sdk.Result {
|
||||
k.setTrend(ctx, msg.Cool)
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
// Handle QuizMsg This is the engine of your module
|
||||
func handleQuizMsg(ctx sdk.Context, ck bank.CoinKeeper, cm Mapper, msg QuizMsg) sdk.Result {
|
||||
func handleQuizMsg(ctx sdk.Context, k Keeper, msg QuizMsg) sdk.Result {
|
||||
|
||||
currentTrend := cm.GetTrend(ctx)
|
||||
correct := k.CheckTrend(ctx, msg.CoolAnswer)
|
||||
|
||||
if msg.CoolAnswer == currentTrend {
|
||||
bonusCoins := sdk.Coins{{currentTrend, 69}}
|
||||
_, err := ck.AddCoins(ctx, msg.Sender, bonusCoins)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
if !correct {
|
||||
return ErrIncorrectCoolAnswer(msg.CoolAnswer).Result()
|
||||
}
|
||||
|
||||
if ctx.IsCheckTx() {
|
||||
return sdk.Result{} // TODO
|
||||
}
|
||||
|
||||
bonusCoins := sdk.Coins{{msg.CoolAnswer, 69}}
|
||||
|
||||
_, err := k.ck.AddCoins(ctx, msg.Sender, bonusCoins)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
|
@ -0,0 +1,59 @@
|
|||
package cool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
// Cool genesis state, containing the genesis trend
|
||||
type GenesisState struct {
|
||||
trend string
|
||||
}
|
||||
|
||||
// Keeper - handlers sets/gets of custom variables for your module
|
||||
type Keeper struct {
|
||||
ck bank.CoinKeeper
|
||||
|
||||
storeKey sdk.StoreKey // The (unexposed) key used to access the store from the Context.
|
||||
}
|
||||
|
||||
// NewKeeper - Returns the Keeper
|
||||
func NewKeeper(key sdk.StoreKey, bankKeeper bank.CoinKeeper) Keeper {
|
||||
return Keeper{bankKeeper, key}
|
||||
}
|
||||
|
||||
// Key to knowing the trend on the streets!
|
||||
var trendKey = []byte("TrendKey")
|
||||
|
||||
// GetTrend - returns the current cool trend
|
||||
func (k Keeper) GetTrend(ctx sdk.Context) string {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(trendKey)
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// Implements sdk.AccountMapper.
|
||||
func (k Keeper) setTrend(ctx sdk.Context, newTrend string) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store.Set(trendKey, []byte(newTrend))
|
||||
}
|
||||
|
||||
// CheckTrend - Returns true or false based on whether guessedTrend is currently cool or not
|
||||
func (k Keeper) CheckTrend(ctx sdk.Context, guessedTrend string) bool {
|
||||
if guessedTrend == k.GetTrend(ctx) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InitGenesis - store the genesis trend
|
||||
func (k Keeper) InitGenesis(ctx sdk.Context, data json.RawMessage) error {
|
||||
var state GenesisState
|
||||
if err := json.Unmarshal(data, &state); err != nil {
|
||||
return err
|
||||
}
|
||||
k.setTrend(ctx, state.trend)
|
||||
return nil
|
||||
}
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
"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, string, cmn.HexBytes, error) {
|
||||
addr, secret, err := server.GenerateCoinKey()
|
||||
if err != nil {
|
||||
return nil, "", 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), secret, addr, 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()
|
||||
}
|
|
@ -3,8 +3,12 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/tendermint/abci/server"
|
||||
"github.com/tendermint/tmlibs/cli"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
@ -17,7 +21,8 @@ func main() {
|
|||
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
|
||||
|
||||
db, err := dbm.NewGoLevelDB("basecoind", "data")
|
||||
rootDir := viper.GetString(cli.HomeFlag)
|
||||
db, err := dbm.NewGoLevelDB("basecoind", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
|
@ -30,7 +35,7 @@ func main() {
|
|||
var baseApp = bam.NewBaseApp("kvstore", logger, db)
|
||||
|
||||
// Set mounts for BaseApp's MultiStore.
|
||||
baseApp.MountStore(capKeyMainStore, sdk.StoreTypeIAVL)
|
||||
baseApp.MountStoresIAVL(capKeyMainStore)
|
||||
|
||||
// Set Tx decoder
|
||||
baseApp.SetTxDecoder(decodeTx)
|
||||
|
|
|
@ -3,8 +3,10 @@ package mock
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
abci "github.com/tendermint/abci/types"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
|
@ -17,7 +19,7 @@ import (
|
|||
// Make sure rootDir is empty before running the test,
|
||||
// in order to guarantee consistent results
|
||||
func NewApp(rootDir string, logger log.Logger) (abci.Application, error) {
|
||||
db, err := dbm.NewGoLevelDB("mock", rootDir)
|
||||
db, err := dbm.NewGoLevelDB("mock", filepath.Join(rootDir, "data"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -105,7 +107,7 @@ func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci
|
|||
// GenInitOptions can be passed into InitCmd,
|
||||
// returns a static string of a few key-values that can be parsed
|
||||
// by InitChainer
|
||||
func GenInitOptions(args []string) (json.RawMessage, error) {
|
||||
func GenInitOptions(args []string) (json.RawMessage, string, cmn.HexBytes, error) {
|
||||
opts := []byte(`{
|
||||
"values": [
|
||||
{
|
||||
|
@ -118,5 +120,5 @@ func GenInitOptions(args []string) (json.RawMessage, error) {
|
|||
}
|
||||
]
|
||||
}`)
|
||||
return opts, nil
|
||||
return opts, "", nil, nil
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestInitApp(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// initialize it future-way
|
||||
opts, err := GenInitOptions(nil)
|
||||
opts, _, _, err := GenInitOptions(nil)
|
||||
require.NoError(t, err)
|
||||
req := abci.RequestInitChain{AppStateBytes: opts}
|
||||
app.InitChain(req)
|
||||
|
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -11,9 +12,17 @@ import (
|
|||
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
type testnetInformation struct {
|
||||
Secret string `json:"secret"`
|
||||
Account string `json:"account"`
|
||||
Validator tmtypes.GenesisValidator `json:"validator"`
|
||||
NodeID p2p.ID `json:"node_id"`
|
||||
}
|
||||
|
||||
// InitCmd will initialize all files for tendermint,
|
||||
// along with proper app_state.
|
||||
// The application can pass in a function to generate
|
||||
|
@ -24,17 +33,19 @@ func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command {
|
|||
genAppState: gen,
|
||||
logger: logger,
|
||||
}
|
||||
return &cobra.Command{
|
||||
cobraCmd := cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize genesis files",
|
||||
RunE: cmd.run,
|
||||
}
|
||||
return &cobraCmd
|
||||
}
|
||||
|
||||
// GenAppState can parse command-line and flag to
|
||||
// GenAppState can parse command-line to
|
||||
// generate default app_state for the genesis file.
|
||||
// Also must return generated seed and address
|
||||
// This is application-specific
|
||||
type GenAppState func(args []string) (json.RawMessage, error)
|
||||
type GenAppState func(args []string) (json.RawMessage, string, cmn.HexBytes, error)
|
||||
|
||||
type initCmd struct {
|
||||
genAppState GenAppState
|
||||
|
@ -42,13 +53,16 @@ type initCmd struct {
|
|||
}
|
||||
|
||||
func (c initCmd) run(cmd *cobra.Command, args []string) error {
|
||||
// Store testnet information as we go
|
||||
var testnetInfo testnetInformation
|
||||
|
||||
// Run the basic tendermint initialization,
|
||||
// set up a default genesis with no app_options
|
||||
config, err := tcmd.ParseConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.initTendermintFiles(config)
|
||||
err = c.initTendermintFiles(config, &testnetInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -59,19 +73,36 @@ func (c initCmd) run(cmd *cobra.Command, args []string) error {
|
|||
}
|
||||
|
||||
// Now, we want to add the custom app_state
|
||||
appState, err := c.genAppState(args)
|
||||
appState, secret, address, err := c.genAppState(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
testnetInfo.Secret = secret
|
||||
testnetInfo.Account = address.String()
|
||||
|
||||
// And add them to the genesis file
|
||||
genFile := config.GenesisFile()
|
||||
return addGenesisState(genFile, appState)
|
||||
if err := addGenesisState(genFile, appState); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
testnetInfo.NodeID = nodeKey.ID()
|
||||
out, err := json.MarshalIndent(testnetInfo, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(out))
|
||||
return nil
|
||||
}
|
||||
|
||||
// This was copied from tendermint/cmd/tendermint/commands/init.go
|
||||
// so we could pass in the config and the logger.
|
||||
func (c initCmd) initTendermintFiles(config *cfg.Config) error {
|
||||
func (c initCmd) initTendermintFiles(config *cfg.Config, info *testnetInformation) error {
|
||||
// private validator
|
||||
privValFile := config.PrivValidatorFile()
|
||||
var privValidator *tmtypes.PrivValidatorFS
|
||||
|
@ -102,6 +133,18 @@ func (c initCmd) initTendermintFiles(config *cfg.Config) error {
|
|||
}
|
||||
c.logger.Info("Generated genesis file", "path", genFile)
|
||||
}
|
||||
|
||||
// reload the config file and find our validator info
|
||||
loadedDoc, err := tmtypes.GenesisDocFromFile(genFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, validator := range loadedDoc.Validators {
|
||||
if validator.PubKey == privValidator.GetPubKey() {
|
||||
info.Validator = validator
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/tendermint/go-wire/data"
|
||||
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
// ShowValidator - ported from Tendermint, show this node's validator info
|
||||
func ShowValidatorCmd(logger log.Logger) *cobra.Command {
|
||||
cmd := showValidator{logger}
|
||||
return &cobra.Command{
|
||||
Use: "show_validator",
|
||||
Short: "Show this node's validator info",
|
||||
RunE: cmd.run,
|
||||
}
|
||||
}
|
||||
|
||||
type showValidator struct {
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func (s showValidator) run(cmd *cobra.Command, args []string) error {
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privValidator := types.LoadOrGenPrivValidatorFS(cfg.PrivValidatorFile())
|
||||
pubKeyJSONBytes, err := data.ToJSON(privValidator.PubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(pubKeyJSONBytes))
|
||||
return nil
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -19,6 +19,11 @@ func (coin Coin) String() string {
|
|||
return fmt.Sprintf("%v%v", coin.Amount, coin.Denom)
|
||||
}
|
||||
|
||||
// SameDenomAs returns true if the two coins are the same denom
|
||||
func (coin Coin) SameDenomAs(other Coin) bool {
|
||||
return (coin.Denom == other.Denom)
|
||||
}
|
||||
|
||||
// IsZero returns if this represents no money
|
||||
func (coin Coin) IsZero() bool {
|
||||
return coin.Amount == 0
|
||||
|
@ -27,8 +32,38 @@ func (coin Coin) IsZero() bool {
|
|||
// IsGTE returns true if they are the same type and the receiver is
|
||||
// an equal or greater value
|
||||
func (coin Coin) IsGTE(other Coin) bool {
|
||||
return (coin.Denom == other.Denom) &&
|
||||
(coin.Amount >= other.Amount)
|
||||
return coin.SameDenomAs(other) && (coin.Amount >= other.Amount)
|
||||
}
|
||||
|
||||
// IsEqual returns true if the two sets of Coins have the same value
|
||||
func (coin Coin) IsEqual(other Coin) bool {
|
||||
return coin.SameDenomAs(other) && (coin.Amount == other.Amount)
|
||||
}
|
||||
|
||||
// IsPositive returns true if coin amount is positive
|
||||
func (coin Coin) IsPositive() bool {
|
||||
return (coin.Amount > 0)
|
||||
}
|
||||
|
||||
// IsNotNegative returns true if coin amount is not negative
|
||||
func (coin Coin) IsNotNegative() bool {
|
||||
return (coin.Amount >= 0)
|
||||
}
|
||||
|
||||
// Adds amounts of two coins with same denom
|
||||
func (coin Coin) Plus(coinB Coin) Coin {
|
||||
if !coin.SameDenomAs(coinB) {
|
||||
return coin
|
||||
}
|
||||
return Coin{coin.Denom, coin.Amount + coinB.Amount}
|
||||
}
|
||||
|
||||
// Subtracts amounts of two coins with same denom
|
||||
func (coin Coin) Minus(coinB Coin) Coin {
|
||||
if !coin.SameDenomAs(coinB) {
|
||||
return coin
|
||||
}
|
||||
return Coin{coin.Denom, coin.Amount - coinB.Amount}
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
@ -55,14 +90,14 @@ func (coins Coins) IsValid() bool {
|
|||
case 0:
|
||||
return true
|
||||
case 1:
|
||||
return coins[0].Amount != 0
|
||||
return !coins[0].IsZero()
|
||||
default:
|
||||
lowDenom := coins[0].Denom
|
||||
for _, coin := range coins[1:] {
|
||||
if coin.Denom <= lowDenom {
|
||||
return false
|
||||
}
|
||||
if coin.Amount == 0 {
|
||||
if coin.IsZero() {
|
||||
return false
|
||||
}
|
||||
// we compare each coin against the last denom
|
||||
|
@ -96,10 +131,7 @@ func (coins Coins) Plus(coinsB Coins) Coins {
|
|||
if coinA.Amount+coinB.Amount == 0 {
|
||||
// ignore 0 sum coin type
|
||||
} else {
|
||||
sum = append(sum, Coin{
|
||||
Denom: coinA.Denom,
|
||||
Amount: coinA.Amount + coinB.Amount,
|
||||
})
|
||||
sum = append(sum, coinA.Plus(coinB))
|
||||
}
|
||||
indexA++
|
||||
indexB++
|
||||
|
@ -168,8 +200,8 @@ func (coins Coins) IsPositive() bool {
|
|||
if len(coins) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, coinAmount := range coins {
|
||||
if coinAmount.Amount <= 0 {
|
||||
for _, coin := range coins {
|
||||
if !coin.IsPositive() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -182,8 +214,8 @@ func (coins Coins) IsNotNegative() bool {
|
|||
if len(coins) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, coinAmount := range coins {
|
||||
if coinAmount.Amount < 0 {
|
||||
for _, coin := range coins {
|
||||
if !coin.IsNotNegative() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,144 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsPositiveCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
expected bool
|
||||
}{
|
||||
{Coin{"A", 1}, true},
|
||||
{Coin{"A", 0}, false},
|
||||
{Coin{"a", -1}, false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.IsPositive()
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNotNegativeCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
expected bool
|
||||
}{
|
||||
{Coin{"A", 1}, true},
|
||||
{Coin{"A", 0}, true},
|
||||
{Coin{"a", -1}, false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.IsNotNegative()
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameDenomAsCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
inputTwo Coin
|
||||
expected bool
|
||||
}{
|
||||
{Coin{"A", 1}, Coin{"A", 1}, true},
|
||||
{Coin{"A", 1}, Coin{"a", 1}, false},
|
||||
{Coin{"a", 1}, Coin{"b", 1}, false},
|
||||
{Coin{"steak", 1}, Coin{"steak", 10}, true},
|
||||
{Coin{"steak", -11}, Coin{"steak", 10}, true},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.SameDenomAs(tc.inputTwo)
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGTECoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
inputTwo Coin
|
||||
expected bool
|
||||
}{
|
||||
{Coin{"A", 1}, Coin{"A", 1}, true},
|
||||
{Coin{"A", 2}, Coin{"A", 1}, true},
|
||||
{Coin{"A", -1}, Coin{"A", 5}, false},
|
||||
{Coin{"a", 1}, Coin{"b", 1}, false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.IsGTE(tc.inputTwo)
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEqualCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
inputTwo Coin
|
||||
expected bool
|
||||
}{
|
||||
{Coin{"A", 1}, Coin{"A", 1}, true},
|
||||
{Coin{"A", 1}, Coin{"a", 1}, false},
|
||||
{Coin{"a", 1}, Coin{"b", 1}, false},
|
||||
{Coin{"steak", 1}, Coin{"steak", 10}, false},
|
||||
{Coin{"steak", -11}, Coin{"steak", 10}, false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.IsEqual(tc.inputTwo)
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlusCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
inputTwo Coin
|
||||
expected Coin
|
||||
}{
|
||||
{Coin{"A", 1}, Coin{"A", 1}, Coin{"A", 2}},
|
||||
{Coin{"A", 1}, Coin{"B", 1}, Coin{"A", 1}},
|
||||
{Coin{"asdf", -4}, Coin{"asdf", 5}, Coin{"asdf", 1}},
|
||||
{Coin{"asdf", -1}, Coin{"asdf", 1}, Coin{"asdf", 0}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.Plus(tc.inputTwo)
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinusCoin(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cases := []struct {
|
||||
inputOne Coin
|
||||
inputTwo Coin
|
||||
expected Coin
|
||||
}{
|
||||
{Coin{"A", 1}, Coin{"A", 1}, Coin{"A", 0}},
|
||||
{Coin{"A", 1}, Coin{"B", 1}, Coin{"A", 1}},
|
||||
{Coin{"asdf", -4}, Coin{"asdf", 5}, Coin{"asdf", -9}},
|
||||
{Coin{"asdf", 10}, Coin{"asdf", 1}, Coin{"asdf", 9}},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
res := tc.inputOne.Minus(tc.inputTwo)
|
||||
assert.Equal(tc.expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoins(t *testing.T) {
|
||||
|
||||
//Define the coins to be used in tests
|
||||
|
|
|
@ -21,7 +21,7 @@ func (code CodeType) IsOK() bool {
|
|||
const (
|
||||
CodeOK CodeType = 0
|
||||
CodeInternal CodeType = 1
|
||||
CodeTxDecode CodeType = 2
|
||||
CodeTxDecode CodeType = 2
|
||||
CodeInvalidSequence CodeType = 3
|
||||
CodeUnauthorized CodeType = 4
|
||||
CodeInsufficientFunds CodeType = 5
|
||||
|
@ -32,7 +32,7 @@ const (
|
|||
CodeInsufficientCoins CodeType = 10
|
||||
CodeInvalidCoins CodeType = 11
|
||||
|
||||
CodeGenesisParse CodeType = 0xdead // TODO: remove ?
|
||||
CodeGenesisParse CodeType = 0xdead // TODO: remove ? // why remove?
|
||||
)
|
||||
|
||||
// NOTE: Don't stringer this, we'll put better messages in later.
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Run only once on chain initialization, should write genesis state to store
|
||||
// or throw an error if some required information was not provided, in which case
|
||||
// the application will panic.
|
||||
type InitGenesis func(ctx Context, data json.RawMessage) error
|
|
@ -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))
|
||||
//}
|
|
@ -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)
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -6,10 +6,10 @@ package version
|
|||
// TODO improve
|
||||
|
||||
const Maj = "0"
|
||||
const Min = "12"
|
||||
const Min = "13"
|
||||
const Fix = "0"
|
||||
|
||||
const Version = "0.12.0"
|
||||
const Version = "0.13.0"
|
||||
|
||||
// GitCommit set by build flags
|
||||
var GitCommit = ""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
type commander struct {
|
||||
storeName string
|
||||
cdc *wire.Codec
|
||||
decoder sdk.AccountDecoder
|
||||
decoder sdk.AccountDecoder
|
||||
}
|
||||
|
||||
func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, decoder sdk.AccountDecoder) func(http.ResponseWriter, *http.Request) {
|
||||
|
|
|
@ -62,19 +62,11 @@ func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error {
|
|||
// get account name
|
||||
name := viper.GetString(client.FlagName)
|
||||
|
||||
// get password
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build message
|
||||
msg := BuildMsg(from, to, coins)
|
||||
|
||||
// build and sign the transaction, then broadcast to Tendermint
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, c.Cdc)
|
||||
res, err := builder.SignBuildBroadcast(name, msg, c.Cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Handle all "bank" type messages.
|
||||
// NewHandler returns a handler for "bank" type messages.
|
||||
func NewHandler(ck CoinKeeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
|
@ -25,18 +25,9 @@ func NewHandler(ck CoinKeeper) sdk.Handler {
|
|||
func handleSendMsg(ctx sdk.Context, ck CoinKeeper, msg SendMsg) sdk.Result {
|
||||
// NOTE: totalIn == totalOut should already have been checked
|
||||
|
||||
for _, in := range msg.Inputs {
|
||||
_, err := ck.SubtractCoins(ctx, in.Address, in.Coins)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
}
|
||||
|
||||
for _, out := range msg.Outputs {
|
||||
_, err := ck.AddCoins(ctx, out.Address, out.Coins)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
err := ck.InputOutputCoins(ctx, msg.Inputs, msg.Outputs)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
// TODO: add some tags so we can search it!
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const moduleName = "bank"
|
||||
|
||||
// CoinKeeper manages transfers between accounts
|
||||
type CoinKeeper struct {
|
||||
am sdk.AccountMapper
|
||||
|
@ -16,6 +18,12 @@ func NewCoinKeeper(am sdk.AccountMapper) CoinKeeper {
|
|||
return CoinKeeper{am: am}
|
||||
}
|
||||
|
||||
// GetCoins returns the coins at the addr.
|
||||
func (ck CoinKeeper) GetCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) sdk.Coins {
|
||||
acc := ck.am.GetAccount(ctx, addr)
|
||||
return acc.GetCoins()
|
||||
}
|
||||
|
||||
// SubtractCoins subtracts amt from the coins at the addr.
|
||||
func (ck CoinKeeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) {
|
||||
acc := ck.am.GetAccount(ctx, addr)
|
||||
|
@ -48,3 +56,37 @@ func (ck CoinKeeper) AddCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins)
|
|||
ck.am.SetAccount(ctx, acc)
|
||||
return newCoins, nil
|
||||
}
|
||||
|
||||
// SendCoins moves coins from one account to another
|
||||
func (ck CoinKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.Address, toAddr sdk.Address, amt sdk.Coins) sdk.Error {
|
||||
_, err := ck.SubtractCoins(ctx, fromAddr, amt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ck.AddCoins(ctx, toAddr, amt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InputOutputCoins handles a list of inputs and outputs
|
||||
func (ck CoinKeeper) InputOutputCoins(ctx sdk.Context, inputs []Input, outputs []Output) sdk.Error {
|
||||
for _, in := range inputs {
|
||||
_, err := ck.SubtractCoins(ctx, in.Address, in.Coins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, out := range outputs {
|
||||
_, err := ck.AddCoins(ctx, out.Address, out.Coins)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -53,14 +53,8 @@ func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error
|
|||
|
||||
// get password
|
||||
name := viper.GetString(client.FlagName)
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, c.cdc)
|
||||
res, err := builder.SignBuildBroadcast(name, msg, c.cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ const (
|
|||
type relayCommander struct {
|
||||
cdc *wire.Codec
|
||||
address sdk.Address
|
||||
decoder sdk.AccountDecoder
|
||||
decoder sdk.AccountDecoder
|
||||
mainStore string
|
||||
ibcStore string
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ type relayCommander struct {
|
|||
func IBCRelayCmd(cdc *wire.Codec) *cobra.Command {
|
||||
cmdr := relayCommander{
|
||||
cdc: cdc,
|
||||
decoder: authcmd.GetAccountDecoder(cdc),
|
||||
decoder: authcmd.GetAccountDecoder(cdc),
|
||||
ibcStore: "ibc",
|
||||
mainStore: "main",
|
||||
}
|
||||
|
@ -80,9 +80,7 @@ func (c relayCommander) runIBCRelay(cmd *cobra.Command, args []string) {
|
|||
func (c relayCommander) loop(fromChainID, fromChainNode, toChainID, toChainNode string) {
|
||||
// get password
|
||||
name := viper.GetString(client.FlagName)
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
passphrase, err := builder.GetPassphraseFromStdin(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@ import (
|
|||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool"
|
||||
)
|
||||
|
||||
// AccountMapper(/CoinKeeper) and IBCMapper should use different StoreKey later
|
||||
|
@ -53,8 +51,6 @@ func makeCodec() *wire.Codec {
|
|||
struct{ sdk.Msg }{},
|
||||
oldwire.ConcreteType{bank.SendMsg{}, msgTypeSend},
|
||||
oldwire.ConcreteType{bank.IssueMsg{}, msgTypeIssue},
|
||||
oldwire.ConcreteType{cool.QuizMsg{}, msgTypeQuiz},
|
||||
oldwire.ConcreteType{cool.SetTrendMsg{}, msgTypeSetTrend},
|
||||
oldwire.ConcreteType{IBCTransferMsg{}, msgTypeIBCTransferMsg},
|
||||
oldwire.ConcreteType{IBCReceiveMsg{}, msgTypeIBCReceiveMsg},
|
||||
)
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
keys "github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) {
|
||||
r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandler(cdc, kb)).Methods("POST")
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/tendermint/go-crypto/keys"
|
||||
|
||||
"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/bank/commands"
|
||||
"github.com/cosmos/cosmos-sdk/x/ibc"
|
||||
)
|
||||
|
||||
type transferBody struct {
|
||||
// Fees sdk.Coin `json="fees"`
|
||||
Amount sdk.Coins `json:"amount"`
|
||||
LocalAccountName string `json:"name"`
|
||||
Password string `json:"password"`
|
||||
SrcChainID string `json:"src_chain_id"`
|
||||
Sequence int64 `json:"sequence"`
|
||||
}
|
||||
|
||||
// TransferRequestHandler - http request handler to transfer coins to a address
|
||||
// on a different chain via IBC
|
||||
func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) {
|
||||
c := commands.Commander{cdc}
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// collect data
|
||||
vars := mux.Vars(r)
|
||||
destChainID := vars["destchain"]
|
||||
address := vars["address"]
|
||||
|
||||
var m transferBody
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := kb.Get(m.LocalAccountName)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
bz, err := hex.DecodeString(address)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
to := sdk.Address(bz)
|
||||
|
||||
// build message
|
||||
packet := ibc.NewIBCPacket(info.PubKey.Address(), to, m.Amount, m.SrcChainID, destChainID)
|
||||
msg := ibc.IBCTransferMsg{packet}
|
||||
|
||||
// sign
|
||||
// XXX: OMG
|
||||
viper.Set(client.FlagSequence, m.Sequence)
|
||||
txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// send
|
||||
res, err := builder.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
output, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ import (
|
|||
"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/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/simplestake"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -76,7 +76,7 @@ func (co commander) bondTxCmd(cmd *cobra.Command, args []string) error {
|
|||
var pubKeyEd crypto.PubKeyEd25519
|
||||
copy(pubKeyEd[:], rawPubKey)
|
||||
|
||||
msg := staking.NewBondMsg(from, stake, pubKeyEd.Wrap())
|
||||
msg := simplestake.NewBondMsg(from, stake, pubKeyEd.Wrap())
|
||||
|
||||
return co.sendMsg(msg)
|
||||
}
|
||||
|
@ -87,21 +87,14 @@ func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
msg := staking.NewUnbondMsg(from)
|
||||
msg := simplestake.NewUnbondMsg(from)
|
||||
|
||||
return co.sendMsg(msg)
|
||||
}
|
||||
|
||||
func (co commander) sendMsg(msg sdk.Msg) error {
|
||||
name := viper.GetString(client.FlagName)
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := builder.SignBuildBroadcast(name, passphrase, msg, co.cdc)
|
||||
res, err := builder.SignBuildBroadcast(name, msg, co.cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
|
@ -1,16 +1,21 @@
|
|||
package staking
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Staking errors reserve 300 - 399.
|
||||
CodeEmptyValidator sdk.CodeType = 300
|
||||
CodeInvalidUnbond sdk.CodeType = 301
|
||||
CodeEmptyStake sdk.CodeType = 302
|
||||
// simplestake errors reserve 300 - 399.
|
||||
CodeEmptyValidator sdk.CodeType = 300
|
||||
CodeInvalidUnbond sdk.CodeType = 301
|
||||
CodeEmptyStake sdk.CodeType = 302
|
||||
CodeIncorrectStakingToken sdk.CodeType = 303
|
||||
)
|
||||
|
||||
func ErrIncorrectStakingToken() sdk.Error {
|
||||
return newError(CodeIncorrectStakingToken, "")
|
||||
}
|
||||
|
||||
func ErrEmptyValidator() sdk.Error {
|
||||
return newError(CodeEmptyValidator, "")
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package simplestake
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// NewHandler returns a handler for "simplestake" type messages.
|
||||
func NewHandler(k Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case BondMsg:
|
||||
return handleBondMsg(ctx, k, msg)
|
||||
case UnbondMsg:
|
||||
return handleUnbondMsg(ctx, k, msg)
|
||||
default:
|
||||
return sdk.ErrUnknownRequest("No match for message type.").Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBondMsg(ctx sdk.Context, k Keeper, msg BondMsg) sdk.Result {
|
||||
power, err := k.Bond(ctx, msg.Address, msg.PubKey, msg.Stake)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
valSet := abci.Validator{
|
||||
PubKey: msg.PubKey.Bytes(),
|
||||
Power: power,
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Code: sdk.CodeOK,
|
||||
ValidatorUpdates: abci.Validators{valSet},
|
||||
}
|
||||
}
|
||||
|
||||
func handleUnbondMsg(ctx sdk.Context, k Keeper, msg UnbondMsg) sdk.Result {
|
||||
pubKey, _, err := k.Unbond(ctx, msg.Address)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
valSet := abci.Validator{
|
||||
PubKey: pubKey.Bytes(),
|
||||
Power: int64(0),
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Code: sdk.CodeOK,
|
||||
ValidatorUpdates: abci.Validators{valSet},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package simplestake
|
||||
|
||||
import (
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
const stakingToken = "steak"
|
||||
|
||||
const moduleName = "simplestake"
|
||||
|
||||
type Keeper struct {
|
||||
ck bank.CoinKeeper
|
||||
|
||||
key sdk.StoreKey
|
||||
cdc *wire.Codec
|
||||
}
|
||||
|
||||
func NewKeeper(key sdk.StoreKey, coinKeeper bank.CoinKeeper) Keeper {
|
||||
cdc := wire.NewCodec()
|
||||
return Keeper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
ck: coinKeeper,
|
||||
}
|
||||
}
|
||||
|
||||
func (k Keeper) getBondInfo(ctx sdk.Context, addr sdk.Address) bondInfo {
|
||||
store := ctx.KVStore(k.key)
|
||||
bz := store.Get(addr)
|
||||
if bz == nil {
|
||||
return bondInfo{}
|
||||
}
|
||||
var bi bondInfo
|
||||
err := k.cdc.UnmarshalBinary(bz, &bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bi
|
||||
}
|
||||
|
||||
func (k Keeper) setBondInfo(ctx sdk.Context, addr sdk.Address, bi bondInfo) {
|
||||
store := ctx.KVStore(k.key)
|
||||
bz, err := k.cdc.MarshalBinary(bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
store.Set(addr, bz)
|
||||
}
|
||||
|
||||
func (k Keeper) deleteBondInfo(ctx sdk.Context, addr sdk.Address) {
|
||||
store := ctx.KVStore(k.key)
|
||||
store.Delete(addr)
|
||||
}
|
||||
|
||||
func (k Keeper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) {
|
||||
if stake.Denom != stakingToken {
|
||||
return 0, ErrIncorrectStakingToken()
|
||||
}
|
||||
|
||||
_, err := k.ck.SubtractCoins(ctx, addr, []sdk.Coin{stake})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
bi = bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: 0,
|
||||
}
|
||||
}
|
||||
|
||||
bi.Power = bi.Power + stake.Amount
|
||||
|
||||
k.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
func (k Keeper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
return crypto.PubKey{}, 0, ErrInvalidUnbond()
|
||||
}
|
||||
k.deleteBondInfo(ctx, addr)
|
||||
|
||||
returnedBond := sdk.Coin{stakingToken, bi.Power}
|
||||
|
||||
_, err := k.ck.AddCoins(ctx, addr, []sdk.Coin{returnedBond})
|
||||
if err != nil {
|
||||
return bi.PubKey, bi.Power, err
|
||||
}
|
||||
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
||||
|
||||
// FOR TESTING PURPOSES -------------------------------------------------
|
||||
|
||||
func (k Keeper) bondWithoutCoins(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, stake sdk.Coin) (int64, sdk.Error) {
|
||||
if stake.Denom != stakingToken {
|
||||
return 0, ErrIncorrectStakingToken()
|
||||
}
|
||||
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
bi = bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: 0,
|
||||
}
|
||||
}
|
||||
|
||||
bi.Power = bi.Power + stake.Amount
|
||||
|
||||
k.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
func (k Keeper) unbondWithoutCoins(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := k.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
return crypto.PubKey{}, 0, ErrInvalidUnbond()
|
||||
}
|
||||
k.deleteBondInfo(ctx, addr)
|
||||
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package staking
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -13,25 +13,29 @@ import (
|
|||
|
||||
"github.com/cosmos/cosmos-sdk/store"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey) {
|
||||
func setupMultiStore() (sdk.MultiStore, *sdk.KVStoreKey, *sdk.KVStoreKey) {
|
||||
db := dbm.NewMemDB()
|
||||
authKey := sdk.NewKVStoreKey("authkey")
|
||||
capKey := sdk.NewKVStoreKey("capkey")
|
||||
ms := store.NewCommitMultiStore(db)
|
||||
ms.MountStoreWithDB(capKey, sdk.StoreTypeIAVL, db)
|
||||
ms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db)
|
||||
ms.LoadLatestVersion()
|
||||
return ms, capKey
|
||||
return ms, authKey, capKey
|
||||
}
|
||||
|
||||
func TestStakingMapperGetSet(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
func TestKeeperGetSet(t *testing.T) {
|
||||
ms, _, capKey := setupMultiStore()
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
stakingMapper := NewMapper(capKey)
|
||||
stakeKeeper := NewKeeper(capKey, bank.NewCoinKeeper(nil))
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
|
||||
bi := stakingMapper.getBondInfo(ctx, addr)
|
||||
bi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
assert.Equal(t, bi, bondInfo{})
|
||||
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
|
@ -41,36 +45,39 @@ func TestStakingMapperGetSet(t *testing.T) {
|
|||
Power: int64(10),
|
||||
}
|
||||
fmt.Printf("Pubkey: %v\n", privKey.PubKey())
|
||||
stakingMapper.setBondInfo(ctx, addr, bi)
|
||||
stakeKeeper.setBondInfo(ctx, addr, bi)
|
||||
|
||||
savedBi := stakingMapper.getBondInfo(ctx, addr)
|
||||
savedBi := stakeKeeper.getBondInfo(ctx, addr)
|
||||
assert.NotNil(t, savedBi)
|
||||
fmt.Printf("Bond Info: %v\n", savedBi)
|
||||
assert.Equal(t, int64(10), savedBi.Power)
|
||||
}
|
||||
|
||||
func TestBonding(t *testing.T) {
|
||||
ms, capKey := setupMultiStore()
|
||||
ms, authKey, capKey := setupMultiStore()
|
||||
|
||||
ctx := sdk.NewContext(ms, abci.Header{}, false, nil)
|
||||
stakingMapper := NewMapper(capKey)
|
||||
|
||||
accountMapper := auth.NewAccountMapper(authKey, &auth.BaseAccount{})
|
||||
coinKeeper := bank.NewCoinKeeper(accountMapper)
|
||||
stakeKeeper := NewKeeper(capKey, coinKeeper)
|
||||
addr := sdk.Address([]byte("some-address"))
|
||||
privKey := crypto.GenPrivKeyEd25519()
|
||||
pubKey := privKey.PubKey()
|
||||
|
||||
_, _, err := stakingMapper.Unbond(ctx, addr)
|
||||
_, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
assert.Equal(t, err, ErrInvalidUnbond())
|
||||
|
||||
_, err = stakingMapper.Bond(ctx, addr, pubKey, 10)
|
||||
_, err = stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.Coin{"steak", 10})
|
||||
assert.Nil(t, err)
|
||||
|
||||
power, err := stakingMapper.Bond(ctx, addr, pubKey, 10)
|
||||
power, err := stakeKeeper.bondWithoutCoins(ctx, addr, pubKey, sdk.Coin{"steak", 10})
|
||||
assert.Equal(t, int64(20), power)
|
||||
|
||||
pk, _, err := stakingMapper.Unbond(ctx, addr)
|
||||
pk, _, err := stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, pubKey, pk)
|
||||
|
||||
_, _, err = stakingMapper.Unbond(ctx, addr)
|
||||
_, _, err = stakeKeeper.unbondWithoutCoins(ctx, addr)
|
||||
assert.Equal(t, err, ErrInvalidUnbond())
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package staking
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
@ -26,7 +26,7 @@ func NewBondMsg(addr sdk.Address, stake sdk.Coin, pubKey crypto.PubKey) BondMsg
|
|||
}
|
||||
|
||||
func (msg BondMsg) Type() string {
|
||||
return "staking"
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (msg BondMsg) ValidateBasic() sdk.Error {
|
||||
|
@ -71,7 +71,7 @@ func NewUnbondMsg(addr sdk.Address) UnbondMsg {
|
|||
}
|
||||
|
||||
func (msg UnbondMsg) Type() string {
|
||||
return "staking"
|
||||
return moduleName
|
||||
}
|
||||
|
||||
func (msg UnbondMsg) ValidateBasic() sdk.Error {
|
|
@ -1,4 +1,4 @@
|
|||
package staking
|
||||
package simplestake
|
||||
|
||||
import (
|
||||
"testing"
|
|
@ -0,0 +1,15 @@
|
|||
package simplestake
|
||||
|
||||
import crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
type bondInfo struct {
|
||||
PubKey crypto.PubKey
|
||||
Power int64
|
||||
}
|
||||
|
||||
func (bi bondInfo) isEmpty() bool {
|
||||
if bi == (bondInfo{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
//}
|
|
@ -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, ¶ms)
|
||||
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
|
||||
}
|
|
@ -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...)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
//}
|
||||
//}
|
||||
//}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
|
||||
//}
|
|
@ -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"`
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package stake
|
||||
|
||||
// XXX test global state functions, candidate exchange rate functions etc.
|
|
@ -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)
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package staking
|
||||
|
||||
import (
|
||||
abci "github.com/tendermint/abci/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
)
|
||||
|
||||
func NewHandler(sm StakingMapper, ck bank.CoinKeeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
switch msg := msg.(type) {
|
||||
case BondMsg:
|
||||
return handleBondMsg(ctx, sm, ck, msg)
|
||||
case UnbondMsg:
|
||||
return handleUnbondMsg(ctx, sm, ck, msg)
|
||||
default:
|
||||
return sdk.ErrUnknownRequest("No match for message type.").Result()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBondMsg(ctx sdk.Context, sm StakingMapper, ck bank.CoinKeeper, msg BondMsg) sdk.Result {
|
||||
_, err := ck.SubtractCoins(ctx, msg.Address, []sdk.Coin{msg.Stake})
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
power, err := sm.Bond(ctx, msg.Address, msg.PubKey, msg.Stake.Amount)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
valSet := abci.Validator{
|
||||
PubKey: msg.PubKey.Bytes(),
|
||||
Power: power,
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Code: sdk.CodeOK,
|
||||
ValidatorUpdates: abci.Validators{valSet},
|
||||
}
|
||||
}
|
||||
|
||||
func handleUnbondMsg(ctx sdk.Context, sm StakingMapper, ck bank.CoinKeeper, msg UnbondMsg) sdk.Result {
|
||||
pubKey, power, err := sm.Unbond(ctx, msg.Address)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
stake := sdk.Coin{
|
||||
Denom: "mycoin",
|
||||
Amount: power,
|
||||
}
|
||||
_, err = ck.AddCoins(ctx, msg.Address, sdk.Coins{stake})
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
valSet := abci.Validator{
|
||||
PubKey: pubKey.Bytes(),
|
||||
Power: int64(0),
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Code: sdk.CodeOK,
|
||||
ValidatorUpdates: abci.Validators{valSet},
|
||||
}
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package staking
|
||||
|
||||
import (
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
wire "github.com/cosmos/cosmos-sdk/wire"
|
||||
)
|
||||
|
||||
type StakingMapper struct {
|
||||
key sdk.StoreKey
|
||||
cdc *wire.Codec
|
||||
}
|
||||
|
||||
func NewMapper(key sdk.StoreKey) StakingMapper {
|
||||
cdc := wire.NewCodec()
|
||||
return StakingMapper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
}
|
||||
}
|
||||
|
||||
func (sm StakingMapper) getBondInfo(ctx sdk.Context, addr sdk.Address) bondInfo {
|
||||
store := ctx.KVStore(sm.key)
|
||||
bz := store.Get(addr)
|
||||
if bz == nil {
|
||||
return bondInfo{}
|
||||
}
|
||||
var bi bondInfo
|
||||
err := sm.cdc.UnmarshalBinary(bz, &bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bi
|
||||
}
|
||||
|
||||
func (sm StakingMapper) setBondInfo(ctx sdk.Context, addr sdk.Address, bi bondInfo) {
|
||||
store := ctx.KVStore(sm.key)
|
||||
bz, err := sm.cdc.MarshalBinary(bi)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
store.Set(addr, bz)
|
||||
}
|
||||
|
||||
func (sm StakingMapper) deleteBondInfo(ctx sdk.Context, addr sdk.Address) {
|
||||
store := ctx.KVStore(sm.key)
|
||||
store.Delete(addr)
|
||||
}
|
||||
|
||||
func (sm StakingMapper) Bond(ctx sdk.Context, addr sdk.Address, pubKey crypto.PubKey, power int64) (int64, sdk.Error) {
|
||||
|
||||
bi := sm.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
bi = bondInfo{
|
||||
PubKey: pubKey,
|
||||
Power: power,
|
||||
}
|
||||
sm.setBondInfo(ctx, addr, bi)
|
||||
return bi.Power, nil
|
||||
}
|
||||
|
||||
newPower := bi.Power + power
|
||||
newBi := bondInfo{
|
||||
PubKey: bi.PubKey,
|
||||
Power: newPower,
|
||||
}
|
||||
sm.setBondInfo(ctx, addr, newBi)
|
||||
|
||||
return newBi.Power, nil
|
||||
}
|
||||
|
||||
func (sm StakingMapper) Unbond(ctx sdk.Context, addr sdk.Address) (crypto.PubKey, int64, sdk.Error) {
|
||||
bi := sm.getBondInfo(ctx, addr)
|
||||
if bi.isEmpty() {
|
||||
return crypto.PubKey{}, 0, ErrInvalidUnbond()
|
||||
}
|
||||
sm.deleteBondInfo(ctx, addr)
|
||||
return bi.PubKey, bi.Power, nil
|
||||
}
|
||||
|
||||
type bondInfo struct {
|
||||
PubKey crypto.PubKey
|
||||
Power int64
|
||||
}
|
||||
|
||||
func (bi bondInfo) isEmpty() bool {
|
||||
if bi == (bondInfo{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue