From 077d99dbc29c5adedd26beb7c2b623ea8fe8ebdc Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Fri, 12 Jun 2020 14:53:10 +0200 Subject: [PATCH 1/3] Initial fuzz test --- go.mod | 1 + x/wasm/internal/keeper/genesis_test.go | 88 ++++++++++++++++++++++++++ x/wasm/internal/keeper/test_fuzz.go | 34 ++++++++++ 3 files changed, 123 insertions(+) create mode 100644 x/wasm/internal/keeper/genesis_test.go create mode 100644 x/wasm/internal/keeper/test_fuzz.go diff --git a/go.mod b/go.mod index f89ecc1c47..351b1e4177 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a // indirect github.com/cosmos/cosmos-sdk v0.38.3 github.com/golang/mock v1.4.3 // indirect + github.com/google/gofuzz v1.0.0 github.com/gorilla/mux v1.7.4 github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/gomega v1.5.0 // indirect diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go new file mode 100644 index 0000000000..67694d9564 --- /dev/null +++ b/x/wasm/internal/keeper/genesis_test.go @@ -0,0 +1,88 @@ +package keeper + +import ( + "io/ioutil" + "os" + "testing" + "time" + + "github.com/CosmWasm/wasmd/x/wasm/internal/types" + wasmTypes "github.com/CosmWasm/wasmd/x/wasm/internal/types" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/staking" + fuzz "github.com/google/gofuzz" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" +) + +func TestGenesisExportImport(t *testing.T) { + srcKeeper, srcCtx := setupKeeper(t) + wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") + require.NoError(t, err) + + f := fuzz.New().Funcs(FuzzAddr, FuzzAbsoluteTxPosition, FuzzContractInfo, FuzzStateModel) + for i := 0; i < 20; i++ { + var ( + codeInfo types.CodeInfo + contract types.ContractInfo + stateModels []types.Model + ) + f.Fuzz(&codeInfo) + f.Fuzz(&contract) + f.Fuzz(&stateModels) + + codeID, err := srcKeeper.Create(srcCtx, codeInfo.Creator, wasmCode, codeInfo.Source, codeInfo.Builder) + require.NoError(t, err) + contract.CodeID = codeID + contractAddr := srcKeeper.generateContractAddress(srcCtx, codeID) + srcKeeper.setContractInfo(srcCtx, contractAddr, &contract) + srcKeeper.setContractState(srcCtx, contractAddr, stateModels) + } + + // export + genesisState := ExportGenesis(srcCtx, srcKeeper) + + // re-import + dstKeeper, dstCtx := setupKeeper(t) + InitGenesis(dstCtx, dstKeeper, genesisState) + + // compare whole DB + srcIT := srcCtx.KVStore(srcKeeper.storeKey).Iterator(nil, nil) + dstIT := dstCtx.KVStore(dstKeeper.storeKey).Iterator(nil, nil) + + for i := 0; srcIT.Valid(); i++ { + require.True(t, dstIT.Valid(), "destination DB has less elements than source. Missing: %q", srcIT.Key()) + require.Equal(t, srcIT.Key(), dstIT.Key(), i) + require.Equal(t, srcIT.Value(), dstIT.Value(), i) + srcIT.Next() + dstIT.Next() + } + require.False(t, dstIT.Valid()) +} + +func setupKeeper(t *testing.T) (Keeper, sdk.Context) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + t.Cleanup(func() { os.RemoveAll(tempDir) }) + + keyContract := sdk.NewKVStoreKey(wasmTypes.StoreKey) + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyContract, sdk.StoreTypeIAVL, db) + require.NoError(t, ms.LoadLatestVersion()) + + ctx := sdk.NewContext(ms, abci.Header{ + Height: 1234567, + Time: time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC), + }, false, log.NewNopLogger()) + + cdc := MakeTestCodec() + wasmConfig := wasmTypes.DefaultWasmConfig() + + srcKeeper := NewKeeper(cdc, keyContract, auth.AccountKeeper{}, nil, staking.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil) + return srcKeeper, ctx +} diff --git a/x/wasm/internal/keeper/test_fuzz.go b/x/wasm/internal/keeper/test_fuzz.go new file mode 100644 index 0000000000..689ef29d7f --- /dev/null +++ b/x/wasm/internal/keeper/test_fuzz.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "github.com/CosmWasm/wasmd/x/wasm/internal/types" + sdk "github.com/cosmos/cosmos-sdk/types" + fuzz "github.com/google/gofuzz" + tmBytes "github.com/tendermint/tendermint/libs/bytes" +) + +func FuzzAddr(m *sdk.AccAddress, c fuzz.Continue) { + *m = make([]byte, 20) + c.Read(*m) +} +func FuzzAbsoluteTxPosition(m *types.AbsoluteTxPosition, c fuzz.Continue) { + m.BlockHeight = int64(c.RandUint64()) // can't be negative + m.TxIndex = c.RandUint64() +} + +func FuzzContractInfo(m *types.ContractInfo, c fuzz.Continue) { + const maxSize = 1024 + m.CodeID = c.RandUint64() + FuzzAddr(&m.Creator, c) + FuzzAddr(&m.Admin, c) + m.Label = c.RandString() + m.InitMsg = make([]byte, c.RandUint64()%maxSize) + c.Read(m.InitMsg) + c.Fuzz(&m.Created) + c.Fuzz(&m.LastUpdated) + m.PreviousCodeID = c.RandUint64() +} +func FuzzStateModel(m *types.Model, c fuzz.Continue) { + m.Key = tmBytes.HexBytes(c.RandString()) + c.Fuzz(&m.Value) +} From f71a9bbb13b70d2e63cd6511e558ad5279ad40d3 Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 29 Jun 2020 16:49:45 +0200 Subject: [PATCH 2/3] Ex/Import missing LastInstanceID key --- x/wasm/internal/keeper/genesis.go | 11 +++++++++++ x/wasm/internal/keeper/genesis_test.go | 5 +++-- x/wasm/internal/keeper/keeper.go | 17 +++++++++++++++++ x/wasm/internal/types/genesis.go | 6 ++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/x/wasm/internal/keeper/genesis.go b/x/wasm/internal/keeper/genesis.go index d3341de5f1..96e39022b8 100644 --- a/x/wasm/internal/keeper/genesis.go +++ b/x/wasm/internal/keeper/genesis.go @@ -29,6 +29,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { keeper.setContractState(ctx, contract.ContractAddress, contract.ContractState) } + for _, seq := range data.Sequences { + keeper.setAutoIncrementID(ctx, seq.IDKey, seq.Value) + } } // ExportGenesis returns a GenesisState for a given context and keeper. @@ -67,5 +70,13 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { return false }) + // types.KeyLastCodeID is updated via keeper create + for _, k := range [][]byte{types.KeyLastInstanceID} { + genState.Sequences = append(genState.Sequences, types.Sequence{ + IDKey: k, + Value: keeper.peekAutoIncrementID(ctx, k), + }) + } + return genState } diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index 67694d9564..9e2fb3bd0c 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -24,8 +24,9 @@ func TestGenesisExportImport(t *testing.T) { wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") require.NoError(t, err) + // store some test data f := fuzz.New().Funcs(FuzzAddr, FuzzAbsoluteTxPosition, FuzzContractInfo, FuzzStateModel) - for i := 0; i < 20; i++ { + for i := 0; i < 25; i++ { var ( codeInfo types.CodeInfo contract types.ContractInfo @@ -57,7 +58,7 @@ func TestGenesisExportImport(t *testing.T) { for i := 0; srcIT.Valid(); i++ { require.True(t, dstIT.Valid(), "destination DB has less elements than source. Missing: %q", srcIT.Key()) require.Equal(t, srcIT.Key(), dstIT.Key(), i) - require.Equal(t, srcIT.Value(), dstIT.Value(), i) + require.Equal(t, srcIT.Value(), dstIT.Value(), "element (%d): %s", i, srcIT.Key()) srcIT.Next() dstIT.Next() } diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 69e278667b..b2b9756043 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -485,6 +485,23 @@ func (k Keeper) autoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { return id } +// peekAutoIncrementID reads the current value without incrementing it. +func (k Keeper) peekAutoIncrementID(ctx sdk.Context, lastIDKey []byte) uint64 { + store := ctx.KVStore(k.storeKey) + bz := store.Get(lastIDKey) + id := uint64(1) + if bz != nil { + id = binary.BigEndian.Uint64(bz) + } + return id +} + +func (k Keeper) setAutoIncrementID(ctx sdk.Context, lastIDKey []byte, val uint64) { + store := ctx.KVStore(k.storeKey) + bz := sdk.Uint64ToBigEndian(val) + store.Set(lastIDKey, bz) +} + func addrFromUint64(id uint64) sdk.AccAddress { addr := make([]byte, 20) addr[0] = 'C' diff --git a/x/wasm/internal/types/genesis.go b/x/wasm/internal/types/genesis.go index 12ee39597f..1a36fff6ec 100644 --- a/x/wasm/internal/types/genesis.go +++ b/x/wasm/internal/types/genesis.go @@ -2,10 +2,16 @@ package types import sdk "github.com/cosmos/cosmos-sdk/types" +type Sequence struct { + IDKey []byte `json:"id_key"` + Value uint64 `json:"value"` +} + // GenesisState is the struct representation of the export genesis type GenesisState struct { Codes []Code `json:"codes"` Contracts []Contract `json:"contracts"` + Sequences []Sequence `json:"sequences"` } // Code struct encompasses CodeInfo and CodeBytes From 005c8bfe0a6c636423e4b384d248daffeceaa9be Mon Sep 17 00:00:00 2001 From: Alex Peters Date: Mon, 29 Jun 2020 18:07:03 +0200 Subject: [PATCH 3/3] Go 1.13 backport --- x/wasm/internal/keeper/genesis_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index 9e2fb3bd0c..88963c4af7 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -20,7 +20,8 @@ import ( ) func TestGenesisExportImport(t *testing.T) { - srcKeeper, srcCtx := setupKeeper(t) + srcKeeper, srcCtx, srcCleanup := setupKeeper(t) + defer srcCleanup() wasmCode, err := ioutil.ReadFile("./testdata/contract.wasm") require.NoError(t, err) @@ -48,7 +49,8 @@ func TestGenesisExportImport(t *testing.T) { genesisState := ExportGenesis(srcCtx, srcKeeper) // re-import - dstKeeper, dstCtx := setupKeeper(t) + dstKeeper, dstCtx, dstCleanup := setupKeeper(t) + defer dstCleanup() InitGenesis(dstCtx, dstKeeper, genesisState) // compare whole DB @@ -65,10 +67,11 @@ func TestGenesisExportImport(t *testing.T) { require.False(t, dstIT.Valid()) } -func setupKeeper(t *testing.T) (Keeper, sdk.Context) { +func setupKeeper(t *testing.T) (Keeper, sdk.Context, func()) { tempDir, err := ioutil.TempDir("", "wasm") require.NoError(t, err) - t.Cleanup(func() { os.RemoveAll(tempDir) }) + cleanup := func() { os.RemoveAll(tempDir) } + //t.Cleanup(cleanup) todo: add with Go 1.14 keyContract := sdk.NewKVStoreKey(wasmTypes.StoreKey) db := dbm.NewMemDB() @@ -85,5 +88,5 @@ func setupKeeper(t *testing.T) (Keeper, sdk.Context) { wasmConfig := wasmTypes.DefaultWasmConfig() srcKeeper := NewKeeper(cdc, keyContract, auth.AccountKeeper{}, nil, staking.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil) - return srcKeeper, ctx + return srcKeeper, ctx, cleanup }