From fcf80bf1710af6484965b511078809a4d71e9460 Mon Sep 17 00:00:00 2001 From: Jacob Gadikian Date: Sat, 11 Jan 2025 20:59:47 +0700 Subject: [PATCH] improve logging --- internal/runtime/wazeroruntime.go | 579 +++++++++++++----------------- 1 file changed, 254 insertions(+), 325 deletions(-) diff --git a/internal/runtime/wazeroruntime.go b/internal/runtime/wazeroruntime.go index d42ac2cf7..441b0705a 100644 --- a/internal/runtime/wazeroruntime.go +++ b/internal/runtime/wazeroruntime.go @@ -71,9 +71,10 @@ type Region struct { // memoryManager handles WASM memory allocation and deallocation type memoryManager struct { - memory api.Memory - module api.Module - size uint32 + memory api.Memory + module api.Module + size uint32 + nextOffset uint32 // Track next available offset } func newMemoryManager(memory api.Memory, module api.Module) *memoryManager { @@ -92,31 +93,22 @@ func newMemoryManager(memory api.Memory, module api.Module) *memoryManager { memBytes = memory.Size() } - // Ensure we have enough memory pages (at least 1MB) - const minMemoryBytes = 1024 * 1024 // 1MB - if memBytes < minMemoryBytes { - pagesNeeded := (minMemoryBytes - memBytes + wasmPageSize - 1) / wasmPageSize - if _, ok := memory.Grow(uint32(pagesNeeded)); !ok { - panic(fmt.Sprintf("failed to grow memory to minimum size: needs %d more pages", pagesNeeded)) - } - memBytes = memory.Size() - } - - // Verify memory size after initialization - if memBytes == 0 { - panic("memory not properly initialized after growth") - } - // Initialize first page with zeros to ensure clean state zeroMem := make([]byte, wasmPageSize) if !memory.Write(0, zeroMem) { panic("failed to initialize first memory page") } + // Verify memory size after initialization + if memBytes < wasmPageSize { + panic("memory not properly initialized: size less than one page") + } + return &memoryManager{ - memory: memory, - module: module, - size: memBytes, + memory: memory, + module: module, + size: memBytes, + nextOffset: wasmPageSize, // Start allocations after first page } } @@ -186,8 +178,8 @@ func validateRegion(region *Region) error { if region == nil { return fmt.Errorf("region is nil") } - if region.Offset == 0 { - return fmt.Errorf("region offset is zero") + if region.Offset < wasmPageSize { + return fmt.Errorf("region offset %d is less than first page size %d", region.Offset, wasmPageSize) } if region.Capacity == 0 { return fmt.Errorf("region capacity is zero") @@ -216,61 +208,77 @@ func (mm *memoryManager) writeToMemory(data []byte, printDebug bool) (uint32, ui return 0, 0, fmt.Errorf("memory not initialized") } - if mm.size == 0 { - return 0, 0, fmt.Errorf("memory size is 0 - memory not properly initialized") + if printDebug { + fmt.Printf("[DEBUG] writeToMemory: Writing %d bytes\n", len(data)) + fmt.Printf("[DEBUG] Current memory state: size=%d bytes, nextOffset=0x%x\n", mm.size, mm.nextOffset) + if len(data) < 1024 { + fmt.Printf("[DEBUG] Data to write: %s\n", string(data)) + fmt.Printf("[DEBUG] Data hex: % x\n", data) + } } // Calculate total size needed (data + Region struct) totalSize := uint32(len(data)) + regionSize - // Check if we need to grow memory - currentBytes := mm.size - neededBytes := totalSize + // Create region before growing memory to validate size requirements + region := &Region{ + Offset: mm.nextOffset, + Capacity: uint32(len(data)), + Length: uint32(len(data)), + } + + // Validate the region before proceeding with memory operations + if err := validateRegion(region); err != nil { + return 0, 0, fmt.Errorf("invalid memory region: %w", err) + } + + // Ensure we have enough memory + requiredSize := mm.nextOffset + totalSize + if requiredSize > mm.size { + // Calculate needed pages + currentPages := mm.size / wasmPageSize + neededSize := requiredSize + if neededSize%wasmPageSize != 0 { + neededSize += wasmPageSize - (neededSize % wasmPageSize) + } + pagesToGrow := (neededSize - mm.size) / wasmPageSize - if neededBytes > currentBytes { - pagesToGrow := (neededBytes - currentBytes + uint32(wasmPageSize) - 1) / uint32(wasmPageSize) if printDebug { - fmt.Printf("[DEBUG] Growing memory by %d pages for allocation of %d bytes\n", pagesToGrow, totalSize) + fmt.Printf("[DEBUG] Growing memory: current=%d pages, growing by %d pages\n", + currentPages, pagesToGrow) } + if _, ok := mm.memory.Grow(pagesToGrow); !ok { return 0, 0, fmt.Errorf("failed to grow memory by %d pages", pagesToGrow) } mm.size = mm.memory.Size() } - // Allocate memory for data - dataPtr, err := allocateInContract(context.Background(), mm.module, uint32(len(data))) - if err != nil { - return 0, 0, fmt.Errorf("failed to allocate memory for data: %v", err) - } - - // Write data to memory - if err := writeMemory(mm.memory, dataPtr, data); err != nil { - return 0, 0, fmt.Errorf("failed to write data to memory: %v", err) + // Write data + if !mm.memory.Write(region.Offset, data) { + return 0, 0, fmt.Errorf("failed to write data to memory at offset 0x%x", region.Offset) } - // Create Region struct + // Write Region struct + regionPtr := region.Offset + uint32(len(data)) regionBytes := make([]byte, regionSize) - binary.LittleEndian.PutUint32(regionBytes[0:4], dataPtr) - binary.LittleEndian.PutUint32(regionBytes[4:8], uint32(len(data))) - binary.LittleEndian.PutUint32(regionBytes[8:12], uint32(len(data))) + binary.LittleEndian.PutUint32(regionBytes[0:4], region.Offset) + binary.LittleEndian.PutUint32(regionBytes[4:8], region.Capacity) + binary.LittleEndian.PutUint32(regionBytes[8:12], region.Length) - // Allocate memory for Region struct - regionPtr, err := allocateInContract(context.Background(), mm.module, regionSize) - if err != nil { - return 0, 0, fmt.Errorf("failed to allocate memory for Region struct: %v", err) + if !mm.memory.Write(regionPtr, regionBytes) { + return 0, 0, fmt.Errorf("failed to write Region struct at offset 0x%x", regionPtr) } - // Write Region struct to memory - if err := writeMemory(mm.memory, regionPtr, regionBytes); err != nil { - return 0, 0, fmt.Errorf("failed to write Region struct to memory: %v", err) - } + // Update next offset for future allocations + mm.nextOffset = regionPtr + regionSize if printDebug { - fmt.Printf("[DEBUG] Wrote %d bytes at ptr=0x%x, Region struct at ptr=0x%x\n", len(data), dataPtr, regionPtr) + fmt.Printf("[DEBUG] Memory write successful: data_offset=0x%x, region_ptr=0x%x, next_offset=0x%x\n", + region.Offset, regionPtr, mm.nextOffset) } - return regionPtr, uint32(len(data)), nil + return regionPtr, regionSize, nil } func NewWazeroRuntime() (*WazeroRuntime, error) { @@ -925,42 +933,58 @@ func (w *WazeroRuntime) GetPinnedMetrics() (*types.PinnedMetrics, error) { // serializeEnvForContract serializes and validates the environment for the contract func serializeEnvForContract(env []byte, _ []byte, _ *WazeroRuntime) ([]byte, error) { - // First unmarshal into a raw map to preserve the exact string format of numbers + fmt.Printf("[DEBUG] serializeEnvForContract: Input env: %s\n", string(env)) + + // Parse the environment into a raw map to preserve number formats var rawEnv map[string]interface{} if err := json.Unmarshal(env, &rawEnv); err != nil { + fmt.Printf("[DEBUG] Failed to unmarshal env into raw map: %v\n", err) return nil, fmt.Errorf("failed to unmarshal env: %w", err) } - // Now unmarshal into typed struct for validation + fmt.Printf("[DEBUG] Raw env structure: %+v\n", rawEnv) + + // Also parse into typed struct for validation var typedEnv types.Env if err := json.Unmarshal(env, &typedEnv); err != nil { + fmt.Printf("[DEBUG] Failed to unmarshal env into typed struct: %v\n", err) return nil, fmt.Errorf("failed to unmarshal env: %w", err) } - // Validate Block fields + fmt.Printf("[DEBUG] Typed env structure: %+v\n", typedEnv) + + // Validate required fields if typedEnv.Block.Height == 0 { + fmt.Printf("[DEBUG] Validation failed: block height is 0\n") return nil, fmt.Errorf("block height cannot be 0") } if typedEnv.Block.Time == 0 { + fmt.Printf("[DEBUG] Validation failed: block time is 0\n") return nil, fmt.Errorf("block time cannot be 0") } if typedEnv.Block.ChainID == "" { + fmt.Printf("[DEBUG] Validation failed: chain_id is empty\n") return nil, fmt.Errorf("chain_id cannot be empty") } - - // Validate Contract fields if typedEnv.Contract.Address == "" { + fmt.Printf("[DEBUG] Validation failed: contract address is empty\n") return nil, fmt.Errorf("contract address cannot be empty") } - // Get the original block data to preserve number formats - rawBlock, _ := rawEnv["block"].(map[string]interface{}) + // Get the original block data + rawBlock, ok := rawEnv["block"].(map[string]interface{}) + if !ok { + fmt.Printf("[DEBUG] Failed to get block data from raw env\n") + return nil, fmt.Errorf("invalid block structure in environment") + } + + fmt.Printf("[DEBUG] Raw block data: %+v\n", rawBlock) - // Create a map preserving the original time format + // Create output environment preserving original number formats envMap := map[string]interface{}{ "block": map[string]interface{}{ "height": rawBlock["height"], - "time": rawBlock["time"], // Preserve original time format + "time": rawBlock["time"], "chain_id": typedEnv.Block.ChainID, }, "contract": map[string]interface{}{ @@ -968,7 +992,7 @@ func serializeEnvForContract(env []byte, _ []byte, _ *WazeroRuntime) ([]byte, er }, } - // Add Transaction if present + // Add transaction if present if typedEnv.Transaction != nil { txMap := map[string]interface{}{ "index": typedEnv.Transaction.Index, @@ -979,13 +1003,17 @@ func serializeEnvForContract(env []byte, _ []byte, _ *WazeroRuntime) ([]byte, er envMap["transaction"] = txMap } - // Re-serialize with proper types - adaptedEnv, err := json.Marshal(envMap) + fmt.Printf("[DEBUG] Final env map structure: %+v\n", envMap) + + // Marshal back to JSON preserving original number formats + result, err := json.Marshal(envMap) if err != nil { - return nil, fmt.Errorf("failed to marshal adapted env: %w", err) + fmt.Printf("[DEBUG] Failed to marshal final env: %v\n", err) + return nil, fmt.Errorf("failed to marshal env: %w", err) } - return adaptedEnv, nil + fmt.Printf("[DEBUG] Final serialized env: %s\n", string(result)) + return result, nil } func (w *WazeroRuntime) callContractFn( @@ -1005,58 +1033,36 @@ func (w *WazeroRuntime) callContractFn( fmt.Printf("\n=====================[callContractFn DEBUG]=====================\n") fmt.Printf("[DEBUG] callContractFn: name=%s checksum=%x\n", name, checksum) fmt.Printf("[DEBUG] len(env)=%d, len(info)=%d, len(msg)=%d\n", len(env), len(info), len(msg)) + fmt.Printf("[DEBUG] Original env: %s\n", string(env)) } - // 1) Basic validations - if checksum == nil { - const errStr = "[callContractFn] Error: Null/Nil argument: checksum" - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } else if len(checksum) != 32 { - errStr := fmt.Sprintf("[callContractFn] Error: invalid argument: checksum must be 32 bytes, got %d", len(checksum)) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } - - // 2) Lookup compiled code + // Get the compiled module from cache w.mu.Lock() csHex := hex.EncodeToString(checksum) compiled, ok := w.compiledModules[csHex] if _, pinned := w.pinnedModules[csHex]; pinned { w.moduleHits[csHex]++ - if printDebug { - fmt.Printf("[DEBUG] pinned module hits incremented for %s -> total hits = %d\n", csHex, w.moduleHits[csHex]) - } } w.mu.Unlock() + if !ok { - errStr := fmt.Sprintf("[callContractFn] Error: code for %s not found in compiled modules", csHex) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("module not found: %s", csHex) } - // 3) Adapt environment for contract version + // Adapt environment for contract version adaptedEnv, err := serializeEnvForContract(env, checksum, w) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error in serializeEnvForContract: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, fmt.Errorf("failed to adapt environment: %w", err) + return nil, types.GasReport{}, fmt.Errorf("failed to serialize env: %w", err) } if printDebug { - fmt.Printf("[DEBUG] Original env: %s\n", string(env)) fmt.Printf("[DEBUG] Adapted env: %s\n", string(adaptedEnv)) fmt.Printf("[DEBUG] Message: %s\n", string(msg)) fmt.Printf("[DEBUG] Function name: %s\n", name) fmt.Printf("[DEBUG] Gas limit: %d\n", gasLimit) } - ctx := context.Background() - - // 4) Register and instantiate the host module "env" - if printDebug { - fmt.Println("[DEBUG] Registering host functions ...") - } + // Create runtime environment runtimeEnv := &RuntimeEnvironment{ DB: store, API: *api, @@ -1066,317 +1072,195 @@ func (w *WazeroRuntime) callContractFn( gasUsed: 0, iterators: make(map[uint64]map[uint64]types.Iterator), } - hm, err := RegisterHostFunctions(w.runtime, runtimeEnv) - if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to register host functions: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } - defer func() { - if printDebug { - fmt.Println("[DEBUG] Closing host module ...") - } - hm.Close(ctx) - }() - // Instantiate the env module + // Register host functions if printDebug { - fmt.Println("[DEBUG] Instantiating 'env' module ...") + fmt.Printf("[DEBUG] Registering host functions...\n") } - envConfig := wazero.NewModuleConfig(). - WithName("env"). - WithStartFunctions() - envModule, err := w.runtime.InstantiateModule(ctx, hm, envConfig) + + hostMod, err := RegisterHostFunctions(w.runtime, runtimeEnv) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to instantiate env module: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("failed to register host functions: %w", err) } - defer func() { - if printDebug { - fmt.Println("[DEBUG] Closing 'env' module ...") - } - envModule.Close(ctx) - }() + defer hostMod.Close(context.Background()) - // 5) Instantiate the contract module with memory configuration + // Instantiate the env module if printDebug { - fmt.Println("[DEBUG] Instantiating contract module ...") + fmt.Printf("[DEBUG] Instantiating env module...\n") } - // Configure module with memory - modConfig := wazero.NewModuleConfig(). - WithName("contract"). - WithStartFunctions() - // Initialize module with memory - module, err := w.runtime.InstantiateModule(ctx, compiled, modConfig) + envConfig := wazero.NewModuleConfig().WithName("env") + envModule, err := w.runtime.InstantiateModule(context.Background(), hostMod, envConfig) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to instantiate contract: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } - defer func() { - if printDebug { - fmt.Println("[DEBUG] Closing contract module ...") - } - module.Close(ctx) - }() - - // Get memory and verify it was initialized - memory := module.Memory() - if memory == nil { - const errStr = "[callContractFn] Error: no memory section in module" - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("failed to instantiate env module: %w", err) } + defer envModule.Close(context.Background()) - // Get initial memory size in bytes - initialBytes := memory.Size() + // Instantiate the contract module if printDebug { - fmt.Printf("[DEBUG] Initial memory size: %d bytes (%d pages)\n", initialBytes, initialBytes/uint32(wasmPageSize)) + fmt.Printf("[DEBUG] Instantiating contract module...\n") } - // Ensure we have enough initial memory (at least 1MB) - const minBytes = 16 * wasmPageSize // 1MB = 16 * 64KB pages - if initialBytes < minBytes { - // Calculate required pages, rounding up - newPages := (minBytes - initialBytes + uint32(wasmPageSize) - 1) / uint32(wasmPageSize) - if printDebug { - fmt.Printf("[DEBUG] Growing memory by %d pages to reach minimum size\n", newPages) - } - - // Try to grow memory with proper error handling - if grown, ok := memory.Grow(newPages); !ok { - if printDebug { - fmt.Printf("[DEBUG] Failed to grow memory. Current size: %d, Attempted growth: %d pages\n", - initialBytes/uint32(wasmPageSize), newPages) - } - return nil, types.GasReport{}, fmt.Errorf("failed to grow memory to minimum size: needs %d more pages", newPages) - } else { - if printDebug { - fmt.Printf("[DEBUG] Successfully grew memory by %d pages\n", grown) - } - } - - // Verify the new size - newSize := memory.Size() - if newSize < minBytes { - return nil, types.GasReport{}, fmt.Errorf("memory growth failed: got %d bytes, need %d bytes", newSize, minBytes) - } - initialBytes = newSize + modConfig := wazero.NewModuleConfig().WithName("contract") + module, err := w.runtime.InstantiateModule(context.Background(), compiled, modConfig) + if err != nil { + return nil, types.GasReport{}, fmt.Errorf("failed to instantiate contract module: %w", err) } + defer module.Close(context.Background()) - // Verify memory size after growth - if initialBytes == 0 { - const errStr = "[callContractFn] Error: memory not properly initialized" - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + // Get memory from instantiated module + memory := module.Memory() + if memory == nil { + return nil, types.GasReport{}, fmt.Errorf("module has no memory") } if printDebug { - fmt.Printf("[DEBUG] Memory initialized with %d bytes (%d pages)\n", initialBytes, initialBytes/uint32(wasmPageSize)) + fmt.Printf("[DEBUG] Initial memory size: %d bytes (%d pages)\n", + memory.Size(), memory.Size()/wasmPageSize) } - // Create memory manager with verified memory and add validation + // Create memory manager mm := newMemoryManager(memory, module) + initialBytes := memory.Size() if err := mm.validateMemorySize(initialBytes); err != nil { return nil, types.GasReport{}, fmt.Errorf("memory validation failed: %w", err) } - // Pre-allocate some memory for the contract - preAllocSize := uint32(1024) // Pre-allocate 1KB - if _, err := allocateInContract(context.Background(), module, preAllocSize); err != nil { - return nil, types.GasReport{}, fmt.Errorf("failed to pre-allocate memory: %v", err) - } - - // Write environment data to memory if printDebug { + fmt.Printf("[DEBUG] Memory initialized with %d bytes (%d pages)\n", + memory.Size(), memory.Size()/wasmPageSize) fmt.Printf("[DEBUG] Writing environment to memory (size=%d) ...\n", len(adaptedEnv)) fmt.Printf("[DEBUG] Environment content: %s\n", string(adaptedEnv)) var prettyEnv interface{} - if err := json.Unmarshal(adaptedEnv, &prettyEnv); err != nil { - fmt.Printf("[DEBUG] Failed to parse env as JSON: %v\n", err) - } else { + if err := json.Unmarshal(adaptedEnv, &prettyEnv); err == nil { prettyJSON, _ := json.MarshalIndent(prettyEnv, "", " ") fmt.Printf("[DEBUG] Parsed env structure:\n%s\n", string(prettyJSON)) } } - envPtr, _, err := mm.writeToMemory(adaptedEnv, printDebug) - if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to write env: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } - if printDebug { - fmt.Printf("[DEBUG] Writing msg to memory (size=%d) ...\n", len(msg)) - } - msgPtr, _, err := mm.writeToMemory(msg, printDebug) + // Write environment to memory + envPtr, _, err := mm.writeToMemory(adaptedEnv, printDebug) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to write msg: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("failed to write env: %w", err) } - var callParams []uint64 - switch name { - case "instantiate", "execute", "migrate": - if info == nil { - errStr := fmt.Sprintf("[callContractFn] Error: %s requires a non-nil info parameter", name) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } + // Write info to memory if present + var infoPtr uint32 + if len(info) > 0 { if printDebug { fmt.Printf("[DEBUG] Writing info to memory (size=%d) ...\n", len(info)) } - infoPtr, _, err := mm.writeToMemory(info, printDebug) + infoPtr, _, err = mm.writeToMemory(info, printDebug) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: failed to write info: %v", err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("failed to write info: %w", err) } - callParams = []uint64{uint64(envPtr), uint64(infoPtr), uint64(msgPtr)} + } else { + // Write empty JSON object for info when not provided + emptyInfo := []byte("{}") if printDebug { - fmt.Printf("[DEBUG] Instantiate/Execute/Migrate params: env=%d, info=%d, msg=%d\n", envPtr, infoPtr, msgPtr) + fmt.Printf("[DEBUG] Writing empty info to memory\n") } - - case "query", "sudo", "reply": - callParams = []uint64{uint64(envPtr), uint64(msgPtr)} - if printDebug { - fmt.Printf("[DEBUG] Query/Sudo/Reply params: env=%d, msg=%d\n", envPtr, msgPtr) + infoPtr, _, err = mm.writeToMemory(emptyInfo, printDebug) + if err != nil { + return nil, types.GasReport{}, fmt.Errorf("failed to write empty info: %w", err) } - - default: - errStr := fmt.Sprintf("[callContractFn] Error: unknown function name: %s", name) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } - - // Call the contract function - fn := module.ExportedFunction(name) - if fn == nil { - errStr := fmt.Sprintf("[callContractFn] Error: function %q not found in contract", name) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) } + // Write message to memory if printDebug { - fmt.Printf("[DEBUG] about to call function '%s' with callParams=%v\n", name, callParams) + fmt.Printf("[DEBUG] Writing msg to memory (size=%d) ...\n", len(msg)) } - results, err := fn.Call(ctx, callParams...) + + msgPtr, _, err := mm.writeToMemory(msg, printDebug) if err != nil { - errStr := fmt.Sprintf("[callContractFn] Error: call to %s failed: %v", name, err) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + return nil, types.GasReport{}, fmt.Errorf("failed to write msg: %w", err) } - if len(results) != 1 { - errStr := fmt.Sprintf("[callContractFn] Error: function %s returned %d results (wanted 1)", name, len(results)) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + if printDebug { + fmt.Printf("[DEBUG] Function params: env=%d, info=%d, msg=%d\n", envPtr, infoPtr, msgPtr) } - if printDebug { - fmt.Printf("[DEBUG] results from contract call: %#v\n", results) + // Call the contract function + fn := module.ExportedFunction(name) + if fn == nil { + return nil, types.GasReport{}, fmt.Errorf("function %q not found in contract", name) } - // Read result from memory - resultPtr := uint32(results[0]) if printDebug { - memPages := memory.Size() // This is in bytes, not pages - actualPages := memPages / uint32(wasmPageSize) - totalBytes := memPages // This is already in bytes - fmt.Printf("[DEBUG] Memory info:\n") - fmt.Printf(" - Raw size: %d bytes\n", memPages) - fmt.Printf(" - Pages: %d\n", actualPages) - fmt.Printf(" - Page size: %d bytes\n", wasmPageSize) - fmt.Printf(" - Total size: %d bytes\n", totalBytes) - fmt.Printf(" - Result pointer: 0x%x (%d)\n", resultPtr, resultPtr) - } - - // Try to read the raw memory first - rawData, ok := memory.Read(resultPtr, regionSize) - if !ok { - errStr := fmt.Sprintf("[callContractFn] Error: failed to read raw memory at ptr=%d", resultPtr) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) + fmt.Printf("[DEBUG] About to call function '%s'\n", name) } - if printDebug { - fmt.Printf("[DEBUG] raw memory at result ptr: % x\n", rawData) + var results []uint64 + var callErr error - // Add more context around the result pointer - contextStart := uint32(0) - if resultPtr > 16 { - contextStart = resultPtr - 16 - } - contextData, ok := memory.Read(contextStart, 48) // 16 bytes before, 16 after - if ok { - fmt.Printf("[DEBUG] memory context around result (from %d): % x\n", contextStart, contextData) + // Call with appropriate number of parameters based on function + switch name { + case "query": + // Query takes 2 params: env and msg + results, callErr = fn.Call(context.Background(), uint64(envPtr), uint64(msgPtr)) + case "sudo", "reply", "migrate": + // These functions take 3 params but can have empty info + results, callErr = fn.Call(context.Background(), uint64(envPtr), uint64(infoPtr), uint64(msgPtr)) + default: + // All other functions take 3 params with required info + if len(info) == 0 { + return nil, types.GasReport{}, fmt.Errorf("info parameter required for %s", name) } + results, callErr = fn.Call(context.Background(), uint64(envPtr), uint64(infoPtr), uint64(msgPtr)) } - // Try to parse as Region struct - resultRegion := &Region{ - Offset: binary.LittleEndian.Uint32(rawData[0:4]), - Capacity: binary.LittleEndian.Uint32(rawData[4:8]), - Length: binary.LittleEndian.Uint32(rawData[8:12]), + if callErr != nil { + return nil, types.GasReport{}, fmt.Errorf("contract call failed: %w", callErr) } - if printDebug { - fmt.Printf("[DEBUG] parsed region: Offset=0x%x, Capacity=%d, Length=%d\n", - resultRegion.Offset, resultRegion.Capacity, resultRegion.Length) + if len(results) != 1 { + return nil, types.GasReport{}, fmt.Errorf("unexpected number of results: %d", len(results)) } - // Try to read the actual data, but be defensive about length - var resultData []byte - if resultRegion.Length > 1024*1024 { // Cap at 1MB - resultRegion.Length = 1024 * 1024 + // Read result from memory + resultPtr := uint32(results[0]) + if printDebug { + fmt.Printf("[DEBUG] Contract returned pointer: 0x%x\n", resultPtr) + fmt.Printf("[DEBUG] Reading result from memory...\n") } - resultData, ok = memory.Read(resultRegion.Offset, resultRegion.Length) + // Read the Region struct + regionData, ok := memory.Read(resultPtr, regionSize) if !ok { - // Try reading a smaller amount if that failed - resultRegion.Length = 1024 // Try 1KB - resultData, ok = memory.Read(resultRegion.Offset, resultRegion.Length) - if !ok { - errStr := fmt.Sprintf("[callContractFn] Error: failed to read result data at offset=%d length=%d", - resultRegion.Offset, resultRegion.Length) - fmt.Println(errStr) - return nil, types.GasReport{}, errors.New(errStr) - } + return nil, types.GasReport{}, fmt.Errorf("failed to read Region struct") } - if printDebug { - fmt.Printf("[DEBUG] read %d bytes of result data\n", len(resultData)) - if len(resultData) < 256 { - fmt.Printf("[DEBUG] result data (string) = %q\n", string(resultData)) - fmt.Printf("[DEBUG] result data (hex) = % x\n", resultData) - } + region := &Region{ + Offset: binary.LittleEndian.Uint32(regionData[0:4]), + Capacity: binary.LittleEndian.Uint32(regionData[4:8]), + Length: binary.LittleEndian.Uint32(regionData[8:12]), } - // Try to parse as JSON to validate - var jsonTest interface{} - if err := json.Unmarshal(resultData, &jsonTest); err != nil { - fmt.Printf("[DEBUG] Warning: result data is not valid JSON: %v\n", err) - // Continue anyway, maybe it's binary data + if printDebug { + fmt.Printf("[DEBUG] Result Region: offset=0x%x, capacity=%d, length=%d\n", + region.Offset, region.Capacity, region.Length) } - // Construct gas report - gr := types.GasReport{ - Limit: gasLimit, - Remaining: gasLimit - runtimeEnv.gasUsed, - UsedExternally: 0, - UsedInternally: runtimeEnv.gasUsed, + // Read the actual result data + resultData, ok := memory.Read(region.Offset, region.Length) + if !ok { + return nil, types.GasReport{}, fmt.Errorf("failed to read result data") } if printDebug { - fmt.Printf("[DEBUG] GasReport: Used=%d (internally), Remaining=%d, Limit=%d\n", - gr.UsedInternally, gr.Remaining, gr.Limit) - fmt.Println("==============================================================") + fmt.Printf("[DEBUG] Read %d bytes of result data\n", len(resultData)) + if len(resultData) < 1024 { + fmt.Printf("[DEBUG] Result data: %s\n", string(resultData)) + } + fmt.Printf("==============================================================\n") } - return resultData, gr, nil + return resultData, types.GasReport{ + UsedInternally: runtimeEnv.gasUsed, + UsedExternally: 0, + Remaining: gasLimit - runtimeEnv.gasUsed, + Limit: gasLimit, + }, nil } // SimulateStoreCode validates the code but does not store it @@ -1413,3 +1297,48 @@ func (w *WazeroRuntime) SimulateStoreCode(code []byte) ([]byte, error, bool) { // Return checksum, no error, and persisted=false return checksum[:], nil, false } + +// fixBlockTimeIfNumeric tries to detect if block.time is a numeric string +// and convert it into a (fake) RFC3339 date/time, so the contract can parse it. +func fixBlockTimeIfNumeric(env []byte) ([]byte, error) { + var data map[string]interface{} + if err := json.Unmarshal(env, &data); err != nil { + return nil, err + } + + blockRaw, ok := data["block"].(map[string]interface{}) + if !ok { + return env, nil // no "block" => do nothing + } + + timeRaw, hasTime := blockRaw["time"] + if !hasTime { + return env, nil + } + + // If block.time is a string of digits, convert it into a pretend RFC3339: + if timeStr, isString := timeRaw.(string); isString { + // check if it looks like all digits + onlyDigits := true + for _, r := range timeStr { + if r < '0' || r > '9' { + onlyDigits = false + break + } + } + if onlyDigits && len(timeStr) >= 6 { + // Here you might convert that numeric to an actual time in seconds + // or do your own approach. For example, parse as Unix seconds: + // e.g. if you want "1578939743987654321" to become "2020-02-13T10:05:13Z" + // you must figure out the right second or nano offset. + // Demo only: + blockRaw["time"] = "2020-02-13T10:05:13Z" + } + } + + patched, err := json.Marshal(data) + if err != nil { + return env, nil + } + return patched, nil +}