diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 6a0fd39dd..5fcc252e3 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -12,7 +12,6 @@ import ( "github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet" zero "github.com/NethermindEth/cairo-vm-go/pkg/parsers/zero" "github.com/NethermindEth/cairo-vm-go/pkg/runner" - "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" "github.com/urfave/cli/v2" ) @@ -125,7 +124,7 @@ func main() { if proofmode { runnerMode = runner.ProofModeZero } - return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, nil) + return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, nil, 0) }, }, { @@ -217,7 +216,11 @@ func main() { if err != nil { return fmt.Errorf("cannot load program: %w", err) } - program, hints, err := runner.AssembleProgram(cairoProgram) + userArgs, err := starknet.ParseCairoProgramArgs(args) + if err != nil { + return fmt.Errorf("cannot parse args: %w", err) + } + program, hints, userArgs, err := runner.AssembleProgram(cairoProgram, userArgs, availableGas) if err != nil { return fmt.Errorf("cannot assemble program: %w", err) } @@ -225,19 +228,7 @@ func main() { if proofmode { runnerMode = runner.ProofModeCairo } - userArgs, err := starknet.ParseCairoProgramArgs(args) - if err != nil { - return fmt.Errorf("cannot parse args: %w", err) - } - if availableGas > 0 { - // The first argument is the available gas - availableGasArg := starknet.CairoFuncArgs{ - Single: new(fp.Element).SetUint64(availableGas), - Array: nil, - } - userArgs = append([]starknet.CairoFuncArgs{availableGasArg}, userArgs...) - } - return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, userArgs) + return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, userArgs, availableGas) }, }, }, @@ -264,9 +255,10 @@ func runVM( hints map[uint64][]hinter.Hinter, runnerMode runner.RunnerMode, userArgs []starknet.CairoFuncArgs, + availableGas uint64, ) error { fmt.Println("Running....") - runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName, userArgs) + runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName, userArgs, availableGas) if err != nil { return fmt.Errorf("cannot create runner: %w", err) } diff --git a/integration_tests/cairo_1_programs/with_input/array_input_sum.cairo b/integration_tests/cairo_1_programs/with_input/array_input_sum__small.cairo similarity index 100% rename from integration_tests/cairo_1_programs/with_input/array_input_sum.cairo rename to integration_tests/cairo_1_programs/with_input/array_input_sum__small.cairo diff --git a/integration_tests/cairo_1_programs/with_input/array_length.cairo b/integration_tests/cairo_1_programs/with_input/array_length__small.cairo similarity index 100% rename from integration_tests/cairo_1_programs/with_input/array_length.cairo rename to integration_tests/cairo_1_programs/with_input/array_length__small.cairo diff --git a/integration_tests/cairo_1_programs/with_input/dict_with_input.cairo b/integration_tests/cairo_1_programs/with_input/dict_with_input__small.cairo similarity index 100% rename from integration_tests/cairo_1_programs/with_input/dict_with_input.cairo rename to integration_tests/cairo_1_programs/with_input/dict_with_input__small.cairo diff --git a/integration_tests/cairo_1_programs/with_input/tensor.cairo b/integration_tests/cairo_1_programs/with_input/tensor__small.cairo similarity index 100% rename from integration_tests/cairo_1_programs/with_input/tensor.cairo rename to integration_tests/cairo_1_programs/with_input/tensor__small.cairo diff --git a/integration_tests/cairo_vm_test.go b/integration_tests/cairo_vm_test.go index d68220650..6f39f0665 100644 --- a/integration_tests/cairo_vm_test.go +++ b/integration_tests/cairo_vm_test.go @@ -66,7 +66,7 @@ func (f *Filter) filtered(testFile string) bool { return false } -func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[string][3]int, benchmark bool, errorExpected bool, zero bool) { +func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[string][3]int, benchmark bool, errorExpected bool, zero bool, inputArgs string) { t.Logf("testing: %s\n", path) compiledOutput, err := compileCairoCode(path, zero) if err != nil { @@ -75,7 +75,7 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str } layout := getLayoutFromFileName(path) - elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero) + elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero, inputArgs) if errorExpected { assert.Error(t, err, path) writeToFile(path) @@ -92,7 +92,7 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str if zero { rustVmFilePath = compiledOutput } - elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero) + elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero, inputArgs) if errorExpected { // we let the code go on so that we can check if the go vm also raises an error assert.Error(t, err, path) @@ -171,16 +171,28 @@ func TestCairoFiles(t *testing.T) { if err != nil { panic(fmt.Errorf("failed to open file: %w", err)) } + file.Close() - roots := []struct { + type TestCase struct { path string zero bool - }{ + } + roots := []TestCase{ {"./cairo_zero_hint_tests/", true}, {"./cairo_zero_file_tests/", true}, {"./builtin_tests/", true}, // {"./cairo_1_programs/", false}, // {"./cairo_1_programs/dict_non_squashed", false}, + // {"./cairo_1_programs/with_input", false}, + } + + // inputArgsMap is used to provide input arguments to the tests that require them. Whenever the args are needed for the new files, they can simply be added here. + inputArgsMap := map[string]string{ + "cairo_1_programs/with_input/array_input_sum__small.cairo": "2 [111 222 333] 1 [444 555 666 777]", + "cairo_1_programs/with_input/array_length__small.cairo": "[1 2 3 4 5 6] [7 8 9 10]", + "cairo_1_programs/with_input/branching.cairo": "123", + "cairo_1_programs/with_input/dict_with_input__small.cairo": "[1 2 3 4]", + "cairo_1_programs/with_input/tensor__small.cairo": "[1 4] [1 5]", } // filter is for debugging purposes @@ -211,22 +223,19 @@ func TestCairoFiles(t *testing.T) { if !filter.filtered(name) { continue } - + inputArgs := inputArgsMap[path] // we run tests concurrently if we don't need benchmarks if !*zerobench { sem <- struct{}{} // acquire a semaphore slot wg.Add(1) - go func(path, name string, root struct { - path string - zero bool - }) { + go func(path, name string, root TestCase, inputArgs string) { defer wg.Done() defer func() { <-sem }() // release the semaphore slot when done - runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero) - }(path, name, root) + runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero, inputArgs) + }(path, name, root, inputArgs) } else { - runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero) + runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero, inputArgs) } } } @@ -400,7 +409,7 @@ func runPythonVm(path, layout string) (time.Duration, string, string, error) { // given a path to a compiled cairo zero file, execute it using the // rust vm and return the trace and memory files location -func runRustVm(path, layout string, zero bool) (time.Duration, string, string, error) { +func runRustVm(path, layout string, zero bool, inputArgs string) (time.Duration, string, string, error) { traceOutput := swapExtenstion(path, rsTraceSuffix) memoryOutput := swapExtenstion(path, rsMemorySuffix) @@ -412,6 +421,8 @@ func runRustVm(path, layout string, zero bool) (time.Duration, string, string, e memoryOutput, "--layout", layout, + "--args", + inputArgs, } if zero { @@ -441,7 +452,7 @@ func runRustVm(path, layout string, zero bool) (time.Duration, string, string, e // given a path to a compiled cairo zero file, execute // it using our vm -func runVm(path, layout string, zero bool) (time.Duration, string, string, string, error) { +func runVm(path, layout string, zero bool, inputArgs string) (time.Duration, string, string, string, error) { traceOutput := swapExtenstion(path, traceSuffix) memoryOutput := swapExtenstion(path, memorySuffix) @@ -471,6 +482,8 @@ func runVm(path, layout string, zero bool) (time.Duration, string, string, strin layout, "--available_gas", "9999999", + "--args", + inputArgs, } } args = append(args, path) diff --git a/pkg/hintrunner/core/hint.go b/pkg/hintrunner/core/hint.go index 6667335b3..6ef697617 100644 --- a/pkg/hintrunner/core/hint.go +++ b/pkg/hintrunner/core/hint.go @@ -1937,30 +1937,75 @@ func (hint *ExternalWriteArgsToMemory) String() string { } func (hint *ExternalWriteArgsToMemory) Execute(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { - userArgsVar, err := ctx.ScopeManager.GetVariableValue("userArgs") + userArgs, err := hinter.GetVariableAs[[]starknet.CairoFuncArgs](&ctx.ScopeManager, "userArgs") if err != nil { return fmt.Errorf("get user args: %v", err) } - userArgs, ok := userArgsVar.([]starknet.CairoFuncArgs) - if !ok { - return fmt.Errorf("expected user args to be a list of CairoFuncArgs") + // The apOffset is the AP correction, which represents the memory slots taken up by the values created by the entry code instructions. + // It is calculated in the `getNewHintRunnerContext()` method. + apOffset, err := hinter.GetVariableAs[uint64](&ctx.ScopeManager, "apOffset") + if err != nil { + return fmt.Errorf("get ap offset: %v", err) } + apOffset += vm.Context.Ap for _, arg := range userArgs { if arg.Single != nil { mv := mem.MemoryValueFromFieldElement(arg.Single) - err := vm.Memory.Write(1, vm.Context.Ap, &mv) + err := vm.Memory.Write(1, apOffset, &mv) if err != nil { return fmt.Errorf("write single arg: %v", err) } + apOffset++ } else if arg.Array != nil { + // The array is stored in memory as follows: + // Each array gets assigned a new segment (the pointer is stored in the arrayBase). + // arrayBase and arrayEnd pointers are written to the Execution Segment consecutively. + // Then, the array elements are written to the newly created array segment. arrayBase := vm.Memory.AllocateEmptySegment() mv := mem.MemoryValueFromMemoryAddress(&arrayBase) - err := vm.Memory.Write(1, vm.Context.Ap, &mv) + err := vm.Memory.Write(1, apOffset, &mv) if err != nil { return fmt.Errorf("write array base: %v", err) } - // TODO: Implement array writing + apOffset++ + arrayEnd := arrayBase + for _, val := range arg.Array { + mv := mem.MemoryValueFromFieldElement(&val) + err := vm.Memory.Write(arrayEnd.SegmentIndex, arrayEnd.Offset, &mv) + if err != nil { + return fmt.Errorf("write array element: %v", err) + } + arrayEnd.Offset += 1 + } + mv = mem.MemoryValueFromMemoryAddress(&arrayEnd) + err = vm.Memory.Write(1, apOffset, &mv) + if err != nil { + return fmt.Errorf("write array end: %v", err) + } + apOffset++ } } return nil } + +type ExternalWriteGasToMemory struct{} + +func (hint *ExternalWriteGasToMemory) String() string { + return "ExternalWriteGasToMemory" +} + +func (hint *ExternalWriteGasToMemory) Execute(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { + // ExternalWriteGasToMemory is a separate hint, that writes the gas value to the memory. + // The gas value is written to the memory cell reserved by the instruction generated in entry code, which is dependent on the ordering of builtins list. + // Therefore the writing of the gas value to the memory is done in a separate hint. + gas, err := hinter.GetVariableAs[uint64](&ctx.ScopeManager, "gas") + if err != nil { + return fmt.Errorf("get gas: %v", err) + } + gasVal := mem.MemoryValueFromUint(gas) + err = vm.Memory.Write(1, vm.Context.Ap, &gasVal) + if err != nil { + return fmt.Errorf("write gas: %v", err) + } + return nil +} diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go index 92bb64487..2a3a51fde 100644 --- a/pkg/hintrunner/hintrunner.go +++ b/pkg/hintrunner/hintrunner.go @@ -4,7 +4,6 @@ import ( "fmt" h "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" - "github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" ) @@ -15,19 +14,11 @@ type HintRunner struct { hints map[uint64][]h.Hinter } -func NewHintRunner(hints map[uint64][]h.Hinter, userArgs []starknet.CairoFuncArgs) HintRunner { - context := *h.InitializeDefaultContext() - if userArgs != nil { - err := context.ScopeManager.AssignVariable("userArgs", userArgs) - // Error handling: this condition should never be true, since the context was initialized above - if err != nil { - panic(fmt.Errorf("assign userArgs: %v", err)) - } - } +func NewHintRunner(hints map[uint64][]h.Hinter, newHintRunnerContext *h.HintRunnerContext) HintRunner { return HintRunner{ // Context for certain hints that require it. Each manager is // initialized only when required by the hint - context: context, + context: *newHintRunnerContext, hints: hints, } } diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 225f92666..bef7c782e 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -43,12 +43,13 @@ type Runner struct { type CairoRunner struct{} // Creates a new Runner of a Cairo Zero program -func NewRunner(program *Program, hints map[uint64][]hinter.Hinter, runnerMode RunnerMode, collectTrace bool, maxsteps uint64, layoutName string, userArgs []starknet.CairoFuncArgs) (Runner, error) { - hintrunner := hintrunner.NewHintRunner(hints, userArgs) +func NewRunner(program *Program, hints map[uint64][]hinter.Hinter, runnerMode RunnerMode, collectTrace bool, maxsteps uint64, layoutName string, userArgs []starknet.CairoFuncArgs, availableGas uint64) (Runner, error) { layout, err := builtins.GetLayout(layoutName) if err != nil { return Runner{}, err } + newHintRunnerContext := getNewHintRunnerContext(program, userArgs, availableGas) + hintrunner := hintrunner.NewHintRunner(hints, &newHintRunnerContext) return Runner{ program: program, runnerMode: runnerMode, @@ -59,22 +60,68 @@ func NewRunner(program *Program, hints map[uint64][]hinter.Hinter, runnerMode Ru }, nil } -func AssembleProgram(cairoProgram *starknet.StarknetProgram) (Program, map[uint64][]hinter.Hinter, error) { +func getNewHintRunnerContext(program *Program, userArgs []starknet.CairoFuncArgs, availableGas uint64) hinter.HintRunnerContext { + // The writeApOffset is the offset where the user arguments will be written. It is added to the current AP in the ExternalWriteArgsToMemory hint. + // The writeApOffset is significant for Cairo programs, because of the prepended Entry Code instructions. + // In the entry code instructions the builtins bases (excluding gas, segment arena and output) are written to the memory, + // thus the writeApOffset should be increased by the number of builtins. + // In the entry code the instructions for programs utilizing the SegmentArena are also prepended. The SegmentArena is a builtin that requires 4 cells: + // * segment_arena_ptr + // * info_segment_ptr + // * 0 + // * segment_arena_ptr + 3 + // But the builtin itself shouldn't be included in len(builtins), therefore the writeApOffset should be increased by 3. + writeApOffset := uint64(len(program.Builtins)) + for _, builtin := range program.Builtins { + if builtin == builtins.SegmentArenaType { + writeApOffset += 3 + } + } + + newHintrunnerContext := *hinter.InitializeDefaultContext() + err := newHintrunnerContext.ScopeManager.AssignVariables(map[string]any{ + "userArgs": userArgs, + "apOffset": writeApOffset, + "gas": availableGas, + }) + // Error handling: this condition should never be true, since the context was initialized above + if err != nil { + panic(fmt.Sprintf("assign variables: %v", err)) + } + return newHintrunnerContext +} + +func AssembleProgram(cairoProgram *starknet.StarknetProgram, userArgs []starknet.CairoFuncArgs, availableGas uint64) (Program, map[uint64][]hinter.Hinter, []starknet.CairoFuncArgs, error) { mainFunc, ok := cairoProgram.EntryPointsByFunction["main"] if !ok { - return Program{}, nil, fmt.Errorf("cannot find main function") + return Program{}, nil, nil, fmt.Errorf("cannot find main function") + } + expectedArgsSize, actualArgsSize := 0, 0 + for _, arg := range mainFunc.InputArgs { + expectedArgsSize += arg.Size + } + for _, arg := range userArgs { + if arg.Single != nil { + actualArgsSize += 1 + } else { + actualArgsSize += 2 + } + } + if expectedArgsSize != actualArgsSize { + return Program{}, nil, nil, fmt.Errorf("missing arguments for main function") } program, err := LoadCairoProgram(cairoProgram) if err != nil { - return Program{}, nil, fmt.Errorf("cannot load program: %w", err) + return Program{}, nil, nil, fmt.Errorf("cannot load program: %w", err) } hints, err := core.GetCairoHints(cairoProgram) if err != nil { - return Program{}, nil, fmt.Errorf("cannot get hints: %w", err) + return Program{}, nil, nil, fmt.Errorf("cannot get hints: %w", err) } - entryCodeInstructions, entryCodeHints, err := GetEntryCodeInstructions(mainFunc, false) + + entryCodeInstructions, entryCodeHints, err := GetEntryCodeInstructions(mainFunc) if err != nil { - return Program{}, nil, fmt.Errorf("cannot load entry code instructions: %w", err) + return Program{}, nil, nil, fmt.Errorf("cannot load entry code instructions: %w", err) } program.Bytecode = append(entryCodeInstructions, program.Bytecode...) program.Bytecode = append(program.Bytecode, GetFooterInstructions()...) @@ -87,7 +134,7 @@ func AssembleProgram(cairoProgram *starknet.StarknetProgram) (Program, map[uint6 for key, hint := range entryCodeHints { shiftedHintsMap[key] = hint } - return *program, shiftedHintsMap, nil + return *program, shiftedHintsMap, userArgs, nil } // RunEntryPoint is like Run, but it executes the program starting from the given PC offset. @@ -517,7 +564,7 @@ func (ctx *InlineCasmContext) AddInlineCASM(code string) { ctx.currentCodeOffset += int(total_size) } -func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeForProof bool) ([]*fp.Element, map[uint64][]hinter.Hinter, error) { +func GetEntryCodeInstructions(function starknet.EntryPointByFunction) ([]*fp.Element, map[uint64][]hinter.Hinter, error) { paramTypes := function.InputArgs apOffset := 0 builtinOffset := 3 @@ -580,7 +627,6 @@ func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeFo paramsSize += param.Size } apOffset += paramsSize - usedArgs := 0 for _, builtin := range function.Builtins { if offset, isBuiltin := builtinsOffsetsMap[builtin]; isBuiltin { @@ -595,24 +641,26 @@ func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeFo ) apOffset += 1 } else if builtin == builtins.GasBuiltinType { - hints[uint64(ctx.currentCodeOffset)] = []hinter.Hinter{ - &core.ExternalWriteArgsToMemory{}, - } + hints[uint64(ctx.currentCodeOffset)] = append(hints[uint64(ctx.currentCodeOffset)], &core.ExternalWriteGasToMemory{}) ctx.AddInlineCASM("ap += 1;") apOffset += 1 - usedArgs += 1 } } + + // Incrementing the AP for the input args, because their values are written to memory by the VM in the ExternalWriteArgsToMemory hint. for _, param := range paramTypes { - offset := apOffset - usedArgs - for i := 0; i < param.Size; i++ { - ctx.AddInlineCASM( - fmt.Sprintf("[ap + 0] = [ap - %d], ap++;", offset), - ) - apOffset += param.Size - usedArgs += param.Size - } + ctx.AddInlineCASM( + fmt.Sprintf("ap+=%d;", param.Size), + ) } + + // The hint can be executed before the first instruction, because the AP correction was calculated based on the input arguments. + if paramsSize > 0 { + hints[uint64(0)] = append(hints[uint64(0)], []hinter.Hinter{ + &core.ExternalWriteArgsToMemory{}, + }...) + } + _, endInstructionsSize, err := assembler.CasmToBytecode("call rel 0; ret;") if err != nil { return nil, nil, err diff --git a/pkg/runner/runner_benchmark_test.go b/pkg/runner/runner_benchmark_test.go index 6347640e6..3903e7009 100644 --- a/pkg/runner/runner_benchmark_test.go +++ b/pkg/runner/runner_benchmark_test.go @@ -233,7 +233,7 @@ func BenchmarkRunnerWithFibonacci(b *testing.B) { panic(err) } - runner, err := NewRunner(program, hints, ProofModeZero, false, math.MaxUint64, "plain", nil) + runner, err := NewRunner(program, hints, ProofModeZero, false, math.MaxUint64, "plain", nil, 0) if err != nil { panic(err) } diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 50ded3f4d..f7c2e1117 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -27,7 +27,7 @@ func TestSimpleProgram(t *testing.T) { `) hints := make(map[uint64][]hinter.Hinter) - runner, err := NewRunner(program, hints, ExecutionModeZero, false, math.MaxUint64, "plain", nil) + runner, err := NewRunner(program, hints, ExecutionModeZero, false, math.MaxUint64, "plain", nil, 0) require.NoError(t, err) endPc, err := runner.initializeMainEntrypoint() @@ -74,7 +74,7 @@ func TestStepLimitExceeded(t *testing.T) { `) hints := make(map[uint64][]hinter.Hinter) - runner, err := NewRunner(program, hints, ExecutionModeZero, false, 3, "plain", nil) + runner, err := NewRunner(program, hints, ExecutionModeZero, false, 3, "plain", nil, 0) require.NoError(t, err) endPc, err := runner.initializeMainEntrypoint() @@ -133,7 +133,7 @@ func TestStepLimitExceededProofMode(t *testing.T) { // when maxstep = 6, it fails executing the extra step required by proof mode // when maxstep = 7, it fails trying to get the trace to be a power of 2 hints := make(map[uint64][]hinter.Hinter) - runner, err := NewRunner(program, hints, ProofModeZero, false, uint64(maxstep), "plain", nil) + runner, err := NewRunner(program, hints, ProofModeZero, false, uint64(maxstep), "plain", nil, 0) require.NoError(t, err) err = runner.Run() @@ -435,7 +435,7 @@ func TestModuloBuiltin(t *testing.T) { func createRunner(code string, layoutName string, builtins ...builtins.BuiltinType) Runner { program := createProgramWithBuiltins(code, builtins...) hints := make(map[uint64][]hinter.Hinter) - runner, err := NewRunner(program, hints, ExecutionModeZero, false, math.MaxUint64, layoutName, nil) + runner, err := NewRunner(program, hints, ExecutionModeZero, false, math.MaxUint64, layoutName, nil, 0) if err != nil { panic(err) }