Skip to content

Commit 5ada20b

Browse files
Add functionalities for main function inputs (#681)
* Fixes for the generation of entry code, fixes of hints parsing * Add modifications to the runner * Add fixes for the entrycode generation * Refactor main CLI, offset the hints indexes by entry code size, load arguments and initial gas to the memory * Add available gas and user args (#677) * Add parsing logic for input user args * Add flags for available gas, input user args, writing args to memory * Fix unit tests for user arguments parsing * Lint the PR * Add user args to hint context * Refactor the code * Fix unconditional append of ExternalWriteArgsToMemory, bug fixes in integration tests * Add fixes of the call size calculation and include ExternalWriteArgsToMemory hint when gas present * Add layouts for integration tests * Add error handling * Fixes in entry code generation * Address changes mentioned in a discussion * Add comment regarding writing to memory in a hint for the future reference in the integration tests with args * Changes in calculations of the initial PC offset, CALL opcode offset incremented by mainFuncOffset, writing user args to the AP in the hint * Turn back VM config to private field * Add error handling on assign of `userArgs` to the initial scope * Lint project * Bump go version from 1.20 -> 1.21 (#678) * Bump go version from 1.20 -> 1.21 * Update golangci-lint * Simplify the Makefile * Correction in the makefile * Fix the integration tests * Fixes in the runner * Fixes in the runner * Fix the unit tests, uncomment pythonVm execution in integration tests, code cleanups * Add writing tokens gas cost to memory * Proper builtins initialization for cairo mode * Address comments in the PR * Fix bugs regarding dicts * Remove prints * Fixes of the last tests for the dicts * Add dict_non_squashed dir to the integration tests * Add checks for the matching args size, rename files, modify the integration tests pipeline * Almost all pass * Fix lint and unit tests * Fix loading gas to the memory as an argument * Fix run for tensor__small.cairo * Bring back cairo0 integration tests * Code refactoring, add comments * Fix the CI
1 parent 8aff6b7 commit 5ada20b

File tree

11 files changed

+172
-80
lines changed

11 files changed

+172
-80
lines changed

cmd/cli/main.go

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet"
1313
zero "github.com/NethermindEth/cairo-vm-go/pkg/parsers/zero"
1414
"github.com/NethermindEth/cairo-vm-go/pkg/runner"
15-
"github.com/consensys/gnark-crypto/ecc/stark-curve/fp"
1615
"github.com/urfave/cli/v2"
1716
)
1817

@@ -125,7 +124,7 @@ func main() {
125124
if proofmode {
126125
runnerMode = runner.ProofModeZero
127126
}
128-
return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, nil)
127+
return runVM(*program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, nil, 0)
129128
},
130129
},
131130
{
@@ -217,27 +216,19 @@ func main() {
217216
if err != nil {
218217
return fmt.Errorf("cannot load program: %w", err)
219218
}
220-
program, hints, err := runner.AssembleProgram(cairoProgram)
219+
userArgs, err := starknet.ParseCairoProgramArgs(args)
220+
if err != nil {
221+
return fmt.Errorf("cannot parse args: %w", err)
222+
}
223+
program, hints, userArgs, err := runner.AssembleProgram(cairoProgram, userArgs, availableGas)
221224
if err != nil {
222225
return fmt.Errorf("cannot assemble program: %w", err)
223226
}
224227
runnerMode := runner.ExecutionModeCairo
225228
if proofmode {
226229
runnerMode = runner.ProofModeCairo
227230
}
228-
userArgs, err := starknet.ParseCairoProgramArgs(args)
229-
if err != nil {
230-
return fmt.Errorf("cannot parse args: %w", err)
231-
}
232-
if availableGas > 0 {
233-
// The first argument is the available gas
234-
availableGasArg := starknet.CairoFuncArgs{
235-
Single: new(fp.Element).SetUint64(availableGas),
236-
Array: nil,
237-
}
238-
userArgs = append([]starknet.CairoFuncArgs{availableGasArg}, userArgs...)
239-
}
240-
return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, userArgs)
231+
return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, userArgs, availableGas)
241232
},
242233
},
243234
},
@@ -264,9 +255,10 @@ func runVM(
264255
hints map[uint64][]hinter.Hinter,
265256
runnerMode runner.RunnerMode,
266257
userArgs []starknet.CairoFuncArgs,
258+
availableGas uint64,
267259
) error {
268260
fmt.Println("Running....")
269-
runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName, userArgs)
261+
runner, err := runner.NewRunner(&program, hints, runnerMode, collectTrace, maxsteps, layoutName, userArgs, availableGas)
270262
if err != nil {
271263
return fmt.Errorf("cannot create runner: %w", err)
272264
}

integration_tests/cairo_vm_test.go

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (f *Filter) filtered(testFile string) bool {
6666
return false
6767
}
6868

69-
func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[string][3]int, benchmark bool, errorExpected bool, zero bool) {
69+
func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[string][3]int, benchmark bool, errorExpected bool, zero bool, inputArgs string) {
7070
t.Logf("testing: %s\n", path)
7171
compiledOutput, err := compileCairoCode(path, zero)
7272
if err != nil {
@@ -75,7 +75,7 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str
7575
}
7676
layout := getLayoutFromFileName(path)
7777

78-
elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero)
78+
elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero, inputArgs)
7979
if errorExpected {
8080
assert.Error(t, err, path)
8181
writeToFile(path)
@@ -92,7 +92,7 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str
9292
if zero {
9393
rustVmFilePath = compiledOutput
9494
}
95-
elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero)
95+
elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero, inputArgs)
9696
if errorExpected {
9797
// we let the code go on so that we can check if the go vm also raises an error
9898
assert.Error(t, err, path)
@@ -171,16 +171,28 @@ func TestCairoFiles(t *testing.T) {
171171
if err != nil {
172172
panic(fmt.Errorf("failed to open file: %w", err))
173173
}
174+
174175
file.Close()
175-
roots := []struct {
176+
type TestCase struct {
176177
path string
177178
zero bool
178-
}{
179+
}
180+
roots := []TestCase{
179181
{"./cairo_zero_hint_tests/", true},
180182
{"./cairo_zero_file_tests/", true},
181183
{"./builtin_tests/", true},
182184
// {"./cairo_1_programs/", false},
183185
// {"./cairo_1_programs/dict_non_squashed", false},
186+
// {"./cairo_1_programs/with_input", false},
187+
}
188+
189+
// 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.
190+
inputArgsMap := map[string]string{
191+
"cairo_1_programs/with_input/array_input_sum__small.cairo": "2 [111 222 333] 1 [444 555 666 777]",
192+
"cairo_1_programs/with_input/array_length__small.cairo": "[1 2 3 4 5 6] [7 8 9 10]",
193+
"cairo_1_programs/with_input/branching.cairo": "123",
194+
"cairo_1_programs/with_input/dict_with_input__small.cairo": "[1 2 3 4]",
195+
"cairo_1_programs/with_input/tensor__small.cairo": "[1 4] [1 5]",
184196
}
185197

186198
// filter is for debugging purposes
@@ -211,22 +223,19 @@ func TestCairoFiles(t *testing.T) {
211223
if !filter.filtered(name) {
212224
continue
213225
}
214-
226+
inputArgs := inputArgsMap[path]
215227
// we run tests concurrently if we don't need benchmarks
216228
if !*zerobench {
217229
sem <- struct{}{} // acquire a semaphore slot
218230
wg.Add(1)
219231

220-
go func(path, name string, root struct {
221-
path string
222-
zero bool
223-
}) {
232+
go func(path, name string, root TestCase, inputArgs string) {
224233
defer wg.Done()
225234
defer func() { <-sem }() // release the semaphore slot when done
226-
runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero)
227-
}(path, name, root)
235+
runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero, inputArgs)
236+
}(path, name, root, inputArgs)
228237
} else {
229-
runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero)
238+
runAndTestFile(t, path, name, benchmarkMap, *zerobench, errorExpected, root.zero, inputArgs)
230239
}
231240
}
232241
}
@@ -400,7 +409,7 @@ func runPythonVm(path, layout string) (time.Duration, string, string, error) {
400409

401410
// given a path to a compiled cairo zero file, execute it using the
402411
// rust vm and return the trace and memory files location
403-
func runRustVm(path, layout string, zero bool) (time.Duration, string, string, error) {
412+
func runRustVm(path, layout string, zero bool, inputArgs string) (time.Duration, string, string, error) {
404413
traceOutput := swapExtenstion(path, rsTraceSuffix)
405414
memoryOutput := swapExtenstion(path, rsMemorySuffix)
406415

@@ -418,9 +427,11 @@ func runRustVm(path, layout string, zero bool) (time.Duration, string, string, e
418427
args = append(args, "--proof_mode")
419428
}
420429

421-
binaryPath := "./../rust_vm_bin/cairo-lang/cairo1-run"
422-
if zero {
423-
binaryPath = "./../rust_vm_bin/cairo-vm-cli"
430+
binaryPath := "./../rust_vm_bin/cairo-vm-cli"
431+
432+
if !zero {
433+
args = append(args, "--args", inputArgs)
434+
binaryPath = "./../rust_vm_bin/cairo-lang/cairo1-run"
424435
}
425436
cmd := exec.Command(binaryPath, args...)
426437

@@ -441,7 +452,7 @@ func runRustVm(path, layout string, zero bool) (time.Duration, string, string, e
441452

442453
// given a path to a compiled cairo zero file, execute
443454
// it using our vm
444-
func runVm(path, layout string, zero bool) (time.Duration, string, string, string, error) {
455+
func runVm(path, layout string, zero bool, inputArgs string) (time.Duration, string, string, string, error) {
445456
traceOutput := swapExtenstion(path, traceSuffix)
446457
memoryOutput := swapExtenstion(path, memorySuffix)
447458

@@ -471,6 +482,8 @@ func runVm(path, layout string, zero bool) (time.Duration, string, string, strin
471482
layout,
472483
"--available_gas",
473484
"9999999",
485+
"--args",
486+
inputArgs,
474487
}
475488
}
476489
args = append(args, path)

pkg/hintrunner/core/hint.go

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1937,30 +1937,75 @@ func (hint *ExternalWriteArgsToMemory) String() string {
19371937
}
19381938

19391939
func (hint *ExternalWriteArgsToMemory) Execute(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error {
1940-
userArgsVar, err := ctx.ScopeManager.GetVariableValue("userArgs")
1940+
userArgs, err := hinter.GetVariableAs[[]starknet.CairoFuncArgs](&ctx.ScopeManager, "userArgs")
19411941
if err != nil {
19421942
return fmt.Errorf("get user args: %v", err)
19431943
}
1944-
userArgs, ok := userArgsVar.([]starknet.CairoFuncArgs)
1945-
if !ok {
1946-
return fmt.Errorf("expected user args to be a list of CairoFuncArgs")
1944+
// The apOffset is the AP correction, which represents the memory slots taken up by the values created by the entry code instructions.
1945+
// It is calculated in the `getNewHintRunnerContext()` method.
1946+
apOffset, err := hinter.GetVariableAs[uint64](&ctx.ScopeManager, "apOffset")
1947+
if err != nil {
1948+
return fmt.Errorf("get ap offset: %v", err)
19471949
}
1950+
apOffset += vm.Context.Ap
19481951
for _, arg := range userArgs {
19491952
if arg.Single != nil {
19501953
mv := mem.MemoryValueFromFieldElement(arg.Single)
1951-
err := vm.Memory.Write(1, vm.Context.Ap, &mv)
1954+
err := vm.Memory.Write(1, apOffset, &mv)
19521955
if err != nil {
19531956
return fmt.Errorf("write single arg: %v", err)
19541957
}
1958+
apOffset++
19551959
} else if arg.Array != nil {
1960+
// The array is stored in memory as follows:
1961+
// Each array gets assigned a new segment (the pointer is stored in the arrayBase).
1962+
// arrayBase and arrayEnd pointers are written to the Execution Segment consecutively.
1963+
// Then, the array elements are written to the newly created array segment.
19561964
arrayBase := vm.Memory.AllocateEmptySegment()
19571965
mv := mem.MemoryValueFromMemoryAddress(&arrayBase)
1958-
err := vm.Memory.Write(1, vm.Context.Ap, &mv)
1966+
err := vm.Memory.Write(1, apOffset, &mv)
19591967
if err != nil {
19601968
return fmt.Errorf("write array base: %v", err)
19611969
}
1962-
// TODO: Implement array writing
1970+
apOffset++
1971+
arrayEnd := arrayBase
1972+
for _, val := range arg.Array {
1973+
mv := mem.MemoryValueFromFieldElement(&val)
1974+
err := vm.Memory.Write(arrayEnd.SegmentIndex, arrayEnd.Offset, &mv)
1975+
if err != nil {
1976+
return fmt.Errorf("write array element: %v", err)
1977+
}
1978+
arrayEnd.Offset += 1
1979+
}
1980+
mv = mem.MemoryValueFromMemoryAddress(&arrayEnd)
1981+
err = vm.Memory.Write(1, apOffset, &mv)
1982+
if err != nil {
1983+
return fmt.Errorf("write array end: %v", err)
1984+
}
1985+
apOffset++
19631986
}
19641987
}
19651988
return nil
19661989
}
1990+
1991+
type ExternalWriteGasToMemory struct{}
1992+
1993+
func (hint *ExternalWriteGasToMemory) String() string {
1994+
return "ExternalWriteGasToMemory"
1995+
}
1996+
1997+
func (hint *ExternalWriteGasToMemory) Execute(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error {
1998+
// ExternalWriteGasToMemory is a separate hint, that writes the gas value to the memory.
1999+
// 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.
2000+
// Therefore the writing of the gas value to the memory is done in a separate hint.
2001+
gas, err := hinter.GetVariableAs[uint64](&ctx.ScopeManager, "gas")
2002+
if err != nil {
2003+
return fmt.Errorf("get gas: %v", err)
2004+
}
2005+
gasVal := mem.MemoryValueFromUint(gas)
2006+
err = vm.Memory.Write(1, vm.Context.Ap, &gasVal)
2007+
if err != nil {
2008+
return fmt.Errorf("write gas: %v", err)
2009+
}
2010+
return nil
2011+
}

pkg/hintrunner/hintrunner.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"fmt"
55

66
h "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter"
7-
"github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet"
87
VM "github.com/NethermindEth/cairo-vm-go/pkg/vm"
98
)
109

@@ -15,19 +14,14 @@ type HintRunner struct {
1514
hints map[uint64][]h.Hinter
1615
}
1716

18-
func NewHintRunner(hints map[uint64][]h.Hinter, userArgs []starknet.CairoFuncArgs) HintRunner {
19-
context := *h.InitializeDefaultContext()
20-
if userArgs != nil {
21-
err := context.ScopeManager.AssignVariable("userArgs", userArgs)
22-
// Error handling: this condition should never be true, since the context was initialized above
23-
if err != nil {
24-
panic(fmt.Errorf("assign userArgs: %v", err))
25-
}
17+
func NewHintRunner(hints map[uint64][]h.Hinter, newHintRunnerContext *h.HintRunnerContext) HintRunner {
18+
if newHintRunnerContext == nil {
19+
newHintRunnerContext = h.InitializeDefaultContext()
2620
}
2721
return HintRunner{
2822
// Context for certain hints that require it. Each manager is
2923
// initialized only when required by the hint
30-
context: context,
24+
context: *newHintRunnerContext,
3125
hints: hints,
3226
}
3327
}

0 commit comments

Comments
 (0)