Refactor bank tests
This commit is contained in:
parent
ad410c1e2e
commit
8bd54f0701
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
stake "github.com/cosmos/cosmos-sdk/x/stake"
|
||||
stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation"
|
||||
|
@ -85,6 +86,7 @@ func TestFullGaiaSimulation(t *testing.T) {
|
|||
simulation.SimulateFromSeed(
|
||||
t, app.BaseApp, appStateFn, seed,
|
||||
[]simulation.TestAndRunTx{
|
||||
banksim.TestAndRunSingleInputMsgSend(app.accountMapper),
|
||||
stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper),
|
||||
stakesim.SimulateMsgEditValidator(app.stakeKeeper),
|
||||
stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper),
|
||||
|
@ -95,6 +97,7 @@ func TestFullGaiaSimulation(t *testing.T) {
|
|||
},
|
||||
[]simulation.RandSetup{},
|
||||
[]simulation.Invariant{
|
||||
banksim.NonnegativeBalanceInvariant(app.accountMapper),
|
||||
stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper),
|
||||
},
|
||||
NumKeys,
|
||||
|
|
|
@ -83,21 +83,6 @@ func getMockApp(t *testing.T) *mock.App {
|
|||
return mapp
|
||||
}
|
||||
|
||||
func TestBankWithRandomMessages(t *testing.T) {
|
||||
mapp := getMockApp(t)
|
||||
setup := func(r *rand.Rand, keys []crypto.PrivKey) {
|
||||
return
|
||||
}
|
||||
|
||||
mapp.RandomizedTesting(
|
||||
t,
|
||||
[]mock.TestAndRunTx{TestAndRunSingleInputMsgSend},
|
||||
[]mock.RandSetup{setup},
|
||||
[]mock.Invariant{ModuleInvariants},
|
||||
100, 30, 30,
|
||||
)
|
||||
}
|
||||
|
||||
func TestMsgSendWithAccounts(t *testing.T) {
|
||||
mapp := getMockApp(t)
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
|
||||
func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant {
|
||||
return func(t *testing.T, app *baseapp.BaseApp, log string) {
|
||||
ctx := app.NewContext(false, abci.Header{})
|
||||
accts := mock.GetAllAccounts(mapper, ctx)
|
||||
for _, acc := range accts {
|
||||
coins := acc.GetCoins()
|
||||
require.True(t, coins.IsNotNegative(),
|
||||
fmt.Sprintf("%s has a negative denomination of %s\n%s",
|
||||
acc.GetAddress().String(),
|
||||
coins.String(),
|
||||
log),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TotalCoinsInvariant checks that the sum of the coins across all accounts
|
||||
// is what is expected
|
||||
func TotalCoinsInvariant(mapper auth.AccountMapper, totalSupplyFn func() sdk.Coins) simulation.Invariant {
|
||||
return func(t *testing.T, app *baseapp.BaseApp, log string) {
|
||||
ctx := app.NewContext(false, abci.Header{})
|
||||
totalCoins := sdk.Coins{}
|
||||
|
||||
chkAccount := func(acc auth.Account) bool {
|
||||
coins := acc.GetCoins()
|
||||
totalCoins = totalCoins.Plus(coins)
|
||||
return false
|
||||
}
|
||||
|
||||
mapper.IterateAccounts(ctx, chkAccount)
|
||||
require.Equal(t, totalSupplyFn(), totalCoins, log)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
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/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both
|
||||
// accounts already exist.
|
||||
func TestAndRunSingleInputMsgSend(mapper auth.AccountMapper) simulation.TestAndRunTx {
|
||||
return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) {
|
||||
fromKey := keys[r.Intn(len(keys))]
|
||||
fromAddr := sdk.AccAddress(fromKey.PubKey().Address())
|
||||
toKey := keys[r.Intn(len(keys))]
|
||||
// Disallow sending money to yourself
|
||||
for {
|
||||
if !fromKey.Equals(toKey) {
|
||||
break
|
||||
}
|
||||
toKey = keys[r.Intn(len(keys))]
|
||||
}
|
||||
toAddr := sdk.AccAddress(toKey.PubKey().Address())
|
||||
initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins()
|
||||
|
||||
denomIndex := r.Intn(len(initFromCoins))
|
||||
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
|
||||
if goErr != nil {
|
||||
return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil
|
||||
}
|
||||
|
||||
action = fmt.Sprintf("%s is sending %s %s to %s",
|
||||
fromAddr.String(),
|
||||
amt.String(),
|
||||
initFromCoins[denomIndex].Denom,
|
||||
toAddr.String(),
|
||||
)
|
||||
log = fmt.Sprintf("%s\n%s", log, action)
|
||||
|
||||
coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}}
|
||||
var msg = bank.MsgSend{
|
||||
Inputs: []bank.Input{bank.NewInput(fromAddr, coins)},
|
||||
Outputs: []bank.Output{bank.NewOutput(toAddr, coins)},
|
||||
}
|
||||
sendAndVerifyMsgSend(t, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey})
|
||||
event("bank/sendAndVerifyMsgSend/ok")
|
||||
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs
|
||||
func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) {
|
||||
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
|
||||
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
|
||||
AccountNumbers := make([]int64, len(msg.Inputs))
|
||||
SequenceNumbers := make([]int64, len(msg.Inputs))
|
||||
|
||||
for i := 0; i < len(msg.Inputs); i++ {
|
||||
acc := mapper.GetAccount(ctx, msg.Inputs[i].Address)
|
||||
AccountNumbers[i] = acc.GetAccountNumber()
|
||||
SequenceNumbers[i] = acc.GetSequence()
|
||||
initialInputAddrCoins[i] = acc.GetCoins()
|
||||
}
|
||||
for i := 0; i < len(msg.Outputs); i++ {
|
||||
acc := mapper.GetAccount(ctx, msg.Outputs[i].Address)
|
||||
initialOutputAddrCoins[i] = acc.GetCoins()
|
||||
}
|
||||
tx := mock.GenTx([]sdk.Msg{msg},
|
||||
AccountNumbers,
|
||||
SequenceNumbers,
|
||||
privkeys...)
|
||||
res := app.Deliver(tx)
|
||||
if !res.IsOK() {
|
||||
// TODO: Do this in a more 'canonical' way
|
||||
fmt.Println(res)
|
||||
fmt.Println(log)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
for i := 0; i < len(msg.Inputs); i++ {
|
||||
terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
|
||||
require.Equal(t,
|
||||
initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins),
|
||||
terminalInputCoins,
|
||||
fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log),
|
||||
)
|
||||
}
|
||||
for i := 0; i < len(msg.Outputs); i++ {
|
||||
terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
|
||||
require.Equal(t,
|
||||
initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins),
|
||||
terminalOutputCoins,
|
||||
fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
|
||||
if !max.GT(sdk.OneInt()) {
|
||||
return sdk.Int{}, errors.New("max too small")
|
||||
}
|
||||
max = max.Sub(sdk.OneInt())
|
||||
return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
|
||||
)
|
||||
|
||||
func TestBankWithRandomMessages(t *testing.T) {
|
||||
mapp := mock.NewApp()
|
||||
|
||||
bank.RegisterWire(mapp.Cdc)
|
||||
mapper := mapp.AccountMapper
|
||||
coinKeeper := bank.NewKeeper(mapper)
|
||||
mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper))
|
||||
|
||||
err := mapp.CompleteSetup([]*sdk.KVStoreKey{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage {
|
||||
mock.RandomSetGenesis(r, mapp, accs, []string{"stake"})
|
||||
return json.RawMessage("{}")
|
||||
}
|
||||
|
||||
simulation.Simulate(
|
||||
t, mapp.BaseApp, appStateFn,
|
||||
[]simulation.TestAndRunTx{
|
||||
TestAndRunSingleInputMsgSend(mapper),
|
||||
},
|
||||
[]simulation.RandSetup{},
|
||||
[]simulation.Invariant{
|
||||
NonnegativeBalanceInvariant(mapper),
|
||||
TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }),
|
||||
},
|
||||
100, 30, 30,
|
||||
)
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
package bank
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
)
|
||||
|
||||
// ModuleInvariants runs all invariants of the bank module.
|
||||
// Currently runs non-negative balance invariant and TotalCoinsInvariant
|
||||
func ModuleInvariants(t *testing.T, app *mock.App, log string) {
|
||||
NonnegativeBalanceInvariant(t, app, log)
|
||||
TotalCoinsInvariant(t, app, log)
|
||||
}
|
||||
|
||||
// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
|
||||
func NonnegativeBalanceInvariant(t *testing.T, app *mock.App, log string) {
|
||||
ctx := app.NewContext(false, abci.Header{})
|
||||
accts := mock.GetAllAccounts(app.AccountMapper, ctx)
|
||||
for _, acc := range accts {
|
||||
coins := acc.GetCoins()
|
||||
assert.True(t, coins.IsNotNegative(),
|
||||
fmt.Sprintf("%s has a negative denomination of %s\n%s",
|
||||
acc.GetAddress().String(),
|
||||
coins.String(),
|
||||
log),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TotalCoinsInvariant checks that the sum of the coins across all accounts
|
||||
// is what is expected
|
||||
func TotalCoinsInvariant(t *testing.T, app *mock.App, log string) {
|
||||
ctx := app.BaseApp.NewContext(false, abci.Header{})
|
||||
totalCoins := sdk.Coins{}
|
||||
|
||||
chkAccount := func(acc auth.Account) bool {
|
||||
coins := acc.GetCoins()
|
||||
totalCoins = totalCoins.Plus(coins)
|
||||
return false
|
||||
}
|
||||
|
||||
app.AccountMapper.IterateAccounts(ctx, chkAccount)
|
||||
require.Equal(t, app.TotalCoinsSupply, totalCoins, log)
|
||||
}
|
||||
|
||||
// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both
|
||||
// accounts already exist.
|
||||
func TestAndRunSingleInputMsgSend(t *testing.T, r *rand.Rand, app *mock.App, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
|
||||
fromKey := keys[r.Intn(len(keys))]
|
||||
fromAddr := sdk.AccAddress(fromKey.PubKey().Address())
|
||||
toKey := keys[r.Intn(len(keys))]
|
||||
// Disallow sending money to yourself
|
||||
for {
|
||||
if !fromKey.Equals(toKey) {
|
||||
break
|
||||
}
|
||||
toKey = keys[r.Intn(len(keys))]
|
||||
}
|
||||
toAddr := sdk.AccAddress(toKey.PubKey().Address())
|
||||
initFromCoins := app.AccountMapper.GetAccount(ctx, fromAddr).GetCoins()
|
||||
|
||||
denomIndex := r.Intn(len(initFromCoins))
|
||||
amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount)
|
||||
if goErr != nil {
|
||||
return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil
|
||||
}
|
||||
|
||||
action = fmt.Sprintf("%s is sending %s %s to %s",
|
||||
fromAddr.String(),
|
||||
amt.String(),
|
||||
initFromCoins[denomIndex].Denom,
|
||||
toAddr.String(),
|
||||
)
|
||||
log = fmt.Sprintf("%s\n%s", log, action)
|
||||
|
||||
coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}}
|
||||
var msg = MsgSend{
|
||||
Inputs: []Input{NewInput(fromAddr, coins)},
|
||||
Outputs: []Output{NewOutput(toAddr, coins)},
|
||||
}
|
||||
sendAndVerifyMsgSend(t, app, msg, ctx, log, []crypto.PrivKey{fromKey})
|
||||
|
||||
return action, nil
|
||||
}
|
||||
|
||||
// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs
|
||||
func sendAndVerifyMsgSend(t *testing.T, app *mock.App, msg MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) {
|
||||
initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs))
|
||||
initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs))
|
||||
AccountNumbers := make([]int64, len(msg.Inputs))
|
||||
SequenceNumbers := make([]int64, len(msg.Inputs))
|
||||
|
||||
for i := 0; i < len(msg.Inputs); i++ {
|
||||
acc := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address)
|
||||
AccountNumbers[i] = acc.GetAccountNumber()
|
||||
SequenceNumbers[i] = acc.GetSequence()
|
||||
initialInputAddrCoins[i] = acc.GetCoins()
|
||||
}
|
||||
for i := 0; i < len(msg.Outputs); i++ {
|
||||
acc := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address)
|
||||
initialOutputAddrCoins[i] = acc.GetCoins()
|
||||
}
|
||||
tx := mock.GenTx([]sdk.Msg{msg},
|
||||
AccountNumbers,
|
||||
SequenceNumbers,
|
||||
privkeys...)
|
||||
res := app.Deliver(tx)
|
||||
if !res.IsOK() {
|
||||
// TODO: Do this in a more 'canonical' way
|
||||
fmt.Println(res)
|
||||
fmt.Println(log)
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
for i := 0; i < len(msg.Inputs); i++ {
|
||||
terminalInputCoins := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins()
|
||||
require.Equal(t,
|
||||
initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins),
|
||||
terminalInputCoins,
|
||||
fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log),
|
||||
)
|
||||
}
|
||||
for i := 0; i < len(msg.Outputs); i++ {
|
||||
terminalOutputCoins := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins()
|
||||
require.Equal(t,
|
||||
initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins),
|
||||
terminalOutputCoins,
|
||||
fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) {
|
||||
if !max.GT(sdk.OneInt()) {
|
||||
return sdk.Int{}, errors.New("max too small")
|
||||
}
|
||||
max = max.Sub(sdk.OneInt())
|
||||
return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil
|
||||
}
|
Loading…
Reference in New Issue