diff --git a/.gitignore b/.gitignore index 5f3c9db7f..b0684f207 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +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/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 362e84e00..03ef49d35 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/Makefile b/Makefile index 3a53f1944..023e4f8c6 100644 --- a/Makefile +++ b/Makefile @@ -19,14 +19,25 @@ gaia: build: @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 @bash publish/publish.sh @@ -74,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) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7640c1d94..e0ef39cde 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -55,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, @@ -71,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) @@ -241,11 +248,6 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // TODO Return something intelligent panic(err) } - err = app.Router().InitGenesis(app.deliverState.ctx, *genesisState) - if err != nil { - // TODO Return something intelligent - panic(err) - } // NOTE: we don't commit, but BeginBlock for block 1 // starts from this deliverState diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 23abf7fd7..1658c591d 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -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) @@ -187,9 +191,8 @@ func TestInitChainer(t *testing.T) { // 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) @@ -246,7 +249,7 @@ func TestDeliverTx(t *testing.T) { counter += 1 return sdk.Result{} - }, nil) + }) tx := testUpdatePowerTx{} // doesn't matter header := abci.Header{AppHash: []byte("apphash")} @@ -281,7 +284,7 @@ func TestQuery(t *testing.T) { store := ctx.KVStore(capKey) store.Set(key, value) return sdk.Result{} - }, nil) + }) query := abci.RequestQuery{ Path: "/main/key", @@ -346,7 +349,7 @@ func TestValidatorChange(t *testing.T) { app.Router().AddRoute(msgType, func(ctx sdk.Context, msg sdk.Msg) sdk.Result { // TODO return sdk.Result{} - }, nil) + }) // Load latest state, which should be empty. err := app.LoadLatestVersion(capKey) diff --git a/baseapp/router.go b/baseapp/router.go index 17be88309..83efe5dad 100644 --- a/baseapp/router.go +++ b/baseapp/router.go @@ -1,8 +1,6 @@ package baseapp import ( - "encoding/json" - "fmt" "regexp" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,16 +8,14 @@ import ( // Router provides handlers for each transaction type. type Router interface { - AddRoute(r string, h sdk.Handler, i sdk.InitGenesis) (rtr Router) + AddRoute(r string, h sdk.Handler) (rtr Router) Route(path string) (h sdk.Handler) - InitGenesis(ctx sdk.Context, data map[string]json.RawMessage) error } // map a transaction type to a handler and an initgenesis function type route struct { r string h sdk.Handler - i sdk.InitGenesis } type router struct { @@ -38,11 +34,11 @@ func NewRouter() *router { var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString // AddRoute - TODO add description -func (rtr *router) AddRoute(r string, h sdk.Handler, i sdk.InitGenesis) Router { +func (rtr *router) AddRoute(r string, h sdk.Handler) Router { if !isAlpha(r) { panic("route expressions can only contain alphanumeric characters") } - rtr.routes = append(rtr.routes, route{r, h, i}) + rtr.routes = append(rtr.routes, route{r, h}) return rtr } @@ -57,20 +53,3 @@ func (rtr *router) Route(path string) (h sdk.Handler) { } return nil } - -// InitGenesis - call `InitGenesis`, where specified, for all routes -// Return the first error if any, otherwise nil -func (rtr *router) InitGenesis(ctx sdk.Context, data map[string]json.RawMessage) error { - for _, route := range rtr.routes { - if route.i != nil { - encoded, found := data[route.r] - if !found { - return sdk.ErrGenesisParse(fmt.Sprintf("Expected module genesis information for module %s but it was not present", route.r)) - } - if err := route.i(ctx, encoded); err != nil { - return err - } - } - } - return nil -} diff --git a/client/keys/utils.go b/client/keys/utils.go index c6239002c..d1b3d3f65 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -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 } diff --git a/client/lcd/.gitignore b/client/lcd/.gitignore deleted file mode 100644 index ae9067025..000000000 --- a/client/lcd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -tmp-base* \ No newline at end of file diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 75d01a6f2..48cc35a8e 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -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) @@ -257,7 +257,7 @@ func TestIBCTransfer(t *testing.T) { // create TX resultTx := doIBCTransfer(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) @@ -295,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) @@ -320,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 @@ -341,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) @@ -358,9 +365,6 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { Coins: coins, }, }, - "cool": map[string]string{ - "trend": "ice-cold", - }, } stateBytes, err := json.Marshal(appState) if err != nil { @@ -387,7 +391,7 @@ func startTMAndLCD() (*nm.Node, net.Listener, error) { return nil, nil, err } - time.Sleep(time.Second * 2) + waitForStart() return node, lcd, nil } @@ -437,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) @@ -456,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 @@ -485,8 +488,6 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad 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 @@ -499,3 +500,72 @@ func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroad 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 + } + } +} diff --git a/examples/basecoin/app/app.go b/examples/basecoin/app/app.go index e8d1a0011..caae16846 100644 --- a/examples/basecoin/app/app.go +++ b/examples/basecoin/app/app.go @@ -18,8 +18,6 @@ import ( "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,12 +39,13 @@ 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("stake"), } @@ -58,20 +58,22 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp { // 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), nil). - AddRoute("cool", cool.NewHandler(coolKeeper), coolKeeper.InitGenesis). - AddRoute("sketchy", sketchy.NewHandler(), nil). - AddRoute("ibc", ibc.NewHandler(ibcMapper, coinKeeper), nil). - AddRoute("simplestake", simplestake.NewHandler(stakeKeeper), nil) + AddRoute("bank", bank.NewHandler(coinKeeper)). + AddRoute("ibc", ibc.NewHandler(ibcMapper, 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,8 +98,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{ibc.IBCTransferMsg{}, msgTypeIBCTransferMsg}, oldwire.ConcreteType{ibc.IBCReceiveMsg{}, msgTypeIBCReceiveMsg}, oldwire.ConcreteType{simplestake.BondMsg{}, msgTypeBondMsg}, diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 0d916b054..4958240b5 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -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,31 +146,19 @@ func TestGenesis(t *testing.T) { } 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() + 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) { @@ -165,69 +172,124 @@ func TestSendMsgWithAccounts(t *testing.T) { 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() - + 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) { @@ -247,9 +309,6 @@ func TestQuizMsg(t *testing.T) { "accounts": []*types.GenesisAccount{ types.NewGenesisAccount(acc1), }, - "cool": map[string]string{ - "trend": "ice-cold", - }, } stateBytes, err := json.MarshalIndent(genesisState, "", "\t") require.Nil(t, err) @@ -264,48 +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, 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) { +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 := 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() + 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) @@ -329,24 +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) } -// TODO describe the use of this function -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 { @@ -367,8 +408,8 @@ func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, e //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())) } diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 1c4b5833b..a0152aee9 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -14,7 +14,6 @@ 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" @@ -63,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), diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 6a4f45956..395667671 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -10,6 +10,7 @@ import ( 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" @@ -28,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", @@ -47,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", filepath.Join(rootDir, "data")) + 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 } diff --git a/examples/democoin/LICENSE b/examples/democoin/LICENSE new file mode 100644 index 000000000..1697a7443 --- /dev/null +++ b/examples/democoin/LICENSE @@ -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. diff --git a/examples/democoin/Makefile b/examples/democoin/Makefile new file mode 100644 index 000000000..067d03e9b --- /dev/null +++ b/examples/democoin/Makefile @@ -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 diff --git a/examples/democoin/README.md b/examples/democoin/README.md new file mode 100644 index 000000000..fe65abda4 --- /dev/null +++ b/examples/democoin/README.md @@ -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. + diff --git a/examples/democoin/app/app.go b/examples/democoin/app/app.go new file mode 100644 index 000000000..2fffb8324 --- /dev/null +++ b/examples/democoin/app/app.go @@ -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{} + } +} diff --git a/examples/democoin/app/app_test.go b/examples/democoin/app/app_test.go new file mode 100644 index 000000000..bf2ddc232 --- /dev/null +++ b/examples/democoin/app/app_test.go @@ -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())) +} diff --git a/examples/democoin/cmd/democli/main.go b/examples/democoin/cmd/democli/main.go new file mode 100644 index 000000000..602e5478e --- /dev/null +++ b/examples/democoin/cmd/democli/main.go @@ -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() +} diff --git a/examples/democoin/cmd/democoind/main.go b/examples/democoin/cmd/democoind/main.go new file mode 100644 index 000000000..d9421954c --- /dev/null +++ b/examples/democoin/cmd/democoind/main.go @@ -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() +} diff --git a/examples/democoin/types/account.go b/examples/democoin/types/account.go new file mode 100644 index 000000000..f34113fc6 --- /dev/null +++ b/examples/democoin/types/account.go @@ -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 +} diff --git a/examples/basecoin/x/cool/commands/tx.go b/examples/democoin/x/cool/commands/tx.go similarity index 97% rename from examples/basecoin/x/cool/commands/tx.go rename to examples/democoin/x/cool/commands/tx.go index c2d643a90..ab817309c 100644 --- a/examples/basecoin/x/cool/commands/tx.go +++ b/examples/democoin/x/cool/commands/tx.go @@ -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 diff --git a/examples/basecoin/x/cool/errors.go b/examples/democoin/x/cool/errors.go similarity index 100% rename from examples/basecoin/x/cool/errors.go rename to examples/democoin/x/cool/errors.go diff --git a/examples/basecoin/x/cool/handler.go b/examples/democoin/x/cool/handler.go similarity index 100% rename from examples/basecoin/x/cool/handler.go rename to examples/democoin/x/cool/handler.go diff --git a/examples/basecoin/x/cool/keeper.go b/examples/democoin/x/cool/keeper.go similarity index 100% rename from examples/basecoin/x/cool/keeper.go rename to examples/democoin/x/cool/keeper.go diff --git a/examples/basecoin/x/cool/types.go b/examples/democoin/x/cool/types.go similarity index 100% rename from examples/basecoin/x/cool/types.go rename to examples/democoin/x/cool/types.go diff --git a/examples/basecoin/x/sketchy/handler.go b/examples/democoin/x/sketchy/handler.go similarity index 100% rename from examples/basecoin/x/sketchy/handler.go rename to examples/democoin/x/sketchy/handler.go diff --git a/examples/gaia/gaiad/main.go b/examples/gaia/gaiad/main.go index 0c4c49eec..70a44d8cb 100644 --- a/examples/gaia/gaiad/main.go +++ b/examples/gaia/gaiad/main.go @@ -9,6 +9,7 @@ import ( 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" @@ -26,10 +27,10 @@ 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) @@ -45,7 +46,7 @@ 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) { diff --git a/examples/kvstore/main.go b/examples/kvstore/main.go index 9bcabb306..0d80826ed 100644 --- a/examples/kvstore/main.go +++ b/examples/kvstore/main.go @@ -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,13 +35,13 @@ 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) // Set a handler Route. - baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore), nil) + baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore)) // Load latest version. if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil { diff --git a/mock/app.go b/mock/app.go index c7c643290..eda490a8e 100644 --- a/mock/app.go +++ b/mock/app.go @@ -6,6 +6,7 @@ import ( "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" @@ -38,7 +39,7 @@ func NewApp(rootDir string, logger log.Logger) (abci.Application, error) { baseApp.SetInitChainer(InitChainer(capKeyMainStore)) // Set a handler Route. - baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore), nil) + baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore)) // Load latest version. if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil { @@ -106,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": [ { @@ -119,5 +120,5 @@ func GenInitOptions(args []string) (json.RawMessage, error) { } ] }`) - return opts, nil + return opts, "", nil, nil } diff --git a/mock/app_test.go b/mock/app_test.go index 103530e50..47db93e1c 100644 --- a/mock/app_test.go +++ b/mock/app_test.go @@ -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) diff --git a/server/init.go b/server/init.go index 12e330dbc..ab4ffcb7a 100644 --- a/server/init.go +++ b/server/init.go @@ -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 } diff --git a/version/version.go b/version/version.go index 59f530bfb..28e4bea17 100644 --- a/version/version.go +++ b/version/version.go @@ -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 = "" diff --git a/x/ibc/ibc_test.go b/x/ibc/ibc_test.go index bec08fb56..1e0431147 100644 --- a/x/ibc/ibc_test.go +++ b/x/ibc/ibc_test.go @@ -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}, ) diff --git a/x/stake/handler.go b/x/stake/handler.go index 7449141aa..6e3b6ff72 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -71,17 +71,6 @@ func NewHandler(k Keeper, ck bank.CoinKeeper) sdk.Handler { //_____________________________________________________________________ -// XXX should be send in the msg (init in CLI) -//func getSender() sdk.Address { -//signers := msg.GetSigners() -//if len(signers) != 1 { -//return sdk.ErrUnauthorized("there can only be one signer for staking transaction").Result() -//} -//sender := signers[0] -//} - -//_____________________________________________________________________ - // These functions assume everything has been authenticated, // now we just perform action and save @@ -187,8 +176,11 @@ func BondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candidat if err != nil { return err } - newShares := k.candidateAddTokens(ctx, candidate, amount.Amount) + p := k.GetPool(ctx) + p, candidate, newShares := p.candidateAddTokens(candidate, amount.Amount) bond.Shares = bond.Shares.Add(newShares) + k.setPool(ctx, p) + k.setCandidate(ctx, candidate) k.setDelegatorBond(ctx, bond) return nil } @@ -258,7 +250,9 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } // Add the coins - returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + p := k.GetPool(ctx) + var returnAmount int64 + p, candidate, returnAmount = p.candidateRemoveShares(candidate, shares) returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} k.coinKeeper.AddCoins(ctx, bond.DelegatorAddr, returnCoins) @@ -267,7 +261,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { // change the share types to unbonded if they were not already if candidate.Status == Bonded { - k.bondedToUnbondedPool(ctx, candidate) + p, candidate = p.bondedToUnbondedPool(candidate) } // lastly update the status @@ -280,6 +274,7 @@ func handleMsgUnbond(ctx sdk.Context, msg MsgUnbond, k Keeper) sdk.Result { } else { k.setCandidate(ctx, candidate) } + k.setPool(ctx, p) return sdk.Result{} } @@ -293,12 +288,16 @@ func UnbondCoins(ctx sdk.Context, k Keeper, bond DelegatorBond, candidate Candid } bond.Shares = bond.Shares.Sub(shares) - returnAmount := k.candidateRemoveShares(ctx, candidate, shares) + p := k.GetPool(ctx) + var returnAmount int64 + p, candidate, returnAmount = p.candidateRemoveShares(candidate, shares) returnCoins := sdk.Coins{{k.GetParams(ctx).BondDenom, returnAmount}} _, err := k.coinKeeper.AddCoins(ctx, candidate.Address, returnCoins) if err != nil { return err } + k.setPool(ctx, p) + k.setCandidate(ctx, candidate) return nil } diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index b6953df5c..b435b754d 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -1,248 +1,245 @@ package stake -//import ( -//"strconv" -//"testing" +/* +import ( + "strconv" + "testing" -//"github.com/stretchr/testify/assert" -//"github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" -//crypto "github.com/tendermint/go-crypto" + crypto "github.com/tendermint/go-crypto" -//sdk "github.com/cosmos/cosmos-sdk/types" -//) + sdk "github.com/cosmos/cosmos-sdk/types" +) -////______________________________________________________________________ +//______________________________________________________________________ -//func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { -//return MsgDeclareCandidacy{ -//Description: Description{}, -//CandidateAddr: address, -//Bond: sdk.Coin{"fermion", amt}, -//PubKey: pubKey, -//} -//} +func newTestMsgDeclareCandidacy(address sdk.Address, pubKey crypto.PubKey, amt int64) MsgDeclareCandidacy { + return MsgDeclareCandidacy{ + Description: Description{}, + CandidateAddr: address, + Bond: sdk.Coin{"fermion", amt}, + PubKey: pubKey, + } +} -//func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate { -//return MsgDelegate{ -//DelegatorAddr: delegatorAddr, -//CandidateAddr: candidateAddr, -//Bond: sdk.Coin{"fermion", amt}, -//} -//} +func newTestMsgDelegate(amt int64, delegatorAddr, candidateAddr sdk.Address) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delegatorAddr, + CandidateAddr: candidateAddr, + Bond: sdk.Coin{"fermion", amt}, + } +} -//func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { -//ctxDeliver, _, keeper := createTestInput(t, addrs[0], false, 1000) -//ctxCheck, _, keeper := createTestInput(t, addrs[0], true, 1000) +func TestDuplicatesMsgDeclareCandidacy(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 1000) -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + assert.True(t, got.IsOK(), "%v", got) -//// one sender can bond to two different addresses -//msgDeclareCandidacy.Address = addrs[1] -//err := checker.declareCandidacy(msgDeclareCandidacy) -//assert.Nil(t, err, "didn't expected error on checkTx") + // one sender cannot bond twice + msgDeclareCandidacy.PubKey = pks[1] + got = handleMsgDeclareCandidacy(ctx, msgDeclareCandidacy, keeper) + assert.False(t, got.IsOK(), "%v", got) +} -//// two addrs cant bond to the same pubkey -//checker.sender = addrs[1] -//msgDeclareCandidacy.Address = addrs[0] -//err = checker.declareCandidacy(msgDeclareCandidacy) -//assert.NotNil(t, err, "expected error on checkTx") -//} +func TestIncrementsMsgDelegate(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 1000) -//func TestIncrementsMsgDelegate(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + // first declare candidacy + bondAmount := int64(10) + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got) + expectedBond := bondAmount // 1 since we send 1 at the start of loop, -//// first declare candidacy -//bondAmount := int64(10) -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], bondAmount) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected declare candidacy msg to be ok, got %v", got) -//expectedBond := bondAmount // 1 since we send 1 at the start of loop, + // just send the same msgbond multiple times + msgDelegate := newTestMsgDelegate(bondAmount, addrs[0]) + for i := 0; i < 5; i++ { + got := deliverer.delegate(msgDelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// just send the same msgbond multiple times -//msgDelegate := newTestMsgDelegate(bondAmount, addrs[0]) -//for i := 0; i < 5; i++ { -//got := deliverer.delegate(msgDelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the accounts and the bond account have the appropriate values + candidates := mapper.GetCandidates() + expectedBond += bondAmount + //expectedSender := initSender - expectedBond + gotBonded := candidates[0].Liabilities.Evaluate() + //gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper + assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) + //assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix + } +} -////Check that the accounts and the bond account have the appropriate values -//candidates := mapper.GetCandidates() -//expectedBond += bondAmount -////expectedSender := initSender - expectedBond -//gotBonded := candidates[0].Liabilities.Evaluate() -////gotSender := accStore[string(deliverer.sender)] //XXX use StoreMapper -//assert.Equal(t, expectedBond, gotBonded, "i: %v, %v, %v", i, expectedBond, gotBonded) -////assert.Equal(t, expectedSender, gotSender, "i: %v, %v, %v", i, expectedSender, gotSender) // XXX fix -//} -//} +func TestIncrementsMsgUnbond(t *testing.T) { + ctx, _, keeper := createTestInput(t, addrs[0], false, 0) -//func TestIncrementsMsgUnbond(t *testing.T) { -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 0) + // set initial bond + initBond := int64(1000) + //accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper + got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) + assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got) -//// set initial bond -//initBond := int64(1000) -////accStore[string(deliverer.sender)] = initBond //XXX use StoreMapper -//got := deliverer.declareCandidacy(newTestMsgDeclareCandidacy(addrs[0], pks[0], initBond)) -//assert.NoError(t, got, "expected initial bond msg to be ok, got %v", got) + // just send the same msgunbond multiple times + // XXX use decimals here + unbondShares, unbondSharesStr := int64(10), "10" + msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) + nUnbonds := 5 + for i := 0; i < nUnbonds; i++ { + got := deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// just send the same msgunbond multiple times -//// XXX use decimals here -//unbondShares, unbondSharesStr := int64(10), "10" -//msgUndelegate := NewMsgUnbond(addrs[0], unbondSharesStr) -//nUnbonds := 5 -//for i := 0; i < nUnbonds; i++ { -//got := deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the accounts and the bond account have the appropriate values + candidates := mapper.GetCandidates() + expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop + //expectedSender := initSender + (initBond - expectedBond) + gotBonded := candidates[0].Liabilities.Evaluate() + //gotSender := accStore[string(deliverer.sender)] // XXX use storemapper -////Check that the accounts and the bond account have the appropriate values -//candidates := mapper.GetCandidates() -//expectedBond := initBond - int64(i+1)*unbondShares // +1 since we send 1 at the start of loop -////expectedSender := initSender + (initBond - expectedBond) -//gotBonded := candidates[0].Liabilities.Evaluate() -////gotSender := accStore[string(deliverer.sender)] // XXX use storemapper + assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) + //assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix + } -//assert.Equal(t, expectedBond, gotBonded, "%v, %v", expectedBond, gotBonded) -////assert.Equal(t, expectedSender, gotSender, "%v, %v", expectedSender, gotSender) //XXX fix -//} + // these are more than we have bonded now + errorCases := []int64{ + //1<<64 - 1, // more than int64 + //1<<63 + 1, // more than int64 + 1<<63 - 1, + 1 << 31, + initBond, + } + for _, c := range errorCases { + unbondShares := strconv.Itoa(int(c)) + msgUndelegate := NewMsgUnbond(addrs[0], unbondShares) + got = deliverer.unbond(msgUndelegate) + assert.Error(t, got, "expected unbond msg to fail") + } -//// these are more than we have bonded now -//errorCases := []int64{ -////1<<64 - 1, // more than int64 -////1<<63 + 1, // more than int64 -//1<<63 - 1, -//1 << 31, -//initBond, -//} -//for _, c := range errorCases { -//unbondShares := strconv.Itoa(int(c)) -//msgUndelegate := NewMsgUnbond(addrs[0], unbondShares) -//got = deliverer.unbond(msgUndelegate) -//assert.Error(t, got, "expected unbond msg to fail") -//} + leftBonded := initBond - unbondShares*int64(nUnbonds) -//leftBonded := initBond - unbondShares*int64(nUnbonds) + // should be unable to unbond one more than we have + msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) + got = deliverer.unbond(msgUndelegate) + assert.Error(t, got, "expected unbond msg to fail") -//// should be unable to unbond one more than we have -//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded)+1)) -//got = deliverer.unbond(msgUndelegate) -//assert.Error(t, got, "expected unbond msg to fail") + // should be able to unbond just what we have + msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) + got = deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected unbond msg to pass") +} -//// should be able to unbond just what we have -//msgUndelegate = NewMsgUnbond(addrs[0], strconv.Itoa(int(leftBonded))) -//got = deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected unbond msg to pass") -//} +func TestMultipleMsgDeclareCandidacy(t *testing.T) { + initSender := int64(1000) + //ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender) + ctx, mapper, keeper := createTestInput(t, addrs[0], false, initSender) + addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} -//func TestMultipleMsgDeclareCandidacy(t *testing.T) { -//initSender := int64(1000) -//ctx, accStore, mapper, deliverer := createTestInput(t, addrs[0], false, initSender) -//addrs := []sdk.Address{addrs[0], addrs[1], addrs[2]} + // bond them all + for i, addr := range addrs { + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) + deliverer.sender = addr + got := deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// bond them all -//for i, addr := range addrs { -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[i], pks[i], 10) -//deliverer.sender = addr -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is bonded + candidates := mapper.GetCandidates() + require.Equal(t, i, len(candidates)) + val := candidates[i] + balanceExpd := initSender - 10 + balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins() + assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) + assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) + assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } -////Check that the account is bonded -//candidates := mapper.GetCandidates() -//require.Equal(t, i, len(candidates)) -//val := candidates[i] -//balanceExpd := initSender - 10 -//balanceGot := accStore.GetAccount(ctx, val.Address).GetCoins() -//assert.Equal(t, i+1, len(candidates), "expected %d candidates got %d, candidates: %v", i+1, len(candidates), candidates) -//assert.Equal(t, 10, int(val.Liabilities.Evaluate()), "expected %d shares, got %d", 10, val.Liabilities) -//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) -//} + // unbond them all + for i, addr := range addrs { + candidatePre := mapper.GetCandidate(addrs[i]) + msgUndelegate := NewMsgUnbond(addrs[i], "10") + deliverer.sender = addr + got := deliverer.unbond(msgUndelegate) + assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// unbond them all -//for i, addr := range addrs { -//candidatePre := mapper.GetCandidate(addrs[i]) -//msgUndelegate := NewMsgUnbond(addrs[i], "10") -//deliverer.sender = addr -//got := deliverer.unbond(msgUndelegate) -//assert.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is unbonded + candidates := mapper.GetCandidates() + assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) -////Check that the account is unbonded -//candidates := mapper.GetCandidates() -//assert.Equal(t, len(addrs)-(i+1), len(candidates), "expected %d candidates got %d", len(addrs)-(i+1), len(candidates)) + candidatePost := mapper.GetCandidate(addrs[i]) + balanceExpd := initSender + balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins() + assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) + assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) + } +} -//candidatePost := mapper.GetCandidate(addrs[i]) -//balanceExpd := initSender -//balanceGot := accStore.GetAccount(ctx, candidatePre.Address).GetCoins() -//assert.Nil(t, candidatePost, "expected nil candidate retrieve, got %d", 0, candidatePost) -//assert.Equal(t, balanceExpd, balanceGot, "expected account to have %d, got %d", balanceExpd, balanceGot) -//} -//} +func TestMultipleMsgDelegate(t *testing.T) { + sender, delegators := addrs[0], addrs[1:] + _, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + ctx, _, keeper := createTestInput(t, addrs[0], false, 0) -//func TestMultipleMsgDelegate(t *testing.T) { -//sender, delegators := addrs[0], addrs[1:] -//_, _, mapper, deliverer := createTestInput(t, addrs[0], false, 1000) + //first make a candidate + msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + require.NoError(t, got, "expected msg to be ok, got %v", got) -////first make a candidate -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(sender, pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//require.NoError(t, got, "expected msg to be ok, got %v", got) + // delegate multiple parties + for i, delegator := range delegators { + msgDelegate := newTestMsgDelegate(10, sender) + deliverer.sender = delegator + got := deliverer.delegate(msgDelegate) + require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// delegate multiple parties -//for i, delegator := range delegators { -//msgDelegate := newTestMsgDelegate(10, sender) -//deliverer.sender = delegator -//got := deliverer.delegate(msgDelegate) -//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is bonded + bond := mapper.getDelegatorBond(delegator, sender) + assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) + } -////Check that the account is bonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.NotNil(t, bond, "expected delegatee bond %d to exist", bond) -//} + // unbond them all + for i, delegator := range delegators { + msgUndelegate := NewMsgUnbond(sender, "10") + deliverer.sender = delegator + got := deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) -//// unbond them all -//for i, delegator := range delegators { -//msgUndelegate := NewMsgUnbond(sender, "10") -//deliverer.sender = delegator -//got := deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected msg %d to be ok, got %v", i, got) + //Check that the account is unbonded + bond := mapper.getDelegatorBond(delegator, sender) + assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) + } +} -////Check that the account is unbonded -//bond := mapper.getDelegatorBond(delegator, sender) -//assert.Nil(t, bond, "expected delegatee bond %d to be nil", bond) -//} -//} +func TestVoidCandidacy(t *testing.T) { + sender, delegator := addrs[0], addrs[1] + _, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) -//func TestVoidCandidacy(t *testing.T) { -//sender, delegator := addrs[0], addrs[1] -//_, _, _, deliverer := createTestInput(t, addrs[0], false, 1000) + // create the candidate + msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) + got := deliverer.declareCandidacy(msgDeclareCandidacy) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// create the candidate -//msgDeclareCandidacy := newTestMsgDeclareCandidacy(addrs[0], pks[0], 10) -//got := deliverer.declareCandidacy(msgDeclareCandidacy) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + // bond a delegator + msgDelegate := newTestMsgDelegate(10, addrs[0]) + deliverer.sender = delegator + got = deliverer.delegate(msgDelegate) + require.NoError(t, got, "expected ok, got %v", got) -//// bond a delegator -//msgDelegate := newTestMsgDelegate(10, addrs[0]) -//deliverer.sender = delegator -//got = deliverer.delegate(msgDelegate) -//require.NoError(t, got, "expected ok, got %v", got) + // unbond the candidates bond portion + msgUndelegate := NewMsgUnbond(addrs[0], "10") + deliverer.sender = sender + got = deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// unbond the candidates bond portion -//msgUndelegate := NewMsgUnbond(addrs[0], "10") -//deliverer.sender = sender -//got = deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") + // test that this pubkey cannot yet be bonded too + deliverer.sender = delegator + got = deliverer.delegate(msgDelegate) + assert.Error(t, got, "expected error, got %v", got) -//// test that this pubkey cannot yet be bonded too -//deliverer.sender = delegator -//got = deliverer.delegate(msgDelegate) -//assert.Error(t, got, "expected error, got %v", got) + // test that the delegator can still withdraw their bonds + got = deliverer.unbond(msgUndelegate) + require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") -//// test that the delegator can still withdraw their bonds -//got = deliverer.unbond(msgUndelegate) -//require.NoError(t, got, "expected no error on runMsgDeclareCandidacy") - -//// verify that the pubkey can now be reused -//got = deliverer.declareCandidacy(msgDeclareCandidacy) -//assert.NoError(t, got, "expected ok, got %v", got) -//} + // verify that the pubkey can now be reused + got = deliverer.declareCandidacy(msgDeclareCandidacy) + assert.NoError(t, got, "expected ok, got %v", got) +} +*/ diff --git a/x/stake/keeper.go b/x/stake/keeper.go index d96bbe3d4..95eb85e76 100644 --- a/x/stake/keeper.go +++ b/x/stake/keeper.go @@ -362,3 +362,33 @@ func (k Keeper) setParams(ctx sdk.Context, params Params) { store.Set(ParamKey, b) k.params = Params{} // clear the cache } + +//_______________________________________________________________________ + +// load/save the pool +func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) { + // check if cached before anything + if k.gs != (Pool{}) { + return k.gs + } + store := ctx.KVStore(k.storeKey) + b := store.Get(PoolKey) + if b == nil { + return initialPool() + } + err := k.cdc.UnmarshalBinary(b, &gs) + if err != nil { + panic(err) // This error should never occur big problem if does + } + return +} + +func (k Keeper) setPool(ctx sdk.Context, p Pool) { + store := ctx.KVStore(k.storeKey) + b, err := k.cdc.MarshalBinary(p) + if err != nil { + panic(err) + } + store.Set(PoolKey, b) + k.gs = Pool{} // clear the cache +} diff --git a/x/stake/keeper_keys.go b/x/stake/keeper_keys.go index 3b4c77174..45e0570bc 100644 --- a/x/stake/keeper_keys.go +++ b/x/stake/keeper_keys.go @@ -16,6 +16,7 @@ var ( 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{0x05} // prefix for each key to the last updated validator group + ToKickOutValidatorsKey = []byte{0x06} // prefix for each key to the last updated validator group DelegatorBondKeyPrefix = []byte{0x07} // prefix for each key to a delegator's bond diff --git a/x/stake/keeper_test.go b/x/stake/keeper_test.go index 17260cc0f..aa36cd585 100644 --- a/x/stake/keeper_test.go +++ b/x/stake/keeper_test.go @@ -558,3 +558,18 @@ func TestParams(t *testing.T) { resParams = keeper.GetParams(ctx) assert.Equal(t, expParams, resParams) } + +func TestPool(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + expPool := initialPool() + + //check that the empty keeper loads the default + resPool := keeper.GetPool(ctx) + assert.Equal(t, expPool, resPool) + + //modify a params, save, and retrieve + expPool.TotalSupply = 777 + keeper.setPool(ctx, expPool) + resPool = keeper.GetPool(ctx) + assert.Equal(t, expPool, resPool) +} diff --git a/x/stake/pool.go b/x/stake/pool.go index 4c185580e..df6407bd8 100644 --- a/x/stake/pool.go +++ b/x/stake/pool.go @@ -4,132 +4,115 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// load/save the global staking state -func (k Keeper) GetPool(ctx sdk.Context) (gs Pool) { - // check if cached before anything - if k.gs != (Pool{}) { - return k.gs +// get the bond ratio of the global state +func (p Pool) bondedRatio() sdk.Rat { + if p.TotalSupply > 0 { + return sdk.NewRat(p.BondedPool, p.TotalSupply) } - store := ctx.KVStore(k.storeKey) - b := store.Get(PoolKey) - if b == nil { - return initialPool() - } - err := k.cdc.UnmarshalBinary(b, &gs) - if err != nil { - panic(err) // This error should never occur big problem if does - } - return + return sdk.ZeroRat } -func (k Keeper) setPool(ctx sdk.Context, p Pool) { - store := ctx.KVStore(k.storeKey) - b, err := k.cdc.MarshalBinary(p) - if err != nil { - panic(err) +// get the exchange rate of bonded token per issued share +func (p Pool) bondedShareExRate() sdk.Rat { + if p.BondedShares.IsZero() { + return sdk.OneRat } - store.Set(PoolKey, b) - k.gs = Pool{} // clear the cache + return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) } -//_______________________________________________________________________ - -//TODO make these next two functions more efficient should be reading and writting to state ye know +// get the exchange rate of unbonded tokens held in candidates per issued share +func (p Pool) unbondedShareExRate() sdk.Rat { + if p.UnbondedShares.IsZero() { + return sdk.OneRat + } + return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) +} // move a candidates asset pool from bonded to unbonded pool -func (k Keeper) bondedToUnbondedPool(ctx sdk.Context, candidate Candidate) { +func (p Pool) bondedToUnbondedPool(candidate Candidate) (Pool, Candidate) { // replace bonded shares with unbonded shares - tokens := k.removeSharesBonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensUnbonded(ctx, tokens) + p, tokens := p.removeSharesBonded(candidate.Assets) + p, candidate.Assets = p.addTokensUnbonded(tokens) candidate.Status = Unbonded - k.setCandidate(ctx, candidate) + return p, candidate } // move a candidates asset pool from unbonded to bonded pool -func (k Keeper) unbondedToBondedPool(ctx sdk.Context, candidate Candidate) { +func (p Pool) unbondedToBondedPool(candidate Candidate) (Pool, Candidate) { // replace unbonded shares with bonded shares - tokens := k.removeSharesUnbonded(ctx, candidate.Assets) - candidate.Assets = k.addTokensBonded(ctx, tokens) + p, tokens := p.removeSharesUnbonded(candidate.Assets) + p, candidate.Assets = p.addTokensBonded(tokens) candidate.Status = Bonded - k.setCandidate(ctx, candidate) + return p, candidate } //_______________________________________________________________________ -func (k Keeper) addTokensBonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.GetPool(ctx) - issuedShares = p.bondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens +func (p Pool) addTokensBonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { + issuedShares = sdk.NewRat(amount).Quo(p.bondedShareExRate()) // (tokens/shares)^-1 * tokens p.BondedPool += amount p.BondedShares = p.BondedShares.Add(issuedShares) - k.setPool(ctx, p) - return + return p, issuedShares } -func (k Keeper) removeSharesBonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.GetPool(ctx) +func (p Pool) removeSharesBonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { removedTokens = p.bondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.BondedShares = p.BondedShares.Sub(shares) p.BondedPool -= removedTokens - k.setPool(ctx, p) - return + return p, removedTokens } -func (k Keeper) addTokensUnbonded(ctx sdk.Context, amount int64) (issuedShares sdk.Rat) { - p := k.GetPool(ctx) +func (p Pool) addTokensUnbonded(amount int64) (p2 Pool, issuedShares sdk.Rat) { issuedShares = p.unbondedShareExRate().Inv().Mul(sdk.NewRat(amount)) // (tokens/shares)^-1 * tokens p.UnbondedShares = p.UnbondedShares.Add(issuedShares) p.UnbondedPool += amount - k.setPool(ctx, p) - return + return p, issuedShares } -func (k Keeper) removeSharesUnbonded(ctx sdk.Context, shares sdk.Rat) (removedTokens int64) { - p := k.GetPool(ctx) +func (p Pool) removeSharesUnbonded(shares sdk.Rat) (p2 Pool, removedTokens int64) { removedTokens = p.unbondedShareExRate().Mul(shares).Evaluate() // (tokens/shares) * shares p.UnbondedShares = p.UnbondedShares.Sub(shares) p.UnbondedPool -= removedTokens - k.setPool(ctx, p) - return + return p, removedTokens } //_______________________________________________________________________ // add tokens to a candidate -func (k Keeper) candidateAddTokens(ctx sdk.Context, candidate Candidate, amount int64) (issuedDelegatorShares sdk.Rat) { +func (p Pool) candidateAddTokens(candidate Candidate, + amount int64) (p2 Pool, candidate2 Candidate, issuedDelegatorShares sdk.Rat) { - p := k.GetPool(ctx) exRate := candidate.delegatorShareExRate() var receivedGlobalShares sdk.Rat if candidate.Status == Bonded { - receivedGlobalShares = k.addTokensBonded(ctx, amount) + p, receivedGlobalShares = p.addTokensBonded(amount) } else { - receivedGlobalShares = k.addTokensUnbonded(ctx, amount) + p, receivedGlobalShares = p.addTokensUnbonded(amount) } candidate.Assets = candidate.Assets.Add(receivedGlobalShares) issuedDelegatorShares = exRate.Mul(receivedGlobalShares) candidate.Liabilities = candidate.Liabilities.Add(issuedDelegatorShares) - k.setPool(ctx, p) // TODO cache Pool? - return + + return p, candidate, issuedDelegatorShares } // remove shares from a candidate -func (k Keeper) candidateRemoveShares(ctx sdk.Context, candidate Candidate, shares sdk.Rat) (createdCoins int64) { +func (p Pool) candidateRemoveShares(candidate Candidate, + shares sdk.Rat) (p2 Pool, candidate2 Candidate, createdCoins int64) { - p := k.GetPool(ctx) //exRate := candidate.delegatorShareExRate() //XXX make sure not used globalPoolSharesToRemove := candidate.delegatorShareExRate().Mul(shares) if candidate.Status == Bonded { - createdCoins = k.removeSharesBonded(ctx, globalPoolSharesToRemove) + p, createdCoins = p.removeSharesBonded(globalPoolSharesToRemove) } else { - createdCoins = k.removeSharesUnbonded(ctx, globalPoolSharesToRemove) + p, createdCoins = p.removeSharesUnbonded(globalPoolSharesToRemove) } candidate.Assets = candidate.Assets.Sub(globalPoolSharesToRemove) candidate.Liabilities = candidate.Liabilities.Sub(shares) - k.setPool(ctx, p) // TODO cache Pool? - return + return p, candidate, createdCoins } diff --git a/x/stake/pool_test.go b/x/stake/pool_test.go index 760a89a16..878288986 100644 --- a/x/stake/pool_test.go +++ b/x/stake/pool_test.go @@ -1,22 +1,374 @@ package stake import ( + "fmt" + "math/rand" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" ) -func TestPool(t *testing.T) { +func TestBondedToUnbondedPool(t *testing.T) { ctx, _, keeper := createTestInput(t, nil, false, 0) - expPool := initialPool() - //check that the empty keeper loads the default - resPool := keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.OneRat, + Liabilities: sdk.OneRat, + } + poolB, candB := poolA.bondedToUnbondedPool(candA) - //modify a params, save, and retrieve - expPool.TotalSupply = 777 - keeper.setPool(ctx, expPool) - resPool = keeper.GetPool(ctx) - assert.Equal(t, expPool, resPool) + // status unbonded + assert.Equal(t, candB.Status, Unbonded) + // same exchange rate, assets unchanged + assert.Equal(t, candB.Assets, candA.Assets) + // bonded pool decreased + assert.Equal(t, poolB.BondedPool, poolA.BondedPool-candA.Assets.Evaluate()) + // unbonded pool increased + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+candA.Assets.Evaluate()) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) +} + +func TestUnbonbedtoBondedPool(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.OneRat, + Liabilities: sdk.OneRat, + } + candA.Status = Unbonded + poolB, candB := poolA.unbondedToBondedPool(candA) + + // status bonded + assert.Equal(t, candB.Status, Bonded) + // same exchange rate, assets unchanged + assert.Equal(t, candB.Assets, candA.Assets) + // bonded pool increased + assert.Equal(t, poolB.BondedPool, poolA.BondedPool+candA.Assets.Evaluate()) + // unbonded pool decreased + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-candA.Assets.Evaluate()) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool, poolA.BondedPool+poolA.UnbondedPool) +} + +func TestAddTokensBonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + poolB, sharesB := poolA.addTokensBonded(10) + assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Add(sharesB)) + assert.Equal(t, poolB.BondedPool, poolA.BondedPool+10) + + // same number of bonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool)) +} + +func TestRemoveSharesBonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + poolB, tokensB := poolA.removeSharesBonded(sdk.NewRat(10)) + assert.Equal(t, poolB.bondedShareExRate(), sdk.OneRat) + + // correct changes to bonded shares and bonded pool + assert.Equal(t, poolB.BondedShares, poolA.BondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.BondedPool, poolA.BondedPool-tokensB) + + // same number of bonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.BondedShares, sdk.NewRat(poolB.BondedPool)) +} + +func TestAddTokensUnbonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, sharesB := poolA.addTokensUnbonded(10) + assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat) + + // correct changes to unbonded shares and unbonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Add(sharesB)) + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool+10) + + // same number of unbonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool)) +} + +func TestRemoveSharesUnbonded(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, tokensB := poolA.removeSharesUnbonded(sdk.NewRat(10)) + assert.Equal(t, poolB.unbondedShareExRate(), sdk.OneRat) + + // correct changes to unbonded shares and bonded pool + assert.Equal(t, poolB.UnbondedShares, poolA.UnbondedShares.Sub(sdk.NewRat(10))) + assert.Equal(t, poolB.UnbondedPool, poolA.UnbondedPool-tokensB) + + // same number of unbonded shares / tokens when exchange rate is one + assert.Equal(t, poolB.UnbondedShares, sdk.NewRat(poolB.UnbondedPool)) +} + +func TestCandidateAddTokens(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + } + poolA.BondedPool = candA.Assets.Evaluate() + poolA.BondedShares = candA.Assets + assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, candB, sharesB := poolA.candidateAddTokens(candA, 10) + + // shares were issued + assert.Equal(t, sdk.NewRat(10).Mul(candA.delegatorShareExRate()), sharesB) + // pool shares were added + assert.Equal(t, candB.Assets, candA.Assets.Add(sdk.NewRat(10))) + // conservation of tokens + assert.Equal(t, poolB.BondedPool, 10+poolA.BondedPool) +} + +func TestCandidateRemoveShares(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + + poolA := keeper.GetPool(ctx) + candA := Candidate{ + Status: Bonded, + Address: addrs[0], + PubKey: pks[0], + Assets: sdk.NewRat(9), + Liabilities: sdk.NewRat(9), + } + poolA.BondedPool = candA.Assets.Evaluate() + poolA.BondedShares = candA.Assets + assert.Equal(t, candA.delegatorShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.bondedShareExRate(), sdk.OneRat) + assert.Equal(t, poolA.unbondedShareExRate(), sdk.OneRat) + poolB, candB, coinsB := poolA.candidateRemoveShares(candA, sdk.NewRat(10)) + + // coins were created + assert.Equal(t, coinsB, int64(10)) + // pool shares were removed + assert.Equal(t, candB.Assets, candA.Assets.Sub(sdk.NewRat(10).Mul(candA.delegatorShareExRate()))) + // conservation of tokens + assert.Equal(t, poolB.UnbondedPool+poolB.BondedPool+coinsB, poolA.UnbondedPool+poolA.BondedPool) +} + +///////////////////////////////////// +// TODO Make all random tests less obfuscated! + +// generate a random candidate +func randomCandidate(r *rand.Rand) Candidate { + var status CandidateStatus + if r.Float64() < float64(0.5) { + status = Bonded + } else { + status = Unbonded + } + assets := sdk.NewRat(int64(r.Int31n(10000))) + liabilities := sdk.NewRat(int64(r.Int31n(10000))) + return Candidate{ + Status: status, + Address: addrs[0], + PubKey: pks[0], + Assets: assets, + Liabilities: liabilities, + } +} + +// generate a random staking state +func randomSetup(r *rand.Rand) (Pool, Candidate) { + pool := Pool{ + TotalSupply: 0, + BondedShares: sdk.ZeroRat, + UnbondedShares: sdk.ZeroRat, + BondedPool: 0, + UnbondedPool: 0, + InflationLastTime: 0, + Inflation: sdk.NewRat(7, 100), + } + + candidate := randomCandidate(r) + if candidate.Status == Bonded { + pool.BondedShares = pool.BondedShares.Add(candidate.Assets) + pool.BondedPool += candidate.Assets.Evaluate() + } else { + pool.UnbondedShares = pool.UnbondedShares.Add(candidate.Assets) + pool.UnbondedPool += candidate.Assets.Evaluate() + } + return pool, candidate +} + +func randomTokens(r *rand.Rand) int64 { + return int64(r.Int31n(10000)) +} + +// operation that transforms staking state +type Operation func(p Pool, c Candidate) (Pool, Candidate, int64, string) + +// pick a random staking operation +func randomOperation(r *rand.Rand) Operation { + operations := []Operation{ + + // bond/unbond + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + var msg string + if cand.Status == Bonded { + msg = fmt.Sprintf("Unbonded previously bonded candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand = p.bondedToUnbondedPool(cand) + } else { + msg = fmt.Sprintf("Bonded previously unbonded candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand = p.unbondedToBondedPool(cand) + } + return p, cand, 0, msg + }, + + // add some tokens to a candidate + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + tokens := int64(r.Int31n(1000)) + + msg := fmt.Sprintf("candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + + p, cand, _ = p.candidateAddTokens(cand, tokens) + + msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) + return p, cand, -1 * tokens, msg // tokens are removed so for accounting must be negative + }, + + // remove some shares from a candidate + func(p Pool, cand Candidate) (Pool, Candidate, int64, string) { + + shares := sdk.NewRat(int64(r.Int31n(1000))) + + if shares.GT(cand.Liabilities) { + shares = cand.Liabilities.Quo(sdk.NewRat(2)) + } + + msg := fmt.Sprintf("candidate %s (assets: %d, liabilities: %d, delegatorShareExRate: %v)", + cand.Address, cand.Assets.Evaluate(), cand.Liabilities.Evaluate(), cand.delegatorShareExRate()) + p, cand, tokens := p.candidateRemoveShares(cand, shares) + + msg = fmt.Sprintf("Removed %d shares from %s", shares.Evaluate(), msg) + + return p, cand, tokens, msg + }, + } + r.Shuffle(len(operations), func(i, j int) { + operations[i], operations[j] = operations[j], operations[i] + }) + return operations[0] +} + +// ensure invariants that should always be true are true +func assertInvariants(t *testing.T, msg string, + pOrig Pool, cOrig Candidate, pMod Pool, cMod Candidate, tokens int64) { + + // total tokens conserved + require.Equal(t, + pOrig.UnbondedPool+pOrig.BondedPool, + pMod.UnbondedPool+pMod.BondedPool+tokens, + "msg: %v\n, pOrig.UnbondedPool: %v, pOrig.BondedPool: %v, pMod.UnbondedPool: %v, pMod.BondedPool: %v, tokens: %v\n", + msg, + pOrig.UnbondedPool, pOrig.BondedPool, + pMod.UnbondedPool, pMod.BondedPool, tokens) + + // nonnegative shares + require.False(t, pMod.BondedShares.LT(sdk.ZeroRat), + "msg: %v\n, pOrig: %v\n, pMod: %v\n, cOrig: %v\n, cMod %v, tokens: %v\n", + msg, pOrig, pMod, cOrig, cMod, tokens) + require.False(t, pMod.UnbondedShares.LT(sdk.ZeroRat), + "msg: %v\n, pOrig: %v\n, pMod: %v\n, cOrig: %v\n, cMod %v, tokens: %v\n", + msg, pOrig, pMod, cOrig, cMod, tokens) + + // nonnegative ex rates + require.False(t, pMod.bondedShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative bondedShareExRate: %d", + msg, pMod.bondedShareExRate().Evaluate()) + + require.False(t, pMod.unbondedShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative unbondedShareExRate: %d", + msg, pMod.unbondedShareExRate().Evaluate()) + + // nonnegative ex rate + require.False(t, cMod.delegatorShareExRate().LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.delegatorShareExRate(): %v (candidate.PubKey: %s)", + msg, + cMod.delegatorShareExRate(), + cMod.PubKey, + ) + + // nonnegative assets / liabilities + require.False(t, cMod.Assets.LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.Assets: %d (candidate.Liabilities: %d, candidate.PubKey: %s)", + msg, + cMod.Assets.Evaluate(), + cMod.Liabilities.Evaluate(), + cMod.PubKey, + ) + + require.False(t, cMod.Liabilities.LT(sdk.ZeroRat), + "Applying operation \"%s\" resulted in negative candidate.Liabilities: %d (candidate.Assets: %d, candidate.PubKey: %s)", + msg, + cMod.Liabilities.Evaluate(), + cMod.Assets.Evaluate(), + cMod.PubKey, + ) +} + +// run random operations in a random order on a random state, assert invariants hold +func TestIntegrationInvariants(t *testing.T) { + for i := 0; i < 10; i++ { + + r1 := rand.New(rand.NewSource(time.Now().UnixNano())) + pool, candidates := randomSetup(r1) + initialPool, initialCandidates := pool, candidates + + assertInvariants(t, "no operation", + initialPool, initialCandidates, + pool, candidates, 0) + + for j := 0; j < 100; j++ { + + r2 := rand.New(rand.NewSource(time.Now().UnixNano())) + pool, candidates, tokens, msg := randomOperation(r2)(pool, candidates) + + assertInvariants(t, msg, + initialPool, initialCandidates, + pool, candidates, tokens) + } + } } diff --git a/x/stake/test_common.go b/x/stake/test_common.go index aef425581..03f9fe92c 100644 --- a/x/stake/test_common.go +++ b/x/stake/test_common.go @@ -124,12 +124,11 @@ func createTestInput(t *testing.T, sender sdk.Address, isCheckTx bool, initCoins ck := bank.NewCoinKeeper(accountMapper) keeper := NewKeeper(ctx, cdc, keyStake, ck) - //params := paramsNoInflation() - params := keeper.GetParams(ctx) - // fill all the addresses with some coins for _, addr := range addrs { - ck.AddCoins(ctx, addr, sdk.Coins{{params.BondDenom, initCoins}}) + ck.AddCoins(ctx, addr, sdk.Coins{ + {keeper.GetParams(ctx).BondDenom, initCoins}, + }) } return ctx, accountMapper, keeper diff --git a/x/stake/tick.go b/x/stake/tick.go index 6aa2da95d..0a85cd865 100644 --- a/x/stake/tick.go +++ b/x/stake/tick.go @@ -2,42 +2,38 @@ package stake import ( sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/abci/types" ) const ( - hrsPerYear = 8766 // as defined by a julian year of 365.25 days - precision = 1000000000 + hrsPerYr = 8766 // as defined by a julian year of 365.25 days + precision = 1000000000 ) -var hrsPerYrRat = sdk.NewRat(hrsPerYear) // as defined by a julian year of 365.25 days +var hrsPerYrRat = sdk.NewRat(hrsPerYr) // as defined by a julian year of 365.25 days // Tick - called at the end of every block -func (k Keeper) Tick(ctx sdk.Context) (change []*abci.Validator, err error) { - - // retrieve params +func (k Keeper) Tick(ctx sdk.Context) (change []Validator) { p := k.GetPool(ctx) - height := ctx.BlockHeight() // Process Validator Provisions - // XXX right now just process every 5 blocks, in new SDK make hourly - if p.InflationLastTime+5 <= height { - p.InflationLastTime = height - k.processProvisions(ctx) + blockTime := ctx.BlockHeader().Time // XXX assuming in seconds, confirm + if p.InflationLastTime+blockTime >= 3600 { + p.InflationLastTime = blockTime + p = k.processProvisions(ctx) } - newVals := k.GetValidators(ctx) + // save the params + k.setPool(ctx, p) - // XXX determine change from old validators, set to change - _ = newVals - return change, nil + change = k.getAccUpdateValidators(ctx) + return } // process provisions for an hour period -func (k Keeper) processProvisions(ctx sdk.Context) { +func (k Keeper) processProvisions(ctx sdk.Context) Pool { pool := k.GetPool(ctx) - pool.Inflation = k.nextInflation(ctx).Round(precision) + pool.Inflation = k.nextInflation(ctx) // Because the validators hold a relative bonded share (`GlobalStakeShare`), when // more bonded tokens are added proportionally to all validators the only term @@ -46,9 +42,7 @@ func (k Keeper) processProvisions(ctx sdk.Context) { provisions := pool.Inflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat).Evaluate() pool.BondedPool += provisions pool.TotalSupply += provisions - - // save the params - k.setPool(ctx, pool) + return pool } // get the next inflation rate for the hour @@ -75,5 +69,5 @@ func (k Keeper) nextInflation(ctx sdk.Context) (inflation sdk.Rat) { inflation = params.InflationMin } - return + return inflation.Round(precision) } diff --git a/x/stake/tick_test.go b/x/stake/tick_test.go index 540ce4699..24d95809f 100644 --- a/x/stake/tick_test.go +++ b/x/stake/tick_test.go @@ -1,116 +1,134 @@ package stake -//import ( -//"testing" +import ( + "testing" -//sdk "github.com/cosmos/cosmos-sdk/types" -//"github.com/stretchr/testify/assert" -//) + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) -//func TestGetInflation(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//params := defaultParams() -//keeper.setParams(ctx, params) -//gs := keeper.GetPool(ctx) +func TestGetInflation(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + pool := keeper.GetPool(ctx) + params := keeper.GetParams(ctx) + hrsPerYrRat := sdk.NewRat(hrsPerYr) -//// Governing Mechanism: -//// bondedRatio = BondedPool / TotalSupply -//// inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange + // Governing Mechanism: + // bondedRatio = BondedPool / TotalSupply + // inflationRateChangePerYear = (1- bondedRatio/ GoalBonded) * MaxInflationRateChange -//tests := []struct { -//setBondedPool, setTotalSupply int64 -//setInflation, expectedChange sdk.Rat -//}{ -//// with 0% bonded atom supply the inflation should increase by InflationRateChange -//{0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYr)}, + tests := []struct { + name string + setBondedPool, setTotalSupply int64 + setInflation, expectedChange sdk.Rat + }{ + // with 0% bonded atom supply the inflation should increase by InflationRateChange + {"test 1", 0, 0, sdk.NewRat(7, 100), params.InflationRateChange.Quo(hrsPerYrRat).Round(precision)}, -//// 100% bonded, starting at 20% inflation and being reduced -//{1, 1, sdk.NewRat(20, 100), sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + // 100% bonded, starting at 20% inflation and being reduced + // (1 - (1/0.67))*(0.13/8667) + {"test 2", 1, 1, sdk.NewRat(20, 100), + sdk.OneRat.Sub(sdk.OneRat.Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, -//// 50% bonded, starting at 10% inflation and being increased -//{1, 2, sdk.NewRat(10, 100), sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)}, + // 50% bonded, starting at 10% inflation and being increased + {"test 3", 1, 2, sdk.NewRat(10, 100), + sdk.OneRat.Sub(sdk.NewRat(1, 2).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYrRat).Round(precision)}, -//// test 7% minimum stop (testing with 100% bonded) -//{1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, -//{1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000)}, + // test 7% minimum stop (testing with 100% bonded) + {"test 4", 1, 1, sdk.NewRat(7, 100), sdk.ZeroRat}, + {"test 5", 1, 1, sdk.NewRat(70001, 1000000), sdk.NewRat(-1, 1000000).Round(precision)}, -//// test 20% maximum stop (testing with 0% bonded) -//{0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, -//{0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000)}, + // test 20% maximum stop (testing with 0% bonded) + {"test 6", 0, 0, sdk.NewRat(20, 100), sdk.ZeroRat}, + {"test 7", 0, 0, sdk.NewRat(199999, 1000000), sdk.NewRat(1, 1000000).Round(precision)}, -//// perfect balance shouldn't change inflation -//{67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, -//} -//for _, tc := range tests { -//gs.BondedPool, p.TotalSupply = tc.setBondedPool, tc.setTotalSupply -//gs.Inflation = tc.setInflation + // perfect balance shouldn't change inflation + {"test 8", 67, 100, sdk.NewRat(15, 100), sdk.ZeroRat}, + } + for _, tc := range tests { + pool.BondedPool, pool.TotalSupply = tc.setBondedPool, tc.setTotalSupply + pool.Inflation = tc.setInflation + keeper.setPool(ctx, pool) -//inflation := nextInflation(gs, params) -//diffInflation := inflation.Sub(tc.setInflation) + inflation := keeper.nextInflation(ctx) + diffInflation := inflation.Sub(tc.setInflation) -//assert.True(t, diffInflation.Equal(tc.expectedChange), -//"%v, %v", diffInflation, tc.expectedChange) -//} -//} + assert.True(t, diffInflation.Equal(tc.expectedChange), + "Name: %v\nDiff: %v\nExpected: %v\n", tc.name, diffInflation, tc.expectedChange) + } +} -//func TestProcessProvisions(t *testing.T) { -//ctx, _, keeper := createTestInput(t, nil, false, 0) -//params := defaultParams() -//keeper.setParams(ctx, params) -//gs := keeper.GetPool(ctx) +func TestProcessProvisions(t *testing.T) { + ctx, _, keeper := createTestInput(t, nil, false, 0) + params := defaultParams() + keeper.setParams(ctx, params) + pool := keeper.GetPool(ctx) -//// create some candidates some bonded, some unbonded -//candidates := candidatesFromAddrsEmpty(addrs) -//for i, candidate := range candidates { -//if i < 5 { -//candidate.Status = Bonded -//} -//mintedTokens := int64((i + 1) * 10000000) -//gs.TotalSupply += mintedTokens -//keeper.candidateAddTokens(ctx, candidate, mintedTokens) -//keeper.setCandidate(ctx, candidate) -//} -//var totalSupply int64 = 550000000 -//var bondedShares int64 = 150000000 -//var unbondedShares int64 = 400000000 + // create some candidates some bonded, some unbonded + candidates := make([]Candidate, 10) + for i := 0; i < 10; i++ { + c := Candidate{ + Status: Unbonded, + PubKey: pks[i], + Address: addrs[i], + Assets: sdk.NewRat(0), + Liabilities: sdk.NewRat(0), + } + if i < 5 { + c.Status = Bonded + } + mintedTokens := int64((i + 1) * 10000000) + pool.TotalSupply += mintedTokens + pool, c, _ = pool.candidateAddTokens(c, mintedTokens) -//// initial bonded ratio ~ 27% -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", p.bondedRatio()) + keeper.setCandidate(ctx, c) + candidates[i] = c + } + keeper.setPool(ctx, pool) + var totalSupply int64 = 550000000 + var bondedShares int64 = 150000000 + var unbondedShares int64 = 400000000 + assert.Equal(t, totalSupply, pool.TotalSupply) + assert.Equal(t, bondedShares, pool.BondedPool) + assert.Equal(t, unbondedShares, pool.UnbondedPool) -//// Supplies -//assert.Equal(t, totalSupply, p.TotalSupply) -//assert.Equal(t, bondedShares, p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) + // initial bonded ratio ~ 27% + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(bondedShares, totalSupply)), "%v", pool.bondedRatio()) -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Equal(sdk.OneRat), "%v", p.bondedShareExRate()) + // test the value of candidate shares + assert.True(t, pool.bondedShareExRate().Equal(sdk.OneRat), "%v", pool.bondedShareExRate()) -//initialSupply := p.TotalSupply -//initialUnbonded := p.TotalSupply - p.BondedPool + initialSupply := pool.TotalSupply + initialUnbonded := pool.TotalSupply - pool.BondedPool -//// process the provisions a year -//for hr := 0; hr < 8766; hr++ { -//expInflation := nextInflation(gs, params).Round(1000000000) -//expProvisions := (expInflation.Mul(sdk.NewRat(gs.TotalSupply)).Quo(hrsPerYr)).Evaluate() -//startBondedPool := p.BondedPool -//startTotalSupply := p.TotalSupply -//processProvisions(ctx, keeper, p, params) -//assert.Equal(t, startBondedPool+expProvisions, p.BondedPool) -//assert.Equal(t, startTotalSupply+expProvisions, p.TotalSupply) -//} -//assert.NotEqual(t, initialSupply, p.TotalSupply) -//assert.Equal(t, initialUnbonded, p.UnbondedPool) -////panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, p.TotalSupply-gs.BondedPool)) + // process the provisions a year + for hr := 0; hr < 8766; hr++ { + pool := keeper.GetPool(ctx) + expInflation := keeper.nextInflation(ctx).Round(1000000000) + expProvisions := (expInflation.Mul(sdk.NewRat(pool.TotalSupply)).Quo(hrsPerYrRat)).Evaluate() + startBondedPool := pool.BondedPool + startTotalSupply := pool.TotalSupply + pool = keeper.processProvisions(ctx) + keeper.setPool(ctx, pool) + //fmt.Printf("hr %v, startBondedPool %v, expProvisions %v, pool.BondedPool %v\n", hr, startBondedPool, expProvisions, pool.BondedPool) + require.Equal(t, startBondedPool+expProvisions, pool.BondedPool, "hr %v", hr) + require.Equal(t, startTotalSupply+expProvisions, pool.TotalSupply) + } + pool = keeper.GetPool(ctx) + assert.NotEqual(t, initialSupply, pool.TotalSupply) + assert.Equal(t, initialUnbonded, pool.UnbondedPool) + //panic(fmt.Sprintf("debug total %v, bonded %v, diff %v\n", p.TotalSupply, p.BondedPool, pool.TotalSupply-pool.BondedPool)) -//// initial bonded ratio ~ 35% ~ 30% increase for bonded holders -//assert.True(t, p.bondedRatio().Equal(sdk.NewRat(105906511, 305906511)), "%v", p.bondedRatio()) + // initial bonded ratio ~ from 27% to 40% increase for bonded holders ownership of total supply + assert.True(t, pool.bondedRatio().Equal(sdk.NewRat(271734723, 671734723)), "%v", pool.bondedRatio()) -//// global supply -//assert.Equal(t, int64(611813022), p.TotalSupply) -//assert.Equal(t, int64(211813022), p.BondedPool) -//assert.Equal(t, unbondedShares, p.UnbondedPool) + // global supply + assert.Equal(t, int64(671734723), pool.TotalSupply) + assert.Equal(t, int64(271734723), pool.BondedPool) + assert.Equal(t, unbondedShares, pool.UnbondedPool) -//// test the value of candidate shares -//assert.True(t, p.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(211813022)), "%v", p.bondedShareExRate()) + // test the value of candidate shares + assert.True(t, pool.bondedShareExRate().Mul(sdk.NewRat(bondedShares)).Equal(sdk.NewRat(271734723)), "%v", pool.bondedShareExRate()) -//} +} diff --git a/x/stake/types.go b/x/stake/types.go index 2799e1d76..4ba7c59d0 100644 --- a/x/stake/types.go +++ b/x/stake/types.go @@ -42,8 +42,6 @@ type Pool struct { Inflation sdk.Rat `json:"inflation"` // current annual inflation rate } -// XXX define globalstate interface? - func initialPool() Pool { return Pool{ TotalSupply: 0, @@ -56,30 +54,6 @@ func initialPool() Pool { } } -// get the bond ratio of the global state -func (p Pool) bondedRatio() sdk.Rat { - if p.TotalSupply > 0 { - return sdk.NewRat(p.BondedPool, p.TotalSupply) - } - return sdk.ZeroRat -} - -// get the exchange rate of bonded token per issued share -func (p Pool) bondedShareExRate() sdk.Rat { - if p.BondedShares.IsZero() { - return sdk.OneRat - } - return sdk.NewRat(p.BondedPool).Quo(p.BondedShares) -} - -// get the exchange rate of unbonded tokens held in candidates per issued share -func (p Pool) unbondedShareExRate() sdk.Rat { - if p.UnbondedShares.IsZero() { - return sdk.OneRat - } - return sdk.NewRat(p.UnbondedPool).Quo(p.UnbondedShares) -} - //_______________________________________________________________________________________________________ // CandidateStatus - status of a validator-candidate