package internal
import (
"context"
"encoding/binary"
"errors"
"sync"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
type Runtime struct {
runtime wazero.Runtime
module api.Module
alloc api.Function
dealloc api.Function
analyze api.Function
mu sync.Mutex
}
func NewRuntime(ctx context.Context, wasmBinary []byte) (*Runtime, error) {
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigCompiler())
module, err := r.Instantiate(ctx, wasmBinary)
if err != nil {
r.Close(ctx)
return nil, err
}
alloc := module.ExportedFunction("wasm_alloc")
if alloc == nil {
r.Close(ctx)
return nil, errors.New("wasm_alloc function not found in WASM module")
}
dealloc := module.ExportedFunction("wasm_dealloc")
if dealloc == nil {
r.Close(ctx)
return nil, errors.New("wasm_dealloc function not found in WASM module")
}
analyze := module.ExportedFunction("contract_info")
if analyze == nil {
r.Close(ctx)
return nil, errors.New("contract_info function not found in WASM module")
}
return &Runtime{
runtime: r,
module: module,
alloc: alloc,
dealloc: dealloc,
analyze: analyze,
}, nil
}
func (r *Runtime) Close(ctx context.Context) error {
return r.runtime.Close(ctx)
}
func (r *Runtime) ContractInfo(ctx context.Context, code []byte, opts uint32) ([]byte, error) {
if len(code) == 0 {
return nil, errors.New("empty bytecode")
}
r.mu.Lock()
defer r.mu.Unlock()
codePtrResult, err := r.alloc.Call(ctx, uint64(len(code)))
if err != nil {
return nil, err
}
if len(codePtrResult) == 0 || codePtrResult[0] == 0 {
return nil, errors.New("failed to allocate memory for bytecode")
}
codePtr := uint32(codePtrResult[0])
defer r.dealloc.Call(ctx, uint64(codePtr), uint64(len(code)))
if !r.module.Memory().Write(codePtr, code) {
return nil, errors.New("failed to write bytecode to WASM memory")
}
resultPtrArr, err := r.analyze.Call(ctx, uint64(codePtr), uint64(len(code)), uint64(opts))
if err != nil {
return nil, err
}
if len(resultPtrArr) == 0 || resultPtrArr[0] == 0 {
return nil, errors.New("contract_info returned null pointer")
}
resultPtr := uint32(resultPtrArr[0])
lenBytes, ok := r.module.Memory().Read(resultPtr, 4)
if !ok {
return nil, errors.New("failed to read result length from WASM memory")
}
resultLen := binary.LittleEndian.Uint32(lenBytes)
result, ok := r.module.Memory().Read(resultPtr+4, resultLen)
if !ok {
return nil, errors.New("failed to read result from WASM memory")
}
resultCopy := make([]byte, len(result))
copy(resultCopy, result)
r.dealloc.Call(ctx, uint64(resultPtr), uint64(resultLen)+4)
return resultCopy, nil
}