diff --git a/tests/mock/app.go b/tests/mock/app.go new file mode 100644 index 000000000..f7b7a0a34 --- /dev/null +++ b/tests/mock/app.go @@ -0,0 +1,85 @@ +package mock + +import ( + "testing" + + "os" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/abci/types" + 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" +) + +// Extended ABCI application +type App struct { + *bam.BaseApp + Cdc *wire.Codec // public since the codec is passed into the module anyways. + KeyMain *sdk.KVStoreKey + KeyAccount *sdk.KVStoreKey + + // TODO: Abstract this out from not needing to be auth specifically + AccountMapper auth.AccountMapper + FeeCollectionKeeper auth.FeeCollectionKeeper + + GenesisAccounts []auth.Account +} + +// NewApp is used for testing the server. For the internal mock app stuff, it uses code in helpers.go +func NewApp() *App { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + + // create the cdc with some standard codecs + cdc := wire.NewCodec() + sdk.RegisterWire(cdc) + wire.RegisterCrypto(cdc) + + // create your application object + app := &App{ + BaseApp: bam.NewBaseApp("mock", cdc, logger, db), + Cdc: cdc, + KeyMain: sdk.NewKVStoreKey("main"), + KeyAccount: sdk.NewKVStoreKey("acc"), + } + + // define the accountMapper + app.AccountMapper = auth.NewAccountMapper( + app.Cdc, + app.KeyAccount, // target store + &auth.BaseAccount{}, // prototype + ) + + // initialize the app, the chainers and blockers can be overwritten before calling complete setup + app.SetInitChainer(app.initChainer) + + app.SetAnteHandler(auth.NewAnteHandler(app.AccountMapper, app.FeeCollectionKeeper)) + + return app +} + +// complete the application setup after the routes have been registered +func (app App) CompleteSetup(t *testing.T, newKeys []*sdk.KVStoreKey) { + + newKeys = append(newKeys, app.KeyMain) + newKeys = append(newKeys, app.KeyAccount) + app.MountStoresIAVL(newKeys...) + err := app.LoadLatestVersion(app.KeyMain) + require.NoError(t, err) +} + +// custom logic for initialization +func (app App) initChainer(ctx sdk.Context, _ abci.RequestInitChain) abci.ResponseInitChain { + + // load the accounts + for _, acc := range app.GenesisAccounts { + app.AccountMapper.SetAccount(ctx, acc) + } + + return abci.ResponseInitChain{} +} diff --git a/tests/mock/simulate_block.go b/tests/mock/simulate_block.go new file mode 100644 index 000000000..fa1000af7 --- /dev/null +++ b/tests/mock/simulate_block.go @@ -0,0 +1,100 @@ +package mock + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + crypto "github.com/tendermint/go-crypto" + + abci "github.com/tendermint/abci/types" +) + +var chainID = "" // TODO + +// set the mock app genesis +func SetGenesis(app *App, accs []auth.Account) { + + // pass the accounts in via the application (lazy) instead of through RequestInitChain + app.GenesisAccounts = accs + + app.InitChain(abci.RequestInitChain{}) + app.Commit() +} + +// check an account balance +func CheckBalance(t *testing.T, app *App, addr sdk.Address, exp sdk.Coins) { + ctxDeliver := app.BaseApp.NewContext(false, abci.Header{}) + res := app.AccountMapper.GetAccount(ctxDeliver, addr) + assert.Equal(t, exp, res.GetCoins()) +} + +// generate a signed transaction +func GenTx(msg sdk.Msg, seq []int64, priv ...crypto.PrivKeyEd25519) auth.StdTx { + + // make the transaction free + fee := auth.StdFee{ + sdk.Coins{{"foocoin", 0}}, + 100000, + } + + sigs := make([]auth.StdSignature, len(priv)) + for i, p := range priv { + sigs[i] = auth.StdSignature{ + PubKey: p.PubKey(), + Signature: p.Sign(auth.StdSignBytes(chainID, seq, fee, msg)), + Sequence: seq[i], + } + } + return auth.NewStdTx(msg, fee, sigs) +} + +// simulate a block +func SignCheckDeliver(t *testing.T, app *App, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { + + // Sign the tx + tx := GenTx(msg, seq, priv...) + + // Run a Check + res := app.Check(tx) + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + + // Simulate a Block + app.BeginBlock(abci.RequestBeginBlock{}) + res = app.Deliver(tx) + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + app.EndBlock(abci.RequestEndBlock{}) + + // XXX fix code or add explaination as to why using commit breaks a bunch of these tests + //app.Commit() +} + +// XXX the only reason we are using Sign Deliver here is because the tests +// break on check tx the second time you use SignCheckDeliver in a test because +// the checktx state has not been updated likely because commit is not being +// called! +func SignDeliver(t *testing.T, app App, msg sdk.Msg, seq []int64, expPass bool, priv ...crypto.PrivKeyEd25519) { + + // Sign the tx + tx := GenTx(msg, seq, priv...) + + // Simulate a Block + app.BeginBlock(abci.RequestBeginBlock{}) + res := app.Deliver(tx) + if expPass { + require.Equal(t, sdk.ABCICodeOK, res.Code, res.Log) + } else { + require.NotEqual(t, sdk.ABCICodeOK, res.Code, res.Log) + } + app.EndBlock(abci.RequestEndBlock{}) +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go new file mode 100644 index 000000000..3dcf37c22 --- /dev/null +++ b/x/bank/app_test.go @@ -0,0 +1,96 @@ +package bank + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/tests/mock" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + + abci "github.com/tendermint/abci/types" + crypto "github.com/tendermint/go-crypto" +) + +// test bank module in a mock application +var ( + chainID = "" // TODO + + 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}} + manyCoins = sdk.Coins{{"foocoin", 1}, {"barcoin", 1}} + fee = auth.StdFee{ + sdk.Coins{{"foocoin", 0}}, + 100000, + } + + sendMsg1 = MsgSend{ + Inputs: []Input{NewInput(addr1, coins)}, + Outputs: []Output{NewOutput(addr2, coins)}, + } +) + +func TestMsgSendWithAccounts(t *testing.T) { + + // initialize the mock application + mapp := mock.NewApp() + + RegisterWire(mapp.Cdc) + coinKeeper := NewKeeper(mapp.AccountMapper) + mapp.Router().AddRoute("bank", NewHandler(coinKeeper)) + + mapp.CompleteSetup(t, []*sdk.KVStoreKey{}) + + // Add an account at genesis + coins, err := sdk.ParseCoins("77foocoin") + require.Nil(t, err) + //acc := auth.NewAccountWithAddress(addr1) + //acc.SetCoins(coins) + //accs := []auth.Account{acc} + + baseAcc := &auth.BaseAccount{ + Address: addr1, + Coins: coins, + } + baseAccs := []auth.Account{baseAcc} + + // Construct genesis state + mock.SetGenesis(mapp, baseAccs) + + // A checkTx context (true) + ctxCheck := mapp.BaseApp.NewContext(true, abci.Header{}) + res1 := mapp.AccountMapper.GetAccount(ctxCheck, addr1) + require.NotNil(t, res1) + assert.Equal(t, baseAcc, res1.(*auth.BaseAccount)) + + // Run a CheckDeliver + mock.SignCheckDeliver(t, mapp, sendMsg1, []int64{0}, true, priv1) + + // Check balances + mock.CheckBalance(t, mapp, addr1, sdk.Coins{{"foocoin", 67}}) + mock.CheckBalance(t, mapp, addr2, sdk.Coins{{"foocoin", 10}}) + + // Delivering again should cause replay error + mock.SignCheckDeliver(t, mapp, sendMsg1, []int64{0}, false, priv1) + + // bumping the txnonce number without resigning should be an auth error + tx := mock.GenTx(sendMsg1, []int64{0}, priv1) + tx.Signatures[0].Sequence = 1 + res := mapp.Deliver(tx) + + assert.Equal(t, sdk.ToABCICode(sdk.CodespaceRoot, sdk.CodeUnauthorized), res.Code, res.Log) + + // resigning the tx with the bumped sequence should work + mock.SignCheckDeliver(t, mapp, sendMsg1, []int64{1}, true, priv1) +}