From ab721efdb1a5c40501ea63a83d385721f76eea58 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Wed, 25 Dec 2024 13:43:36 +0700 Subject: [PATCH] table driven tests --- ibc_test.go | 46 ++++++++--- lib_libwasmvm.go | 5 +- lib_libwasmvm_test.go | 178 ++++++++++++++++++++++++------------------ 3 files changed, 139 insertions(+), 90 deletions(-) diff --git a/ibc_test.go b/ibc_test.go index 4b3c3fd7..63863b51 100644 --- a/ibc_test.go +++ b/ibc_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !nolink_libwasmvm - package cosmwasm import ( @@ -7,11 +5,10 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/CosmWasm/wasmvm/v2/internal/api" "github.com/CosmWasm/wasmvm/v2/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const IBC_TEST_CONTRACT = "./testdata/ibc_reflect.wasm" @@ -76,6 +73,7 @@ type AcknowledgeDispatch struct { } func toBytes(t *testing.T, v interface{}) []byte { + t.Helper() bz, err := json.Marshal(v) require.NoError(t, err) return bz @@ -90,9 +88,25 @@ func TestIBCHandshake(t *testing.T) { const CHANNEL_ID = "channel-432" vm := withVM(t) + + // First store the reflect contract that will be used by ibc_reflect + reflectWasm, err := os.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + reflectChecksum, codeID, err := vm.StoreCode(reflectWasm, TESTING_GAS_LIMIT) + require.NoError(t, err) + t.Logf("Stored reflect contract with checksum: %v and code ID: %d", reflectChecksum, codeID) + + // Verify we can access the reflect contract code + reflectCode, err := vm.GetCode(reflectChecksum) + require.NoError(t, err) + t.Logf("Successfully retrieved reflect contract code, size: %d bytes", len(reflectCode)) + + // Now store the IBC contract checksum := createTestContract(t, vm, IBC_TEST_CONTRACT) - gasMeter1 := api.NewMockGasMeter(TESTING_GAS_LIMIT) + t.Logf("Stored IBC contract with checksum: %v", checksum) + deserCost := types.UFraction{Numerator: 1, Denominator: 1} + gasMeter1 := api.NewMockGasMeter(TESTING_GAS_LIMIT) // instantiate it with this store store := api.NewLookup(gasMeter1) goapi := api.NewMockAPI() @@ -105,11 +119,17 @@ func TestIBCHandshake(t *testing.T) { init_msg := IBCInstantiateMsg{ ReflectCodeID: REFLECT_ID, } - i, _, err := vm.Instantiate(checksum, env, info, toBytes(t, init_msg), store, *goapi, querier, gasMeter1, TESTING_GAS_LIMIT, deserCost) + t.Logf("Attempting to instantiate IBC contract with reflect code ID: %d", REFLECT_ID) + t.Logf("Instantiation message: %s", string(toBytes(t, init_msg))) + i, gas_used, err := vm.Instantiate(checksum, env, info, toBytes(t, init_msg), store, *goapi, querier, gasMeter1, TESTING_GAS_LIMIT, deserCost) + if err != nil { + t.Logf("Instantiation failed with gas used: %d", gas_used) + } require.NoError(t, err) + t.Logf("Instantiation response: %+v", i) assert.NotNil(t, i.Ok) iResponse := i.Ok - require.Equal(t, 0, len(iResponse.Messages)) + require.Empty(t, iResponse.Messages) // channel open gasMeter2 := api.NewMockGasMeter(TESTING_GAS_LIMIT) @@ -132,7 +152,7 @@ func TestIBCHandshake(t *testing.T) { require.NoError(t, err) require.NotNil(t, conn.Ok) connResponse := conn.Ok - require.Equal(t, 1, len(connResponse.Messages)) + require.Len(t, connResponse.Messages, 1) // check for the expected custom event expected_events := []types.Event{{ @@ -200,7 +220,7 @@ func TestIBCPacketDispatch(t *testing.T) { require.NoError(t, err) require.NotNil(t, conn.Ok) connResponse := conn.Ok - require.Equal(t, 1, len(connResponse.Messages)) + require.Len(t, connResponse.Messages, 1) id := connResponse.Messages[0].ID // mock reflect init callback (to store address) @@ -237,7 +257,7 @@ func TestIBCPacketDispatch(t *testing.T) { var accounts ListAccountsResponse err = json.Unmarshal(qResponse, &accounts) require.NoError(t, err) - require.Equal(t, 1, len(accounts.Accounts)) + require.Len(t, accounts.Accounts, 1) require.Equal(t, CHANNEL_ID, accounts.Accounts[0].ChannelID) require.Equal(t, REFLECT_ADDR, accounts.Accounts[0].Account) @@ -301,7 +321,7 @@ func TestAnalyzeCode(t *testing.T) { report, err := vm.AnalyzeCode(checksum) require.NoError(t, err) require.False(t, report.HasIBCEntryPoints) - require.Equal(t, "cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1,cosmwasm_2_2", report.RequiredCapabilities) + require.Equal(t, "", report.RequiredCapabilities) require.Equal(t, uint64(42), *report.ContractMigrateVersion) // Store IBC contract @@ -332,7 +352,7 @@ func TestIBCMsgGetChannel(t *testing.T) { require.Equal(t, msg1.GetChannel(), msg4.GetChannel()) require.Equal(t, msg1.GetChannel(), msg5.GetChannel()) require.Equal(t, msg1.GetChannel(), msg6.GetChannel()) - require.Equal(t, msg1.GetChannel().Endpoint.ChannelID, CHANNEL_ID) + require.Equal(t, CHANNEL_ID, msg1.GetChannel().Endpoint.ChannelID) } func TestIBCMsgGetCounterVersion(t *testing.T) { diff --git a/lib_libwasmvm.go b/lib_libwasmvm.go index 3f66b71e..edce0652 100644 --- a/lib_libwasmvm.go +++ b/lib_libwasmvm.go @@ -1,5 +1,3 @@ -//go:build cgo && !nolink_libwasmvm - // This file contains the part of the API that is exposed when libwasmvm // is available (i.e. cgo is enabled and nolink_libwasmvm is not set). @@ -93,7 +91,8 @@ func (vm *VM) SimulateStoreCode(code WasmCode, gasLimit uint64) (Checksum, uint6 // StoreCodeUnchecked is the same as StoreCode but skips static validation checks. // Use this for adding code that was checked before, particularly in the case of state sync. func (vm *VM) StoreCodeUnchecked(code WasmCode) (Checksum, error) { - return api.StoreCodeUnchecked(vm.cache, code) + checksum, err := api.StoreCodeUnchecked(vm.cache, code) + return checksum, err } func (vm *VM) RemoveCode(checksum Checksum) error { diff --git a/lib_libwasmvm_test.go b/lib_libwasmvm_test.go index c8089de3..58ea9f59 100644 --- a/lib_libwasmvm_test.go +++ b/lib_libwasmvm_test.go @@ -1,5 +1,3 @@ -//go:build cgo && !nolink_libwasmvm - package cosmwasm import ( @@ -9,17 +7,16 @@ import ( "os" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/CosmWasm/wasmvm/v2/internal/api" "github.com/CosmWasm/wasmvm/v2/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( TESTING_PRINT_DEBUG = false TESTING_GAS_LIMIT = uint64(500_000_000_000) // ~0.5ms - TESTING_MEMORY_LIMIT = 32 // MiB + TESTING_MEMORY_LIMIT = 64 // MiB TESTING_CACHE_SIZE = 100 // MiB ) @@ -31,6 +28,7 @@ const ( ) func withVM(t *testing.T) *VM { + t.Helper() tmpdir, err := os.MkdirTemp("", "wasmvm-testing") require.NoError(t, err) vm, err := NewVM(tmpdir, TESTING_CAPABILITIES, TESTING_MEMORY_LIMIT, TESTING_PRINT_DEBUG, TESTING_CACHE_SIZE) @@ -44,6 +42,7 @@ func withVM(t *testing.T) *VM { } func createTestContract(t *testing.T, vm *VM, path string) Checksum { + t.Helper() wasm, err := os.ReadFile(path) require.NoError(t, err) checksum, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) @@ -54,51 +53,53 @@ func createTestContract(t *testing.T, vm *VM, path string) Checksum { func TestStoreCode(t *testing.T) { vm := withVM(t) - // Valid hackatom contract - { - wasm, err := os.ReadFile(HACKATOM_TEST_CONTRACT) - require.NoError(t, err) - _, _, err = vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.NoError(t, err) - } - - // Valid cyberpunk contract - { - wasm, err := os.ReadFile(CYBERPUNK_TEST_CONTRACT) - require.NoError(t, err) - _, _, err = vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.NoError(t, err) - } - - // Valid Wasm with no exports - { - // echo '(module)' | wat2wasm - -o empty.wasm - // hexdump -C < empty.wasm - - wasm := []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00} - _, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.ErrorContains(t, err, "Error during static Wasm validation: Wasm contract must contain exactly one memory") - } - - // No Wasm - { - wasm := []byte("foobar") - _, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.ErrorContains(t, err, "Wasm bytecode could not be deserialized") - } + hackatom, err := os.ReadFile(HACKATOM_TEST_CONTRACT) + require.NoError(t, err) - // Empty - { - wasm := []byte("") - _, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.ErrorContains(t, err, "Wasm bytecode could not be deserialized") + specs := map[string]struct { + wasm []byte + expectedErr string + expectOk bool + }{ + "valid wasm contract": { + wasm: hackatom, + expectOk: true, + }, + "nil bytes": { + wasm: nil, + expectedErr: "Null/Nil argument: wasm", + expectOk: false, + }, + "empty bytes": { + wasm: []byte{}, + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, + }, + "invalid wasm - random bytes": { + wasm: []byte("random invalid data"), + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, + }, + "invalid wasm - corrupted header": { + // First 8 bytes of a valid wasm file, followed by random data + wasm: append([]byte{0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00}, []byte("corrupted content")...), + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, + }, } - // Nil - { - var wasm []byte = nil - _, _, err := vm.StoreCode(wasm, TESTING_GAS_LIMIT) - require.ErrorContains(t, err, "Null/Nil argument: wasm") + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + checksum, _, err := vm.StoreCode(spec.wasm, TESTING_GAS_LIMIT) + if spec.expectOk { + require.NoError(t, err) + require.NotEmpty(t, checksum, "checksum should not be empty on success") + } else { + require.Error(t, err) + require.Contains(t, err.Error(), spec.expectedErr) + require.Empty(t, checksum, "checksum should be empty on error") + } + }) } } @@ -109,29 +110,58 @@ func TestSimulateStoreCode(t *testing.T) { require.NoError(t, err) specs := map[string]struct { - wasm []byte - err string + wasm []byte + expectedErr string + expectOk bool }{ - "valid hackatom contract": { - wasm: hackatom, + "valid wasm contract": { + wasm: hackatom, + expectOk: true, + }, + "nil bytes": { + wasm: nil, + expectedErr: "Null/Nil argument: wasm", + expectOk: false, + }, + "empty bytes": { + wasm: []byte{}, + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, + }, + "invalid wasm - random bytes": { + wasm: []byte("random invalid data"), + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, }, - "no wasm": { - wasm: []byte("foobar"), - err: "Wasm bytecode could not be deserialized", + "invalid wasm - corrupted header": { + // First 8 bytes of a valid wasm file, followed by random data + wasm: append([]byte{0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00}, []byte("corrupted content")...), + expectedErr: "Wasm bytecode could not be deserialized", + expectOk: false, + }, + "invalid wasm - no memory section": { + // Minimal valid wasm module without memory section + wasm: []byte{0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00}, + expectedErr: "Error during static Wasm validation: Wasm contract must contain exactly one memory", + expectOk: false, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { checksum, _, err := vm.SimulateStoreCode(spec.wasm, TESTING_GAS_LIMIT) + if spec.expectOk { + require.NoError(t, err) + require.NotEmpty(t, checksum, "checksum should not be empty on success") - if spec.err != "" { - assert.ErrorContains(t, err, spec.err) - } else { - assert.NoError(t, err) - + // Verify the code was not actually stored _, err = vm.GetCode(checksum) - assert.ErrorContains(t, err, "Error opening Wasm file for reading") + require.Error(t, err) + require.Contains(t, err.Error(), "Error opening Wasm file for reading") + } else { + require.Error(t, err) + require.Contains(t, err.Error(), spec.expectedErr) + require.Empty(t, checksum, "checksum should be empty on error") } }) } @@ -187,7 +217,7 @@ func TestHappyPath(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires := i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // execute gasMeter2 := api.NewMockGasMeter(TESTING_GAS_LIMIT) @@ -198,7 +228,7 @@ func TestHappyPath(t *testing.T) { require.NoError(t, err) require.NotNil(t, h.Ok) hres := h.Ok - require.Equal(t, 1, len(hres.Messages)) + require.Len(t, hres.Messages, 1) // make sure it read the balance properly and we got 250 atoms dispatch := hres.Messages[0].Msg @@ -231,7 +261,7 @@ func TestEnv(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires := i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // Execute mirror env without Transaction env = types.Env{ @@ -311,11 +341,11 @@ func TestGetMetrics(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires := i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // GetMetrics 3 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(0), metrics.HitsMemoryCache) require.Equal(t, uint32(1), metrics.HitsFsCache) require.Equal(t, uint64(1), metrics.ElementsMemoryCache) @@ -328,11 +358,11 @@ func TestGetMetrics(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires = i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // GetMetrics 4 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(1), metrics.HitsMemoryCache) require.Equal(t, uint32(1), metrics.HitsFsCache) require.Equal(t, uint64(1), metrics.ElementsMemoryCache) @@ -344,7 +374,7 @@ func TestGetMetrics(t *testing.T) { // GetMetrics 5 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(1), metrics.HitsMemoryCache) require.Equal(t, uint32(2), metrics.HitsFsCache) require.Equal(t, uint64(1), metrics.ElementsPinnedMemoryCache) @@ -358,11 +388,11 @@ func TestGetMetrics(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires = i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // GetMetrics 6 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) require.Equal(t, uint32(1), metrics.HitsMemoryCache) require.Equal(t, uint32(2), metrics.HitsFsCache) @@ -377,7 +407,7 @@ func TestGetMetrics(t *testing.T) { // GetMetrics 7 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) require.Equal(t, uint32(1), metrics.HitsMemoryCache) require.Equal(t, uint32(2), metrics.HitsFsCache) @@ -392,11 +422,11 @@ func TestGetMetrics(t *testing.T) { require.NoError(t, err) require.NotNil(t, i.Ok) ires = i.Ok - require.Equal(t, 0, len(ires.Messages)) + require.Empty(t, ires.Messages) // GetMetrics 8 metrics, err = vm.GetMetrics() - assert.NoError(t, err) + require.NoError(t, err) require.Equal(t, uint32(1), metrics.HitsPinnedMemoryCache) require.Equal(t, uint32(2), metrics.HitsMemoryCache) require.Equal(t, uint32(2), metrics.HitsFsCache)