diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 55839c55..1f41bfa3 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/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 1ef9abe7..a8e009f9 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/.gitignore b/.gitignore index e69755e3..3e7d9c96 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ cov.out cpu.out *.test integration_tests/BenchMarks.txt +integration_tests/list_tests_in_progress.txt rust_vm_bin/ # Local Env Specific diff --git a/Makefile b/Makefile index c8f39935..d3c129ac 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 \ - 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/ && \ - 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 dd9f6d6a..6a0fd39d 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -7,12 +7,12 @@ import ( "os" "path/filepath" - "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/core" "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" hintrunner "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/zero" "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" ) @@ -27,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", @@ -119,11 +121,11 @@ func main() { if err != nil { return fmt.Errorf("cannot load program: %w", err) } - runnerMode := runner.ExecutionMode + runnerMode := runner.ExecutionModeZero if proofmode { - runnerMode = runner.ProofModeCairo0 + runnerMode = runner.ProofModeZero } - 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) }, }, { @@ -192,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) @@ -203,25 +217,27 @@ func main() { if err != nil { return fmt.Errorf("cannot load program: %w", err) } - hints, err := core.GetCairoHints(cairoProgram) + program, hints, err := runner.AssembleProgram(cairoProgram) if err != nil { - return fmt.Errorf("cannot get hints: %w", err) + return fmt.Errorf("cannot assemble program: %w", err) } - program, err := runner.LoadCairoProgram(cairoProgram) - if err != nil { - return fmt.Errorf("cannot load program: %w", err) + runnerMode := runner.ExecutionModeCairo + if proofmode { + runnerMode = runner.ProofModeCairo } - entryCodeInstructions, err := runner.GetEntryCodeInstructions() + userArgs, err := starknet.ParseCairoProgramArgs(args) if err != nil { - return fmt.Errorf("cannot load entry code instructions: %w", err) + return fmt.Errorf("cannot parse args: %w", err) } - program.Bytecode = append(entryCodeInstructions, program.Bytecode...) - program.Bytecode = append(program.Bytecode, runner.GetFooterInstructions()...) - runnerMode := runner.ExecutionMode - if proofmode { - runnerMode = runner.ProofModeCairo1 + 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) + return runVM(program, proofmode, maxsteps, entrypointOffset, collectTrace, traceLocation, buildMemory, memoryLocation, layoutName, airPublicInputLocation, airPrivateInputLocation, hints, runnerMode, userArgs) }, }, }, @@ -247,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/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/fibonacci.cairo b/integration_tests/cairo_1_programs/fibonacci__small.cairo similarity index 100% rename from integration_tests/cairo_1_programs/fibonacci.cairo rename to integration_tests/cairo_1_programs/fibonacci__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 4be973bd..4b6a030b 100644 --- a/integration_tests/cairo_vm_test.go +++ b/integration_tests/cairo_vm_test.go @@ -74,28 +74,33 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str return } layout := getLayoutFromFileName(path) - rustVmFilePath := path - if zero { - rustVmFilePath = compiledOutput - } - elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero) + + elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero) 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) + writeToFile(path) + return } else { if err != nil { t.Error(err) + writeToFile(path) return } } - elapsedGo, traceFile, memoryFile, _, err := runVm(compiledOutput, layout, zero) + rustVmFilePath := path + if zero { + rustVmFilePath = compiledOutput + } + elapsedRs, rsTraceFile, rsMemoryFile, err := runRustVm(rustVmFilePath, layout, zero) 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) return } else { if err != nil { t.Error(err) + writeToFile(path) return } } @@ -103,11 +108,13 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str trace, memory, err := decodeProof(traceFile, memoryFile) if err != nil { t.Error(err) + writeToFile(path) return } rsTrace, rsMemory, err := decodeProof(rsTraceFile, rsMemoryFile) if err != nil { t.Error(err) + writeToFile(path) return } @@ -144,16 +151,6 @@ func runAndTestFile(t *testing.T, path string, name string, benchmarkMap map[str return } - if !assert.Equal(t, pyTrace, rsTrace) { - t.Logf("pytrace:\n%s\n", traceRepr(pyTrace)) - t.Logf("rstrace:\n%s\n", traceRepr(rsTrace)) - writeToFile(path) - } - if !assert.Equal(t, pyMemory, rsMemory) { - t.Logf("pymemory;\n%s\n", memoryRepr(pyMemory)) - t.Logf("rsmemory;\n%s\n", memoryRepr(rsMemory)) - writeToFile(path) - } if !assert.Equal(t, pyTrace, trace) { t.Logf("pytrace:\n%s\n", traceRepr(pyTrace)) t.Logf("trace:\n%s\n", traceRepr(trace)) @@ -182,7 +179,7 @@ func TestCairoFiles(t *testing.T) { {"./cairo_zero_hint_tests/", true}, {"./cairo_zero_file_tests/", true}, {"./builtin_tests/", true}, - // {"./cairo_1_programs/", false}, + {"./cairo_1_programs/", false}, } // filter is for debugging purposes @@ -329,7 +326,7 @@ func compileCairoCode(path string, zero bool) (string, error) { } } else { sierraOutput := swapExtenstion(path, sierraSuffix) - cliCommand = "../rust_vm_bin/cairo/cairo-compile" + cliCommand = "../rust_vm_bin/cairo-lang/cairo-compile" args = []string{ "--single-file", path, @@ -346,7 +343,7 @@ func compileCairoCode(path string, zero bool) (string, error) { ) } - cliCommand = "../rust_vm_bin/cairo/sierra-compile-json" + cliCommand = "../rust_vm_bin/cairo-lang/sierra-compile-json" args = []string{ sierraOutput, compiledOutput, @@ -420,7 +417,7 @@ func runRustVm(path, layout string, zero bool) (time.Duration, string, string, e args = append(args, "--proof_mode") } - binaryPath := "./../rust_vm_bin/cairo/cairo1-run" + binaryPath := "./../rust_vm_bin/cairo-lang/cairo1-run" if zero { binaryPath = "./../rust_vm_bin/cairo-vm-cli" } @@ -460,9 +457,22 @@ func runVm(path, layout string, zero bool) (time.Duration, string, string, strin memoryOutput, "--layout", layout, - path, } - + if !zero { + args = []string{ + cliCommand, + // "--proofmode", + "--tracefile", + traceOutput, + "--memoryfile", + memoryOutput, + "--layout", + layout, + "--available_gas", + "9999999", + } + } + args = append(args, path) cmd := exec.Command( "../bin/cairo-vm", args..., diff --git a/pkg/hintrunner/core/cairo_hintparser.go b/pkg/hintrunner/core/cairo_hintparser.go index 86168d73..f0e25a13 100644 --- a/pkg/hintrunner/core/cairo_hintparser.go +++ b/pkg/hintrunner/core/cairo_hintparser.go @@ -142,6 +142,20 @@ func GetHintByName(hint starknet.Hint) (hinter.Hinter, error) { quotient: parseCellRefer(args.Quotient), remainder: parseCellRefer(args.Remainder), }, nil + case starknet.Uint256InvModNName: + args := hint.Args.(*starknet.Uint256InvModN) + return &Uint256InvModN{ + B0: parseResOperand(args.B0), + B1: parseResOperand(args.B1), + N0: parseResOperand(args.N0), + N1: parseResOperand(args.N1), + G0OrNoInv: parseCellRefer(args.G0OrNoInv), + G1Option: parseCellRefer(args.G1Option), + SOrR0: parseCellRefer(args.SOrR0), + SOrR1: parseCellRefer(args.SOrR1), + TOrK0: parseCellRefer(args.TOrK0), + TOrK1: parseCellRefer(args.TOrK1), + }, nil case starknet.Uint256DivModName: args := hint.Args.(*starknet.Uint256DivMod) return &Uint256DivMod{ diff --git a/pkg/hintrunner/core/hint.go b/pkg/hintrunner/core/hint.go index 8e98ad23..202c6891 100644 --- a/pkg/hintrunner/core/hint.go +++ b/pkg/hintrunner/core/hint.go @@ -478,7 +478,7 @@ func (hint DivMod) Execute(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) e return nil } -type U256InvModN struct { +type Uint256InvModN struct { B0 hinter.Reference B1 hinter.Reference N0 hinter.Reference @@ -491,11 +491,11 @@ type U256InvModN struct { TOrK1 hinter.Reference } -func (hint U256InvModN) String() string { +func (hint Uint256InvModN) String() string { return "U256InvModN" } -func (hint U256InvModN) Execute(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { +func (hint Uint256InvModN) Execute(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { B0, err := hint.B0.Resolve(vm) if err != nil { return fmt.Errorf("resolve B0 operand %s: %v", hint.B0, err) @@ -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/core/hint_test.go b/pkg/hintrunner/core/hint_test.go index 39dbe079..058c44ee 100644 --- a/pkg/hintrunner/core/hint_test.go +++ b/pkg/hintrunner/core/hint_test.go @@ -1045,7 +1045,7 @@ func TestU256InvModN(t *testing.T) { N0Felt := f.NewElement(1) N1Felt := f.NewElement(0) - hint := U256InvModN{ + hint := Uint256InvModN{ B0: hinter.Immediate(B0Felt), B1: hinter.Immediate(B1Felt), N0: hinter.Immediate(N0Felt), @@ -1134,7 +1134,7 @@ func TestU256InvModN(t *testing.T) { N0Felt := f.NewElement(100) N1Felt := f.NewElement(0) - hint := U256InvModN{ + hint := Uint256InvModN{ B0: hinter.Immediate(B0Felt), B1: hinter.Immediate(B1Felt), N0: hinter.Immediate(N0Felt), @@ -1223,7 +1223,7 @@ func TestU256InvModN(t *testing.T) { N0Felt := f.NewElement(2) N1Felt := f.NewElement(0) - hint := U256InvModN{ + hint := Uint256InvModN{ B0: hinter.Immediate(B0Felt), B1: hinter.Immediate(B1Felt), N0: hinter.Immediate(N0Felt), diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go index f8e54ebb..c1a209e7 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/hintrunner/hintrunner_test.go b/pkg/hintrunner/hintrunner_test.go index 0e057de9..c3cbbd5e 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.go b/pkg/parsers/starknet/args.go new file mode 100644 index 00000000..0e7d2845 --- /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/parsers/starknet/args_test.go b/pkg/parsers/starknet/args_test.go new file mode 100644 index 00000000..f6bdd6f9 --- /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/parsers/starknet/hint.go b/pkg/parsers/starknet/hint.go index 01bc19ac..5010f265 100644 --- a/pkg/parsers/starknet/hint.go +++ b/pkg/parsers/starknet/hint.go @@ -24,7 +24,7 @@ const ( TestLessThanOrEqualAddressName HintName = "TestLessThanOrEqualAddress" WideMul128Name HintName = "WideMul128" DivModName HintName = "DivMod" - U256InvModName HintName = "U256InvMod" + Uint256InvModNName HintName = "U256InvModN" Uint256DivModName HintName = "Uint256DivMod" Uint512DivModByUint256Name HintName = "Uint512DivModByUint256" SquareRootName HintName = "SquareRoot" @@ -115,7 +115,7 @@ type DivMod struct { Remainder CellRef `json:"remainder" validate:"required"` } -type U256InvMod struct { +type Uint256InvModN struct { B0 ResOperand `json:"b0" validate:"required"` B1 ResOperand `json:"b1" validate:"required"` N0 ResOperand `json:"n0" validate:"required"` @@ -483,11 +483,9 @@ func (h *Hint) UnmarshalJSON(data []byte) error { if err != nil { return err } - for k, v := range rawHint { h.Name = HintName(k) var args any - switch h.Name { // Starknet hints case SystemCallName: @@ -509,8 +507,8 @@ func (h *Hint) UnmarshalJSON(data []byte) error { args = &WideMul128{} case DivModName: args = &DivMod{} - case U256InvModName: - args = &U256InvMod{} + case Uint256InvModNName: + args = &Uint256InvModN{} case Uint256DivModName: args = &Uint256DivMod{} case Uint512DivModByUint256Name: diff --git a/pkg/runner/gas.go b/pkg/runner/gas.go new file mode 100644 index 00000000..86c89c39 --- /dev/null +++ b/pkg/runner/gas.go @@ -0,0 +1,72 @@ +package runner + +import ( + "fmt" + + "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + mem "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" +) + +type TokenGasCost uint8 + +const ( + ConstToken TokenGasCost = iota + 1 + HoleToken + RangeCheckToken + RangeCheck96Token + PedersenToken + PoseidonToken + BitwiseToken + EcOpToken + AddModToken + MulModToken +) + +func getTokenGasCost(token TokenGasCost) (uint64, error) { + switch token { + case ConstToken: + return 1, nil + case PedersenToken: + return 4050, nil + case PoseidonToken: + return 491, nil + case BitwiseToken: + return 583, nil + case EcOpToken: + return 4085, nil + case AddModToken: + return 230, nil + case MulModToken: + return 604, nil + default: + return 0, fmt.Errorf("token has no cost") + } +} + +func gasInitialization(memory *memory.Memory) error { + builtinsCostSegmentAddress := memory.AllocateEmptySegment() + mv := mem.MemoryValueFromMemoryAddress(&builtinsCostSegmentAddress) + programSegment := memory.Segments[vm.ProgramSegment] + err := memory.Write(0, programSegment.Len(), &mv) + if err != nil { + return err + } + preCostTokenTypes := []TokenGasCost{PedersenToken, PoseidonToken, BitwiseToken, EcOpToken, AddModToken, MulModToken} + for _, token := range preCostTokenTypes { + cost, err := getTokenGasCost(token) + if err != nil { + return err + } + mv := mem.MemoryValueFromUint(cost) + err = memory.WriteToAddress(&builtinsCostSegmentAddress, &mv) + if err != nil { + return err + } + builtinsCostSegmentAddress, err = builtinsCostSegmentAddress.AddOffset(1) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 17d3abf0..2b3cc36d 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -3,10 +3,13 @@ package runner import ( "errors" "fmt" + "slices" "github.com/NethermindEth/cairo-vm-go/pkg/assembler" "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner" + "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/core" "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" + "github.com/NethermindEth/cairo-vm-go/pkg/parsers/starknet" "github.com/NethermindEth/cairo-vm-go/pkg/utils" "github.com/NethermindEth/cairo-vm-go/pkg/vm" "github.com/NethermindEth/cairo-vm-go/pkg/vm/builtins" @@ -17,9 +20,10 @@ import ( type RunnerMode uint8 const ( - ExecutionMode RunnerMode = iota + 1 - ProofModeCairo0 - ProofModeCairo1 + ExecutionModeZero RunnerMode = iota + 1 + ProofModeZero + ExecutionModeCairo + ProofModeCairo ) type Runner struct { @@ -39,8 +43,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 @@ -55,6 +59,37 @@ func NewRunner(program *Program, hints map[uint64][]hinter.Hinter, runnerMode Ru }, nil } +func AssembleProgram(cairoProgram *starknet.StarknetProgram) (Program, map[uint64][]hinter.Hinter, error) { + mainFunc, ok := cairoProgram.EntryPointsByFunction["main"] + if !ok { + return Program{}, nil, fmt.Errorf("cannot find main function") + } + program, err := LoadCairoProgram(cairoProgram) + if err != nil { + return Program{}, 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) + } + 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()...) + + 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 +} + // RunEntryPoint is like Run, but it executes the program starting from the given PC offset. // This PC offset is expected to be a start from some function inside the loaded program. func (runner *Runner) RunEntryPoint(pc uint64) error { @@ -77,14 +112,13 @@ func (runner *Runner) RunEntryPoint(pc uint64) error { returnFp := memory.AllocateEmptySegment() mvReturnFp := mem.MemoryValueFromMemoryAddress(&returnFp) cairo1FpOffset := uint64(0) - if runner.runnerMode == ProofModeCairo1 { + if runner.runnerMode == ProofModeCairo { cairo1FpOffset = 2 } end, err := runner.initializeEntrypoint(pc, nil, &mvReturnFp, memory, stack, cairo1FpOffset) if err != nil { return err } - if err := runner.RunUntilPc(&end); err != nil { return err } @@ -107,7 +141,7 @@ func (runner *Runner) Run() error { return err } - if runner.runnerMode == ProofModeCairo0 || runner.runnerMode == ProofModeCairo1 { + if runner.isProofMode() { // +1 because proof mode require an extra instruction run // pow2 because proof mode also requires that the trace is a power of two pow2Steps := utils.NextPowerOfTwo(runner.vm.Step + 1) @@ -140,19 +174,22 @@ func (runner *Runner) initializeMainEntrypoint() (mem.MemoryAddress, error) { return mem.UnknownAddress, err } switch runner.runnerMode { - case ExecutionMode, ProofModeCairo1: + case ExecutionModeZero, ExecutionModeCairo, ProofModeCairo: 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") - } - if runner.runnerMode == ExecutionMode { - return runner.initializeEntrypoint(mainPCOffset, nil, &mvReturnFp, memory, stack, 0) + if runner.runnerMode == ProofModeCairo { + // In Cairo mainPCOffset is equal to the offset of program segment base + return runner.initializeEntrypoint(uint64(0), nil, &mvReturnFp, memory, stack, 2) + } else if runner.runnerMode == ExecutionModeCairo { + return runner.initializeEntrypoint(uint64(0), nil, &mvReturnFp, memory, stack, 0) } else { - return runner.initializeEntrypoint(mainPCOffset, nil, &mvReturnFp, memory, stack, 2) + mainPCOffset, ok := runner.program.Entrypoints["main"] + if !ok { + return mem.UnknownAddress, errors.New("can't find an entrypoint for main") + } + return runner.initializeEntrypoint(mainPCOffset, nil, &mvReturnFp, memory, stack, 0) } - case ProofModeCairo0: + case ProofModeZero: initialPCOffset, ok := runner.program.Labels["__start__"] if !ok { return mem.UnknownAddress, @@ -207,27 +244,50 @@ func (runner *Runner) initializeBuiltins(memory *mem.Memory) ([]mem.MemoryValue, } // check if all builtins from the program are in the layout for _, programBuiltin := range runner.program.Builtins { - if programBuiltin != builtins.GasBuiltinType { - if _, found := builtinsSet[programBuiltin]; !found { - builtinName, err := programBuiltin.MarshalJSON() - if err != nil { - return []mem.MemoryValue{}, err - } - return []mem.MemoryValue{}, fmt.Errorf("builtin %s not found in the layout: %s", builtinName, runner.layout.Name) + if programBuiltin == builtins.GasBuiltinType || programBuiltin == builtins.SegmentArenaType { + continue + } + if _, found := builtinsSet[programBuiltin]; !found { + builtinName, err := programBuiltin.MarshalJSON() + if err != nil { + return []mem.MemoryValue{}, err } + return []mem.MemoryValue{}, fmt.Errorf("builtin %s not found in the layout: %s", builtinName, runner.layout.Name) } } stack := []mem.MemoryValue{} - // adding to the stack only the builtins that are both in the program and in the layout + for _, bRunner := range runner.layout.Builtins { - builtinSegment := memory.AllocateBuiltinSegment(bRunner.Runner) - if utils.Contains(runner.program.Builtins, bRunner.Builtin) { - stack = append(stack, mem.MemoryValueFromMemoryAddress(&builtinSegment)) + if runner.isCairoMode() { + if utils.Contains(runner.program.Builtins, bRunner.Builtin) { + builtinSegment := memory.AllocateBuiltinSegment(bRunner.Runner) + stack = append(stack, mem.MemoryValueFromMemoryAddress(&builtinSegment)) + } + } else { + builtinSegment := memory.AllocateBuiltinSegment(bRunner.Runner) + if utils.Contains(runner.program.Builtins, bRunner.Builtin) { + stack = append(stack, mem.MemoryValueFromMemoryAddress(&builtinSegment)) + } + } + } + // Write builtins costs segment address to the end of the program segment + if runner.isCairoMode() { + err := gasInitialization(memory) + if err != nil { + return nil, err } } return stack, nil } +func (runner *Runner) isProofMode() bool { + return runner.runnerMode == ProofModeCairo || runner.runnerMode == ProofModeZero +} + +func (runner *Runner) isCairoMode() bool { + return runner.runnerMode == ExecutionModeCairo || runner.runnerMode == ProofModeCairo +} + func (runner *Runner) initializeVm( initialPC *mem.MemoryAddress, stack []mem.MemoryValue, memory *mem.Memory, cairo1FpOffset uint64, ) error { @@ -239,7 +299,6 @@ func (runner *Runner) initializeVm( return err } } - initialFp := offset + uint64(len(stack)) + cairo1FpOffset var err error // initialize vm @@ -248,7 +307,7 @@ func (runner *Runner) initializeVm( Ap: initialFp, Fp: initialFp, }, memory, vm.VirtualMachineConfig{ - ProofMode: runner.runnerMode == ProofModeCairo0 || runner.runnerMode == ProofModeCairo1, + ProofMode: runner.isProofMode(), CollectTrace: runner.collectTrace, }) return err @@ -458,105 +517,88 @@ func (ctx *InlineCasmContext) AddInlineCASM(code string) { ctx.currentCodeOffset += int(total_size) } -func GetEntryCodeInstructions() ([]*fp.Element, error) { - //TODO: investigate how to implement function param types - paramTypes := []struct { - genericTypeId builtins.BuiltinType - size int - }{} - codeOffset := 0 - - ctx := &InlineCasmContext{} - - builtinOffset := map[builtins.BuiltinType]int{ - builtins.PedersenType: 10, - builtins.RangeCheckType: 9, - builtins.BitwiseType: 8, - builtins.ECOPType: 7, - builtins.PoseidonType: 6, - builtins.RangeCheck96Type: 5, - builtins.AddModeType: 4, - builtins.MulModType: 3, - } - +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{}{ - 1: {}, + builtins.SystemType: {}, + } + + for _, builtin := range []builtins.BuiltinType{ + builtins.MulModType, + builtins.AddModeType, + builtins.RangeCheck96Type, + builtins.PoseidonType, + builtins.ECOPType, + builtins.BitwiseType, + builtins.RangeCheckType, + builtins.PedersenType, + } { + if slices.Contains(function.Builtins, builtin) { + builtinsOffsetsMap[builtins.BuiltinType(builtin)] = builtinOffset + builtinOffset += 1 + } } - apOffset := 0 + ctx := &InlineCasmContext{} paramsSize := 0 for _, param := range paramTypes { - ty, size := param.genericTypeId, param.size - if _, inBuiltin := builtinOffset[ty]; !inBuiltin { - if _, emulated := emulatedBuiltins[ty]; !emulated && ty != 99 { - paramsSize += size - } - } + paramsSize += param.Size } - ctx.AddInlineCASM( - fmt.Sprintf("ap += %d;", paramsSize), - ) apOffset += paramsSize - - for _, param := range paramTypes { - if param.genericTypeId == 99 { - ctx.AddInlineCASM( - `%{ memory[ap + 0] = segments.add() %} - %{ memory[ap + 1] = segments.add() %} - ap += 2; - [ap + 0] = 0, ap++; - [ap - 2] = [[ap - 3]]; - [ap - 1] = [[ap - 3] + 1]; - [ap - 1] = [[ap - 3] + 2]; - apOffset += 3`, - ) - } - } - usedArgs := 0 - for _, param := range paramTypes { - ty, tySize := param.genericTypeId, param.size - if offset, isBuiltin := builtinOffset[ty]; isBuiltin { + var hints map[uint64][]hinter.Hinter + for _, builtin := range function.Builtins { + if offset, isBuiltin := builtinsOffsetsMap[builtin]; isBuiltin { ctx.AddInlineCASM( fmt.Sprintf("[ap + 0] = [fp - %d], ap++;", offset), ) apOffset += 1 - } else if _, emulated := emulatedBuiltins[ty]; emulated { + } else if _, emulated := emulatedBuiltins[builtin]; emulated { ctx.AddInlineCASM( - `memory[ap + 0] = segments.add(); - ap += 1;`, + ` + %{ memory[ap + 0] = segments.add() %} + ap += 1; + `, ) apOffset += 1 - } else if ty == 99 { + } else if builtin == builtins.SegmentArenaType { offset := apOffset - paramsSize ctx.AddInlineCASM( fmt.Sprintf("[ap + 0] = [ap - %d] + 3, ap++;", offset), ) apOffset += 1 - } else { - offset := apOffset - usedArgs - for i := 0; i < tySize; i++ { - ctx.AddInlineCASM( - fmt.Sprintf("[ap + 0] = [ap - %d], ap++;", offset), - ) - apOffset += 1 - usedArgs += 1 + } else if builtin == builtins.GasBuiltinType { + hints = map[uint64][]hinter.Hinter{ + uint64(ctx.currentCodeOffset): { + &core.ExternalWriteArgsToMemory{}, + }, } + ctx.AddInlineCASM("ap += 1;") + apOffset += 1 + usedArgs += 1 } } - - beforeFinalCall := ctx.currentCodeOffset - finalCallSize := 3 - offset := finalCallSize + codeOffset - ctx.AddInlineCASM(fmt.Sprintf(` - call rel %d; - ret; - `, offset)) - if beforeFinalCall+finalCallSize != ctx.currentCodeOffset { - return nil, errors.New("final call offset mismatch") + 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 + } } - - 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/runner/runner_benchmark_test.go b/pkg/runner/runner_benchmark_test.go index d86be979..6347640e 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, ProofModeZero, 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 8c876d64..50ded3f4 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, ExecutionModeZero, 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, ExecutionModeZero, 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, ProofModeZero, false, uint64(maxstep), "plain", nil) require.NoError(t, err) err = runner.Run() @@ -434,9 +434,8 @@ 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, ExecutionMode, false, math.MaxUint64, layoutName) + runner, err := NewRunner(program, hints, ExecutionModeZero, false, math.MaxUint64, layoutName, nil) if err != nil { panic(err) } diff --git a/pkg/vm/builtins/builtin_runner.go b/pkg/vm/builtins/builtin_runner.go index 7e58e1bd..5a12f2ab 100644 --- a/pkg/vm/builtins/builtin_runner.go +++ b/pkg/vm/builtins/builtin_runner.go @@ -25,6 +25,7 @@ const ( AddModeType MulModType GasBuiltinType + SystemType ) func Runner(name BuiltinType) memory.BuiltinRunner { diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 65c6ab8e..743c4c7c 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -91,6 +91,17 @@ type VirtualMachine struct { RcLimitsMax uint16 } +func (vm *VirtualMachine) PrintMemory() { + for i := range vm.Memory.Segments { + for j, cell := range vm.Memory.Segments[i].Data { + if !cell.Known() { + continue + } + 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,