Repository: NethermindEth/cairo-vm-go
Pull request #677 (2024-12-13 14:12:38)

Add available gas and user args
This PR introduces the following changes:
1. Passing the input arguments for the main func and initial gas through the flags
2. Parsing of user arguments in form of single numeric values and non-nested numeric arrays, i. e. 1 1 [1 2 3] 4 5 [6] 7 [8 9] + tests
3. Modifications of integration tests pipeline
4. Writing the args to the memory, using ExternalWriteArgsToMemory hint
5. Correction in the main function offset calculation (always 0 for cairo1)
6. Writing gas costs segment to the end of the program segment
7. Correction in calculation of the offset of the `CALL` opcode in the entry code (incremented by the offset of the main fuction)
8. Always writing user args to the `AP`, instead of `AP - len(userArgs)`


Patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 55839c5..1f41bfa 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -19,7 +19,7 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/setup-go@v4
         with:
-          go-version: '1.20'
+          go-version: '1.21'
           cache: false
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
@@ -27,7 +27,7 @@ jobs:
           # Require: The version of golangci-lint to use.
           # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version.
           # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit.
-          version: v1.53.3
+          version: v1.55.2
 
           # Optional: working directory, useful for monorepos
           # working-directory: somedir
diff --git a/Makefile b/Makefile
index 79269ac..d3c129a 100644
--- a/Makefile
+++ b/Makefile
@@ -41,31 +41,29 @@ integration:
 	@echo "Running integration tests..."
 	@$(MAKE) build
 	@if [ $$? -eq 0 ]; then \
-		if [ ! -d ./rust_vm_bin ]; then \
-			mkdir -p ./rust_vm_bin; \
+		if [ ! -d rust_vm_bin ]; then \
+			mkdir -p rust_vm_bin; \
 		fi; \
-		if [ ! -d ./rust_vm_bin/cairo ]; then \
-			mkdir -p ./rust_vm_bin/cairo; \
+		if [ ! -d rust_vm_bin/cairo ]; then \
+			mkdir -p rust_vm_bin/cairo-lang; \
 		fi; \
-		if [ ! -f ./rust_vm_bin/cairo/cairo-compile ] || [ ! -f ./rust_vm_bin/cairo/sierra-compile-json ] || [ ! -d ./rust_vm_bin/corelib ]; then \
-			cd ./rust_vm_bin/cairo; \
+		if [ ! -f ./rust_vm_bin/cairo-lang/cairo-compile ] || [ ! -f ./rust_vm_bin/cairo-lang/sierra-compile-json ] || [ ! -d rust_vm_bin/corelib ]; then \
+			cd rust_vm_bin; \
 			git clone --single-branch --branch feat/main-casm-json --depth=1 https://github.com/zmalatrax/cairo.git; \
-			mv cairo/corelib ../../rust_vm_bin/; \
-			cd cairo/crates/bin && \
-			cargo build --release --bin cairo-compile --bin sierra-compile-json && \
-			cd ../../../; \
-			mv cairo/target/release/cairo-compile cairo/target/release/sierra-compile-json ../cairo/ && \
-			rm -rf ./cairo; \
+			mv cairo/corelib .; \
+			cd cairo/crates/bin && cargo build --release --bin cairo-compile --bin sierra-compile-json && cd ../../../; \
+			mv cairo/target/release/cairo-compile cairo/target/release/sierra-compile-json cairo-lang; \
+			rm -rf cairo; \
+			cd ../; \
 		fi; \
-		if [ ! -f ./rust_vm_bin/cairo/cairo1-run ] || [ ! -f ./rust_vm_bin/cairo-vm-cli ]; then \
-			cd ./rust_vm_bin; \
-			git clone https://github.com/lambdaclass/cairo-vm.git && \
-			cd cairo-vm/; \
-			cargo build --release --bin cairo-vm-cli --bin cairo1-run; \
-			mv cairo-vm/target/release/cairo1-run ./cairo/ && \
-			mv cairo-vm/target/release/cairo-vm-cli ../rust_vm_bin/ && \
+		if [ ! -f ./rust_vm_bin/cairo-lang/cairo1-run ] || [ ! -f ./rust_vm_bin/cairo-vm-cli ]; then \
+			cd rust_vm_bin; \
+			git clone https://github.com/lambdaclass/cairo-vm.git; \
+			cd cairo-vm && cargo build --release --bin cairo-vm-cli --bin cairo1-run && cd ../; \
+			mv cairo-vm/target/release/cairo1-run cairo-lang;\
+			mv cairo-vm/target/release/cairo-vm-cli . ; \
 			rm -rf cairo-vm; \
-			cd ../../; \
+			cd ../; \
 		fi; \
 		go test ./integration_tests/... -v; \
 	else \
diff --git a/cmd/cli/main.go b/cmd/cli/main.go
index 082c364..151b4a2 100644
--- a/cmd/cli/main.go
+++ b/cmd/cli/main.go
@@ -12,6 +12,7 @@ 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"
 )
 
@@ -26,6 +27,8 @@ func main() {
 	var layoutName string
 	var airPublicInputLocation string
 	var airPrivateInputLocation string
+	var args string
+	var availableGas uint64
 	app := &cli.App{
 		Name:                 "cairo-vm",
 		Usage:                "A cairo virtual machine",
@@ -122,7 +125,7 @@ func main() {
 					if proofmode {
 						runnerMode = runner.ProofModeCairo0
 					}
-					return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode)
+					return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, nil)
 				},
 			},
 			{
@@ -191,6 +194,18 @@ func main() {
 						Required:    false,
 						Destination: &airPrivateInputLocation,
 					},
+					&cli.StringFlag{
+						Name:        "args",
+						Usage:       "input arguments for the `main` function in the cairo progran",
+						Required:    false,
+						Destination: &args,
+					},
+					&cli.Uint64Flag{
+						Name:        "available_gas",
+						Usage:       "available gas for the VM execution",
+						Required:    false,
+						Destination: &availableGas,
+					},
 				},
 				Action: func(ctx *cli.Context) error {
 					pathToFile := ctx.Args().Get(0)
@@ -210,7 +225,19 @@ func main() {
 					if proofmode {
 						runnerMode = runner.ProofModeCairo1
 					}
-					return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode)
+					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)
 				},
 			},
 		},
@@ -236,9 +263,10 @@ func runVM(
 	airPrivateInputLocation string,
 	hints map[uint64][]hinter.Hinter,
 	runnerMode runner.RunnerMode,
+	userArgs []starknet.CairoFuncArgs,
 ) error {
 	fmt.Println("Running....")
-	runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName)
+	runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName, userArgs)
 	if err != nil {
 		return fmt.Errorf("cannot create runner: %w", err)
 	}
diff --git a/pkg/hintrunner/core/hint.go b/pkg/hintrunner/core/hint.go
index 3efa7a9..202c689 100644
--- a/pkg/hintrunner/core/hint.go
+++ b/pkg/hintrunner/core/hint.go
@@ -1929,3 +1929,38 @@ func (hint *FieldSqrt) Execute(vm *VM.VirtualMachine, _ *hinter.HintRunnerContex
 
 	return vm.Memory.WriteToAddress(&sqrtAddr, &sqrtVal)
 }
+
+type ExternalWriteArgsToMemory struct{}
+
+func (hint *ExternalWriteArgsToMemory) String() string {
+	return "ExternalWriteToMemory"
+}
+
+func (hint *ExternalWriteArgsToMemory) Execute(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error {
+	userArgsVar, err := ctx.ScopeManager.GetVariableValue("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")
+	}
+	for _, arg := range userArgs {
+		if arg.Single != nil {
+			mv := mem.MemoryValueFromFieldElement(arg.Single)
+			err := vm.Memory.Write(1, vm.Context.Ap, &mv)
+			if err != nil {
+				return fmt.Errorf("write single arg: %v", err)
+			}
+		} else if arg.Array != nil {
+			arrayBase := vm.Memory.AllocateEmptySegment()
+			mv := mem.MemoryValueFromMemoryAddress(&arrayBase)
+			err := vm.Memory.Write(1, vm.Context.Ap, &mv)
+			if err != nil {
+				return fmt.Errorf("write array base: %v", err)
+			}
+			// TODO: Implement array writing
+		}
+	}
+	return nil
+}
diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go
index f8e54eb..c1a209e 100644
--- a/pkg/hintrunner/hintrunner.go
+++ b/pkg/hintrunner/hintrunner.go
@@ -4,6 +4,7 @@ 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"
 )
 
@@ -14,11 +15,18 @@ type HintRunner struct {
 	hints map[uint64][]h.Hinter
 }
 
-func NewHintRunner(hints map[uint64][]h.Hinter) HintRunner {
+func NewHintRunner(hints map[uint64][]h.Hinter, userArgs []starknet.CairoFuncArgs) HintRunner {
+	context := *h.InitializeDefaultContext()
+	if userArgs != nil {
+		err := context.ScopeManager.AssignVariable("userArgs", userArgs)
+		if err != nil {
+			panic(fmt.Errorf("assign userArgs: %v", err))
+		}
+	}
 	return HintRunner{
 		// Context for certain hints that require it. Each manager is
 		// initialized only when required by the hint
-		context: *h.InitializeDefaultContext(),
+		context: context,
 		hints:   hints,
 	}
 }
diff --git a/pkg/parsers/starknet/args.go b/pkg/parsers/starknet/args.go
new file mode 100644
index 0000000..0e7d284
--- /dev/null
+++ b/pkg/parsers/starknet/args.go
@@ -0,0 +1,56 @@
+package starknet
+
+import (
+	"fmt"
+	"regexp"
+	"strings"
+
+	"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
+)
+
+type CairoFuncArgs struct {
+	Single *fp.Element
+	Array  []fp.Element
+}
+
+func ParseCairoProgramArgs(input string) ([]CairoFuncArgs, error) {
+	re := regexp.MustCompile(`\[[^\]]*\]|\S+`)
+	tokens := re.FindAllString(input, -1)
+	var result []CairoFuncArgs
+
+	parseValueToFelt := func(token string) (*fp.Element, error) {
+		felt, err := new(fp.Element).SetString(token)
+		if err != nil {
+			return nil, fmt.Errorf("invalid felt value: %v", err)
+		}
+		return felt, nil
+	}
+
+	for _, token := range tokens {
+		if single, err := parseValueToFelt(token); err == nil {
+			result = append(result, CairoFuncArgs{
+				Single: single,
+				Array:  nil,
+			})
+		} else if strings.HasPrefix(token, "[") && strings.HasSuffix(token, "]") {
+			arrayStr := strings.Trim(token, "[]")
+			arrayElements := strings.Fields(arrayStr)
+			array := make([]fp.Element, len(arrayElements))
+			for i, element := range arrayElements {
+				single, err := parseValueToFelt(element)
+				if err != nil {
+					return nil, fmt.Errorf("invalid felt value in array: %v", err)
+				}
+				array[i] = *single
+			}
+			result = append(result, CairoFuncArgs{
+				Single: nil,
+				Array:  array,
+			})
+		} else {
+			return nil, fmt.Errorf("invalid token: %s", token)
+		}
+	}
+
+	return result, nil
+}
diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go
index bac6dae..f3228b1 100644
--- a/pkg/runner/runner.go
+++ b/pkg/runner/runner.go
@@ -42,8 +42,8 @@ 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) (Runner, error) {
-	hintrunner := hintrunner.NewHintRunner(hints)
+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)
 	layout, err := builtins.GetLayout(layoutName)
 	if err != nil {
 		return Runner{}, err
@@ -67,22 +67,25 @@ func AssembleProgram(cairoProgram *starknet.StarknetProgram) (Program, map[uint6
 	if err != nil {
 		return Program{}, nil, fmt.Errorf("cannot load program: %w", err)
 	}
-	entryCodeInstructions, err := GetEntryCodeInstructions(mainFunc, false, 0)
+	hints, err := core.GetCairoHints(cairoProgram)
+	if err != nil {
+		return Program{}, nil, fmt.Errorf("cannot get hints: %w", err)
+	}
+	entryCodeInstructions, entryCodeHints, err := GetEntryCodeInstructions(mainFunc, false)
 	if err != nil {
 		return Program{}, nil, fmt.Errorf("cannot load entry code instructions: %w", err)
 	}
 	program.Bytecode = append(entryCodeInstructions, program.Bytecode...)
 	program.Bytecode = append(program.Bytecode, GetFooterInstructions()...)
 
-	hints, err := core.GetCairoHints(cairoProgram)
-	if err != nil {
-		return Program{}, nil, fmt.Errorf("cannot get hints: %w", err)
-	}
 	offset := uint64(len(entryCodeInstructions))
 	shiftedHintsMap := make(map[uint64][]hinter.Hinter)
 	for key, value := range hints {
 		shiftedHintsMap[key+offset] = value
 	}
+	for key, hint := range entryCodeHints {
+		shiftedHintsMap[key] = hint
+	}
 	return *program, shiftedHintsMap, nil
 }
 
@@ -115,10 +118,6 @@ func (runner *Runner) RunEntryPoint(pc uint64) error {
 	if err != nil {
 		return err
 	}
-	err = runner.loadArguments(uint64(0), uint64(8979879877))
-	if err != nil {
-		return err
-	}
 	if err := runner.RunUntilPc(&end); err != nil {
 		return err
 	}
@@ -136,10 +135,6 @@ func (runner *Runner) Run() error {
 		return fmt.Errorf("initializing main entry point: %w", err)
 	}
 
-	err = runner.loadArguments(uint64(0), uint64(8979879877))
-	if err != nil {
-		return err
-	}
 	err = runner.RunUntilPc(&end)
 	if err != nil {
 		return err
@@ -181,10 +176,8 @@ func (runner *Runner) initializeMainEntrypoint() (mem.MemoryAddress, error) {
 	case ExecutionMode, ProofModeCairo1:
 		returnFp := memory.AllocateEmptySegment()
 		mvReturnFp := mem.MemoryValueFromMemoryAddress(&returnFp)
-		mainPCOffset, ok := runner.program.Entrypoints["main"]
-		if !ok {
-			return mem.UnknownAddress, errors.New("can't find an entrypoint for main")
-		}
+		// In Cairo mainPCOffset is equal to the offset of program segment base
+		mainPCOffset := uint64(0)
 		if runner.runnerMode == ExecutionMode {
 			return runner.initializeEntrypoint(mainPCOffset, nil, &mvReturnFp, memory, stack, 0)
 		} else {
@@ -281,7 +274,16 @@ func (runner *Runner) initializeVm(
 			return err
 		}
 	}
-
+	// Write builtins costs segment address to the end of the program segment
+	if runner.runnerMode == ProofModeCairo1 || runner.runnerMode == ExecutionMode {
+		builtinsCostSegmentAddress := memory.AllocateEmptySegment()
+		mv := mem.MemoryValueFromMemoryAddress(&builtinsCostSegmentAddress)
+		programSegment := memory.Segments[vm.ProgramSegment]
+		err := memory.Write(0, programSegment.Len(), &mv)
+		if err != nil {
+			return err
+		}
+	}
 	initialFp := offset + uint64(len(stack)) + cairo1FpOffset
 	var err error
 	// initialize vm
@@ -296,12 +298,6 @@ func (runner *Runner) initializeVm(
 	return err
 }
 
-func (runner *Runner) loadArguments(args, initialGas uint64) error {
-	mv := mem.MemoryValueFromUint(initialGas)
-	runner.vm.Memory.Segments[vm.ExecutionSegment].Write(runner.vm.Context.Ap+1, &mv)
-	return nil
-}
-
 // run until the program counter equals the `pc` parameter
 func (runner *Runner) RunUntilPc(pc *mem.MemoryAddress) error {
 	for !runner.vm.Context.Pc.Equal(pc) {
@@ -506,11 +502,11 @@ func (ctx *InlineCasmContext) AddInlineCASM(code string) {
 	ctx.currentCodeOffset += int(total_size)
 }
 
-func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeForProof bool, initialGas uint64) ([]*fp.Element, error) {
+func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeForProof bool) ([]*fp.Element, map[uint64][]hinter.Hinter, error) {
 	paramTypes := function.InputArgs
 	apOffset := 0
 	builtinOffset := 3
-
+	codeOffset := uint64(function.Offset)
 	builtinsOffsetsMap := map[builtins.BuiltinType]int{}
 	emulatedBuiltins := map[builtins.BuiltinType]struct{}{
 		builtins.SystemType: {},
@@ -539,7 +535,7 @@ func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeFo
 	}
 	apOffset += paramsSize
 	usedArgs := 0
-
+	var hints map[uint64][]hinter.Hinter
 	for _, builtin := range function.Builtins {
 		if offset, isBuiltin := builtinsOffsetsMap[builtin]; isBuiltin {
 			ctx.AddInlineCASM(
@@ -561,11 +557,12 @@ func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeFo
 			)
 			apOffset += 1
 		} else if builtin == builtins.GasBuiltinType {
-			ctx.AddInlineCASM(
-				`
-					ap += 1;
-				`,
-			)
+			hints = map[uint64][]hinter.Hinter{
+				uint64(ctx.currentCodeOffset): {
+					&core.ExternalWriteArgsToMemory{},
+				},
+			}
+			ctx.AddInlineCASM("ap += 1;")
 			apOffset += 1
 			usedArgs += 1
 		}
@@ -580,10 +577,13 @@ func GetEntryCodeInstructions(function starknet.EntryPointByFunction, finalizeFo
 			usedArgs += param.Size
 		}
 	}
-	ctx.AddInlineCASM(fmt.Sprintf("call rel %d;", apOffset+1))
-	ctx.AddInlineCASM("ret;")
-
-	return ctx.instructions, nil
+	_, endInstructionsSize, err := assembler.CasmToBytecode("call rel 0; ret;")
+	if err != nil {
+		return nil, nil, err
+	}
+	totalSize := uint64(endInstructionsSize) + uint64(codeOffset)
+	ctx.AddInlineCASM(fmt.Sprintf("call rel %d; ret;", int(totalSize)))
+	return ctx.instructions, hints, nil
 }
 
 func GetFooterInstructions() []*fp.Element {
diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go
index 65c6ab8..7673f6a 100644
--- a/pkg/vm/vm.go
+++ b/pkg/vm/vm.go
@@ -91,6 +91,14 @@ type VirtualMachine struct {
 	RcLimitsMax uint16
 }
 
+func (vm *VirtualMachine) PrintMemory() {
+	for i := range vm.Memory.Segments {
+		for j, cell := range vm.Memory.Segments[i].Data {
+			fmt.Printf("%d:%d %s\n", i, j, cell)
+		}
+	}
+}
+
 // NewVirtualMachine creates a VM from the program bytecode using a specified config.
 func NewVirtualMachine(
 	initialContext Context, memory *mem.Memory, config VirtualMachineConfig,


Test patch:
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 1ef9abe..a8e009f 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -15,7 +15,7 @@ jobs:
         - name: Set up Go
           uses: actions/setup-go@v4
           with:
-            go-version: "1.20"
+            go-version: "1.21"
 
         - name: Build
           run: make build
diff --git a/integration_tests/cairo_1_programs/dict_with_struct.cairo b/integration_tests/cairo_1_programs/dict_with_struct__small.cairo
similarity index 100%
rename from integration_tests/cairo_1_programs/dict_with_struct.cairo
rename to integration_tests/cairo_1_programs/dict_with_struct__small.cairo
diff --git a/integration_tests/cairo_1_programs/dictionaries.cairo b/integration_tests/cairo_1_programs/dictionaries__small.cairo
similarity index 100%
rename from integration_tests/cairo_1_programs/dictionaries.cairo
rename to integration_tests/cairo_1_programs/dictionaries__small.cairo
diff --git a/integration_tests/cairo_1_programs/sample.cairo b/integration_tests/cairo_1_programs/sample__small.cairo
similarity index 100%
rename from integration_tests/cairo_1_programs/sample.cairo
rename to integration_tests/cairo_1_programs/sample__small.cairo
diff --git a/integration_tests/cairo_vm_test.go b/integration_tests/cairo_vm_test.go
index 4c940cc..c38a14c 100644
--- a/integration_tests/cairo_vm_test.go
+++ b/integration_tests/cairo_vm_test.go
@@ -459,16 +459,18 @@ func runVm(path, layout string, zero bool) (time.Duration, string, string, strin
 	}
 	args := []string{
 		cliCommand,
-		"--proofmode",
+		// "--proofmode",
 		"--tracefile",
 		traceOutput,
 		"--memoryfile",
 		memoryOutput,
 		"--layout",
 		layout,
-		path,
 	}
-
+	if !zero {
+		args = append(args, "--available_gas", "9999999")
+	}
+	args = append(args, path)
 	cmd := exec.Command(
 		"../bin/cairo-vm",
 		args...,
diff --git a/pkg/hintrunner/hintrunner_test.go b/pkg/hintrunner/hintrunner_test.go
index 0e057de..c3cbbd5 100644
--- a/pkg/hintrunner/hintrunner_test.go
+++ b/pkg/hintrunner/hintrunner_test.go
@@ -20,7 +20,7 @@ func TestExistingHint(t *testing.T) {
 
 	hr := NewHintRunner(map[uint64][]hinter.Hinter{
 		10: {&allocHint},
-	})
+	}, nil)
 
 	vm.Context.Pc = memory.MemoryAddress{
 		SegmentIndex: 0,
@@ -44,7 +44,7 @@ func TestNoHint(t *testing.T) {
 
 	hr := NewHintRunner(map[uint64][]hinter.Hinter{
 		10: {&allocHint},
-	})
+	}, nil)
 
 	vm.Context.Pc = memory.MemoryAddress{
 		SegmentIndex: 0,
diff --git a/pkg/parsers/starknet/args_test.go b/pkg/parsers/starknet/args_test.go
new file mode 100644
index 0000000..f6bdd6f
--- /dev/null
+++ b/pkg/parsers/starknet/args_test.go
@@ -0,0 +1,115 @@
+package starknet
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestParseStarknetProgramArgs(t *testing.T) {
+	testCases := []struct {
+		name     string
+		args     string
+		expected []CairoFuncArgs
+		err      error
+	}{
+		{
+			name: "single arg",
+			args: "1",
+			expected: []CairoFuncArgs{
+				{
+					Single: new(fp.Element).SetUint64(1),
+					Array:  nil,
+				},
+			},
+			err: nil,
+		},
+		{
+			name: "single array arg",
+			args: "[1 2 3 4]",
+			expected: []CairoFuncArgs{
+				{
+					Single: nil,
+					Array: []fp.Element{
+						*new(fp.Element).SetUint64(1),
+						*new(fp.Element).SetUint64(2),
+						*new(fp.Element).SetUint64(3),
+						*new(fp.Element).SetUint64(4),
+					},
+				},
+			},
+		},
+		{
+			name:     "nested array arg",
+			args:     "[1 [2 3 4]]",
+			expected: nil,
+			err:      fmt.Errorf("invalid felt value in array: invalid felt value: Element.SetString failed -> can't parse number into a big.Int [2"),
+		},
+		{
+			name: "mixed args",
+			args: "1 [2 3 4] 5 [6 7 8] [1] 9 9 [12341341234 0]",
+			expected: []CairoFuncArgs{
+				{
+					Single: new(fp.Element).SetUint64(1),
+					Array:  nil,
+				},
+				{
+					Single: nil,
+					Array: []fp.Element{
+						*new(fp.Element).SetUint64(2),
+						*new(fp.Element).SetUint64(3),
+						*new(fp.Element).SetUint64(4),
+					},
+				},
+				{
+					Single: new(fp.Element).SetUint64(5),
+					Array:  nil,
+				},
+				{
+					Single: nil,
+					Array: []fp.Element{
+						*new(fp.Element).SetUint64(6),
+						*new(fp.Element).SetUint64(7),
+						*new(fp.Element).SetUint64(8),
+					},
+				},
+				{
+					Single: nil,
+					Array: []fp.Element{
+						*new(fp.Element).SetUint64(1),
+					},
+				},
+				{
+					Single: new(fp.Element).SetUint64(9),
+					Array:  nil,
+				},
+				{
+					Single: new(fp.Element).SetUint64(9),
+					Array:  nil,
+				},
+				{
+					Single: nil,
+					Array: []fp.Element{
+						*new(fp.Element).SetUint64(12341341234),
+						*new(fp.Element).SetUint64(0),
+					},
+				},
+			},
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			args, err := ParseCairoProgramArgs(testCase.args)
+			if testCase.err != nil {
+				require.Error(t, err)
+				assert.Equal(t, testCase.err.Error(), err.Error())
+				return
+			}
+			require.NoError(t, err)
+			assert.Equal(t, testCase.expected, args)
+		})
+	}
+}
diff --git a/pkg/runner/runner_benchmark_test.go b/pkg/runner/runner_benchmark_test.go
index d86be97..64bc7b8 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, ProofModeCairo0, false, math.MaxUint64, "plain")
+		runner, err := NewRunner(program, hints, ProofModeCairo0, false, math.MaxUint64, "plain", nil)
 		if err != nil {
 			panic(err)
 		}
diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go
index 8c876d6..f94834c 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, ExecutionMode, false, math.MaxUint64, "plain")
+	runner, err := NewRunner(program, hints, ExecutionMode, false, math.MaxUint64, "plain", nil)
 	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, ExecutionMode, false, 3, "plain")
+	runner, err := NewRunner(program, hints, ExecutionMode, false, 3, "plain", nil)
 	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, ProofModeCairo0, false, uint64(maxstep), "plain")
+		runner, err := NewRunner(program, hints, ProofModeCairo0, false, uint64(maxstep), "plain", nil)
 		require.NoError(t, err)
 
 		err = runner.Run()
@@ -436,7 +436,7 @@ func createRunner(code string, layoutName string, builtins ...builtins.BuiltinTy
 	program := createProgramWithBuiltins(code, builtins...)
 
 	hints := make(map[uint64][]hinter.Hinter)
-	runner, err := NewRunner(program, hints, ExecutionMode, false, math.MaxUint64, layoutName)
+	runner, err := NewRunner(program, hints, ExecutionMode, false, math.MaxUint64, layoutName, nil)
 	if err != nil {
 		panic(err)
 	}