151 lines
4.9 KiB
Go
151 lines
4.9 KiB
Go
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 := 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 := 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
|
|
}
|