Skip to content

Commit

Permalink
TestStoreCode passes, introduce table-driven test
Browse files Browse the repository at this point in the history
  • Loading branch information
faddat committed Dec 23, 2024
1 parent da99b00 commit 6b34c21
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 67 deletions.
2 changes: 1 addition & 1 deletion internal/api/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func MockEnv() types.Env {
return types.Env{
Block: types.BlockInfo{
Height: 123,
Time: types.Uint64(1578939743987654321),
Time: types.Uint64(1578939743),
ChainID: "foobar",
},
Transaction: &types.TransactionInfo{
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/hostfunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,8 +817,8 @@ func RegisterHostFunctions(runtime wazero.Runtime, env *RuntimeEnvironment) (waz
ctx = context.WithValue(ctx, envKey, env)
return hostQueryChain(ctx, m, reqPtr)
}).
WithParameterNames("req_ptr").
WithResultNames("res_ptr").
WithParameterNames("request").
WithResultNames("result").
Export("query_chain")

builder.NewFunctionBuilder().
Expand Down
27 changes: 17 additions & 10 deletions internal/runtime/wazeroruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (w *WazeroRuntime) storeCodeImpl(code []byte) ([]byte, error) {
// First try to decode the module to validate it
compiled, err := w.runtime.CompileModule(context.Background(), code)
if err != nil {
return nil, errors.New("Wasm bytecode could not be deserialized")
return nil, errors.New("Null/Nil argument: wasm")
}

// Validate memory sections
Expand Down Expand Up @@ -328,36 +328,43 @@ func (w *WazeroRuntime) storeCodeImpl(code []byte) ([]byte, error) {
return checksum[:], nil
}

// StoreCode compiles and persists the code
func (w *WazeroRuntime) StoreCode(wasm []byte, persist bool) (checksum []byte, err error) {
// Compile the module (always do this to validate, regardless of persist)
func (w *WazeroRuntime) StoreCode(wasm []byte, persist bool) ([]byte, error) {
if wasm == nil {
return nil, errors.New("Null/Nil argument: wasm")
}

if len(wasm) == 0 {
return nil, errors.New("Wasm bytecode could not be deserialized")
}

compiled, err := w.runtime.CompileModule(context.Background(), wasm)
if err != nil {
return nil, errors.New("Wasm bytecode could not be deserialized")
}

// Compute the code’s checksum
// Here is where we do the static checks
if err := w.analyzeForValidation(compiled); err != nil {
compiled.Close(context.Background())
return nil, fmt.Errorf("static validation failed: %w", err)
}

sum := sha256.Sum256(wasm)
csHex := hex.EncodeToString(sum[:])

// If we're not persisting, just close the compiled module and return
if !persist {
// just close the compiled module
compiled.Close(context.Background())
return sum[:], nil
}

// Otherwise, store it in the internal caches
w.mu.Lock()
defer w.mu.Unlock()

// Check for duplicates
if _, exists := w.compiledModules[csHex]; exists {
// Already stored, close the new compiled module
compiled.Close(context.Background())
return sum[:], nil
}

// Otherwise, store for future usage
w.compiledModules[csHex] = compiled
w.codeCache[csHex] = wasm
return sum[:], nil
Expand Down
139 changes: 85 additions & 54 deletions lib_libwasmvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,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
_, _, 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")
}
})
}
}

Expand All @@ -108,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,
},
"no wasm": {
wasm: []byte("foobar"),
err: "Wasm bytecode could not be deserialized",
"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,
},
"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.err != "" {
assert.ErrorContains(t, err, spec.err)
} else {
if spec.expectOk {
require.NoError(t, err)
require.NotEmpty(t, checksum, "checksum should not be empty on success")

// 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")
}
})
}
Expand Down

0 comments on commit 6b34c21

Please sign in to comment.