From acba30e86c9b689580409cb4ec53637845584da0 Mon Sep 17 00:00:00 2001 From: quasilyte Date: Tue, 20 Feb 2024 13:11:10 +0400 Subject: [PATCH 1/3] implement is_nn Cairo0 hint (#207) implement several Cairo0 hint is_nn hint uses the assert_felt_le beneath it, but it was implemented beforehand. This PR has no tests included since #204 is not solved yet. I used a couple of Cairo0 scripts to test this functionality with a set of different arguments to cover both hints that are a part of `is_nn` function. (One of them handles negatives while another is for the non-negatives.) Refs #164 --- pkg/hintrunner/zero/hintcode.go | 8 ++ pkg/hintrunner/zero/zerohint.go | 170 ++++++++++++++++++++++++++++++++ pkg/utils/math.go | 13 +++ 3 files changed, 191 insertions(+) diff --git a/pkg/hintrunner/zero/hintcode.go b/pkg/hintrunner/zero/hintcode.go index 2ce2f14ff..32fd9b43f 100644 --- a/pkg/hintrunner/zero/hintcode.go +++ b/pkg/hintrunner/zero/hintcode.go @@ -1,7 +1,11 @@ package zero const ( + // This is a block for hint code strings where there is a single + // hint per function it belongs to (with some exceptions like testAssignCode). allocSegmentCode string = "memory[ap] = segments.add()" + isLeFeltCode string = "memory[ap] = 0 if (ids.a % PRIME) <= (ids.b % PRIME) else 1" + assertLtFeltCode string = "from starkware.cairo.common.math_utils import assert_integer\nassert_integer(ids.a)\nassert_integer(ids.b)\nassert (ids.a % PRIME) < (ids.b % PRIME), \\\n f'a = {ids.a % PRIME} is not less than b = {ids.b % PRIME}.'" // This is a very simple Cairo0 hint that allows us to test // the identifier resolution code. @@ -13,4 +17,8 @@ const ( assertLeFeltExcluded0Code string = "memory[ap] = 1 if excluded != 0 else 0" assertLeFeltExcluded1Code string = "memory[ap] = 1 if excluded != 1 else 0" assertLeFeltExcluded2Code string = "assert excluded == 2" + + // is_nn() hints. + isNNCode string = "memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1" + isNNOutOfRangeCode string = "memory[ap] = 0 if 0 <= ((-ids.a - 1) % PRIME) < range_check_builtin.bound else 1" ) diff --git a/pkg/hintrunner/zero/zerohint.go b/pkg/hintrunner/zero/zerohint.go index e477e3d7c..4bb58f91b 100644 --- a/pkg/hintrunner/zero/zerohint.go +++ b/pkg/hintrunner/zero/zerohint.go @@ -7,7 +7,10 @@ import ( "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/core" "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" zero "github.com/NethermindEth/cairo-vm-go/pkg/parsers/zero" + "github.com/NethermindEth/cairo-vm-go/pkg/utils" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) // GenericZeroHinter wraps an adhoc Cairo0 inline (pythonic) hint implementation. @@ -54,6 +57,10 @@ func GetHintFromCode(program *zero.ZeroProgram, rawHint zero.Hint, hintPC uint64 switch rawHint.Code { case allocSegmentCode: return CreateAllocSegmentHinter(resolver) + case isLeFeltCode: + return createIsLeFeltHinter(resolver) + case assertLtFeltCode: + return createAssertLtFeltHinter(resolver) case testAssignCode: return createTestAssignHinter(resolver) case assertLeFeltCode: @@ -64,6 +71,10 @@ func GetHintFromCode(program *zero.ZeroProgram, rawHint zero.Hint, hintPC uint64 return createAssertLeFeltExcluded1Hinter(resolver) case assertLeFeltExcluded2Code: return createAssertLeFeltExcluded2Hinter(resolver) + case isNNCode: + return createIsNNHinter(resolver) + case isNNOutOfRangeCode: + return createIsNNOutOfRangeHinter(resolver) default: return nil, fmt.Errorf("Not identified hint") } @@ -73,6 +84,95 @@ func CreateAllocSegmentHinter(resolver hintReferenceResolver) (hinter.Hinter, er return &core.AllocSegment{Dst: hinter.ApCellRef(0)}, nil } +func createIsLeFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + argA, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + argB, err := resolver.GetResOperander("b") + if err != nil { + return nil, err + } + + h := &GenericZeroHinter{ + Name: "IsLeFelt", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + //> memory[ap] = 0 if (ids.a % PRIME) <= (ids.b % PRIME) else 1 + apAddr := vm.Context.AddressAp() + + a, err := argA.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + b, err := argB.Resolve(vm) + if err != nil { + return err + } + bFelt, err := b.FieldElement() + if err != nil { + return err + } + + var v memory.MemoryValue + if utils.FeltLe(aFelt, bFelt) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } + return h, nil +} + +func createAssertLtFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + argA, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + argB, err := resolver.GetResOperander("b") + if err != nil { + return nil, err + } + + h := &GenericZeroHinter{ + Name: "AssertLtFelt", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + //> from starkware.cairo.common.math_utils import assert_integer + //> assert_integer(ids.a) + //> assert_integer(ids.b) + //> assert (ids.a % PRIME) < (ids.b % PRIME), + //> f'a = {ids.a % PRIME} is not less than b = {ids.b % PRIME}.' + a, err := argA.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + b, err := argB.Resolve(vm) + if err != nil { + return err + } + bFelt, err := b.FieldElement() + if err != nil { + return err + } + + if !utils.FeltLt(aFelt, bFelt) { + return fmt.Errorf("a = %v is not less than b = %v", aFelt, bFelt) + } + return nil + }, + } + return h, nil +} + func createTestAssignHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { arg, err := resolver.GetReference("a") if err != nil { @@ -144,6 +244,76 @@ func createAssertLeFeltExcluded2Hinter(resolver hintReferenceResolver) (hinter.H return h, nil } +func createIsNNHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + argA, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + + h := &GenericZeroHinter{ + Name: "IsNN", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + apAddr := vm.Context.AddressAp() + //> memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1 + a, err := argA.Resolve(vm) + if err != nil { + return err + } + // aFelt is already modulo PRIME, no need to adjust it. + aFelt, err := a.FieldElement() + if err != nil { + return err + } + // range_check_builtin.bound is utils.FeltMax128 (1 << 128). + var v memory.MemoryValue + if utils.FeltLt(aFelt, &utils.FeltMax128) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } + return h, nil +} + +func createIsNNOutOfRangeHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + // This hint is executed for the negative values. + // If the value was non-negative, it's usually handled by the IsNN hint. + + argA, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + + h := &GenericZeroHinter{ + Name: "IsNNOutOfRange", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + apAddr := vm.Context.AddressAp() + //> memory[ap] = 0 if 0 <= ((-ids.a - 1) % PRIME) < range_check_builtin.bound else 1 + a, err := argA.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + var lhs fp.Element + lhs.Sub(&utils.FeltZero, aFelt) //> -ids.a + lhs.Sub(&lhs, &utils.FeltOne) + var v memory.MemoryValue + if utils.FeltLt(aFelt, &utils.FeltMax128) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } + return h, nil +} + func getParameters(zeroProgram *zero.ZeroProgram, hint zero.Hint, hintPC uint64) (hintReferenceResolver, error) { resolver := NewReferenceResolver() diff --git a/pkg/utils/math.go b/pkg/utils/math.go index a6272cac9..e9daf8d59 100644 --- a/pkg/utils/math.go +++ b/pkg/utils/math.go @@ -4,6 +4,8 @@ import ( "math/bits" "golang.org/x/exp/constraints" + + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) // Takes a uint64 and an int16 and outputs their addition as well @@ -59,3 +61,14 @@ func Max[T constraints.Integer](a, b T) T { } return b } + +// FeltLt implements `a < b` felt comparison. +func FeltLt(a, b *fp.Element) bool { + return a.Cmp(b) == -1 +} + +// FeltLe implements `a <= b` felt comparison. +func FeltLe(a, b *fp.Element) bool { + // a is less or equal than b if it's not greater than b. + return a.Cmp(b) != 1 +} From 17b48b48b829929b8f6d47e25187d4c8334cfcbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carmen=20Irene=20Cabrera=20Rodr=C3=ADguez?= <49727740+cicr99@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:55:02 +0100 Subject: [PATCH 2/3] Get address for ResOperands (#210) * add Get method for ResOperands * modify method name to avoid errors --- pkg/hintrunner/hinter/operand.go | 50 +++++++++++++++++--------- pkg/hintrunner/hinter/operand_test.go | 4 +-- pkg/hintrunner/zero/hintparser.go | 6 ++-- pkg/hintrunner/zero/hintparser_test.go | 11 +++--- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/pkg/hintrunner/hinter/operand.go b/pkg/hintrunner/hinter/operand.go index 5000b2b7a..f052c3bf2 100644 --- a/pkg/hintrunner/hinter/operand.go +++ b/pkg/hintrunner/hinter/operand.go @@ -55,6 +55,7 @@ type ResOperander interface { fmt.Stringer ApplyApTracking(hint, ref zero.ApTracking) Reference + GetAddress(vm *VM.VirtualMachine) (mem.MemoryAddress, error) Resolve(vm *VM.VirtualMachine) (mem.MemoryValue, error) } @@ -67,15 +68,19 @@ func (deref Deref) String() string { } func (deref Deref) Resolve(vm *VM.VirtualMachine) (mem.MemoryValue, error) { - address, err := deref.Deref.Get(vm) + address, err := deref.GetAddress(vm) if err != nil { - return mem.MemoryValue{}, fmt.Errorf("get cell: %w", err) + return mem.UnknownValue, fmt.Errorf("get cell address: %w", err) } return vm.Memory.ReadFromAddress(&address) } +func (deref Deref) GetAddress(vm *VM.VirtualMachine) (mem.MemoryAddress, error) { + return deref.Deref.Get(vm) +} + type DoubleDeref struct { - Deref CellRefer + Deref Deref Offset int16 } @@ -84,36 +89,40 @@ func (dderef DoubleDeref) String() string { } func (dderef DoubleDeref) Resolve(vm *VM.VirtualMachine) (mem.MemoryValue, error) { - lhsAddr, err := dderef.Deref.Get(vm) + addr, err := dderef.GetAddress(vm) if err != nil { - return mem.UnknownValue, fmt.Errorf("get lhs address %s: %w", lhsAddr, err) + return mem.UnknownValue, err } - lhs, err := vm.Memory.ReadFromAddress(&lhsAddr) + value, err := vm.Memory.ReadFromAddress(&addr) if err != nil { - return mem.UnknownValue, fmt.Errorf("read lhs address %s: %w", lhsAddr, err) + return mem.UnknownValue, fmt.Errorf("read result at %s: %w", addr, err) + } + + return value, nil +} + +func (dderef DoubleDeref) GetAddress(vm *VM.VirtualMachine) (mem.MemoryAddress, error) { + lhs, err := dderef.Deref.Resolve(vm) + if err != nil { + return mem.UnknownAddress, fmt.Errorf("get lhs address: %w", err) } // Double deref implies the left hand side read must be an address address, err := lhs.MemoryAddress() if err != nil { - return mem.UnknownValue, err + return mem.UnknownAddress, err } newOffset, overflow := utils.SafeOffset(address.Offset, dderef.Offset) if overflow { - return mem.UnknownValue, fmt.Errorf("overflow %d + %d", address.Offset, dderef.Offset) + return mem.UnknownAddress, fmt.Errorf("overflow %d + %d", address.Offset, dderef.Offset) } resAddr := mem.MemoryAddress{ SegmentIndex: address.SegmentIndex, Offset: newOffset, } - value, err := vm.Memory.ReadFromAddress(&resAddr) - if err != nil { - return mem.UnknownValue, fmt.Errorf("read result at %s: %w", resAddr, err) - } - - return value, nil + return resAddr, nil } type Immediate f.Element @@ -128,6 +137,10 @@ func (imm Immediate) Resolve(vm *VM.VirtualMachine) (mem.MemoryValue, error) { return mem.MemoryValueFromFieldElement(&felt), nil } +func (imm Immediate) GetAddress(vm *VM.VirtualMachine) (mem.MemoryAddress, error) { + return mem.UnknownAddress, fmt.Errorf("cannot get an address from an immediate value %s", imm) +} + type Operator uint8 const ( @@ -174,6 +187,11 @@ func (bop BinaryOp) Resolve(vm *VM.VirtualMachine) (mem.MemoryValue, error) { } } +func (bop BinaryOp) GetAddress(vm *VM.VirtualMachine) (mem.MemoryAddress, error) { + // TODO: Check if it's possible in some cases such as Deref + Immediate + return mem.UnknownAddress, fmt.Errorf("cannot get an address from a Binary Operation operand") +} + type Reference interface { ApplyApTracking(hint, ref zero.ApTracking) Reference } @@ -197,7 +215,7 @@ func (v Deref) ApplyApTracking(hint, ref zero.ApTracking) Reference { } func (v DoubleDeref) ApplyApTracking(hint, ref zero.ApTracking) Reference { - v.Deref = v.Deref.ApplyApTracking(hint, ref).(CellRefer) + v.Deref = v.Deref.ApplyApTracking(hint, ref).(Deref) return v } diff --git a/pkg/hintrunner/hinter/operand_test.go b/pkg/hintrunner/hinter/operand_test.go index 99d0bb7d5..55d4fcb1d 100644 --- a/pkg/hintrunner/hinter/operand_test.go +++ b/pkg/hintrunner/hinter/operand_test.go @@ -70,7 +70,7 @@ func TestResolveDoubleDerefPositiveOffset(t *testing.T) { ) var apCell ApCellRef = 7 - dderf := DoubleDeref{apCell, 14} + dderf := DoubleDeref{Deref{apCell}, 14} value, err := dderf.Resolve(vm) require.NoError(t, err) @@ -92,7 +92,7 @@ func TestResolveDoubleDerefNegativeOffset(t *testing.T) { ) var apCell ApCellRef = 7 - dderf := DoubleDeref{apCell, -14} + dderf := DoubleDeref{Deref{apCell}, -14} value, err := dderf.Resolve(vm) require.NoError(t, err) diff --git a/pkg/hintrunner/zero/hintparser.go b/pkg/hintrunner/zero/hintparser.go index 520f4e332..c112c9b06 100644 --- a/pkg/hintrunner/zero/hintparser.go +++ b/pkg/hintrunner/zero/hintparser.go @@ -117,13 +117,15 @@ func (expression DerefCastExp) Evaluate() (hinter.Reference, error) { return hinter.Deref{Deref: result}, nil case hinter.Deref: return hinter.DoubleDeref{ - Deref: result.Deref, + Deref: result, Offset: 0, }, nil case DerefOffset: return hinter.DoubleDeref{ - Deref: result.Deref.Deref, + Deref: hinter.Deref{ + Deref: result.Deref.Deref, + }, Offset: int16(*result.Offset), }, nil diff --git a/pkg/hintrunner/zero/hintparser_test.go b/pkg/hintrunner/zero/hintparser_test.go index 0fcbf5a63..1b524cefd 100644 --- a/pkg/hintrunner/zero/hintparser_test.go +++ b/pkg/hintrunner/zero/hintparser_test.go @@ -31,8 +31,11 @@ func TestHintParser(t *testing.T) { Parameter: "[cast([ap + 2], felt)]", ExpectedCellRefer: nil, ExpectedResOperander: hinter.DoubleDeref{ - Deref: hinter.ApCellRef(2), - Offset: 0}, + Deref: hinter.Deref{ + Deref: hinter.ApCellRef(2), + }, + Offset: 0, + }, }, { Parameter: "cast([ap + 2] + [ap], felt)", @@ -72,11 +75,11 @@ func TestHintParser(t *testing.T) { require.NoError(t, err) if test.ExpectedCellRefer != nil { - require.Equal(t, test.ExpectedCellRefer, output, "Expected CellRefer type") + require.Equal(t, test.ExpectedCellRefer, output, "unexpected CellRefer type") } if test.ExpectedResOperander != nil { - require.Equal(t, test.ExpectedResOperander, output, "Expected ResOperander type") + require.Equal(t, test.ExpectedResOperander, output, "unexpected ResOperander type") } } } From 2be67c9dbd4990c57716dc151a345fa1c4a3d531 Mon Sep 17 00:00:00 2001 From: quasilyte Date: Wed, 21 Feb 2024 18:17:26 +0400 Subject: [PATCH 3/3] pkg/hintrunner/zero: add unit tests for the existing Cairo0 hints (#211) Also split hint constructor and Cairo0 hint wrapper code to make unit testing easier. --- pkg/hintrunner/zero/zerohint.go | 208 ------------------- pkg/hintrunner/zero/zerohint_math.go | 227 +++++++++++++++++++++ pkg/hintrunner/zero/zerohint_math_test.go | 192 +++++++++++++++++ pkg/hintrunner/zero/zerohint_test.go | 181 ++++++++++++++++ pkg/hintrunner/zero/zerohint_utils_test.go | 50 +++++ 5 files changed, 650 insertions(+), 208 deletions(-) create mode 100644 pkg/hintrunner/zero/zerohint_math.go create mode 100644 pkg/hintrunner/zero/zerohint_math_test.go create mode 100644 pkg/hintrunner/zero/zerohint_test.go create mode 100644 pkg/hintrunner/zero/zerohint_utils_test.go diff --git a/pkg/hintrunner/zero/zerohint.go b/pkg/hintrunner/zero/zerohint.go index 4bb58f91b..6d05ced44 100644 --- a/pkg/hintrunner/zero/zerohint.go +++ b/pkg/hintrunner/zero/zerohint.go @@ -7,10 +7,7 @@ import ( "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/core" "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" zero "github.com/NethermindEth/cairo-vm-go/pkg/parsers/zero" - "github.com/NethermindEth/cairo-vm-go/pkg/utils" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" - "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" - "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) // GenericZeroHinter wraps an adhoc Cairo0 inline (pythonic) hint implementation. @@ -84,95 +81,6 @@ func CreateAllocSegmentHinter(resolver hintReferenceResolver) (hinter.Hinter, er return &core.AllocSegment{Dst: hinter.ApCellRef(0)}, nil } -func createIsLeFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - argA, err := resolver.GetResOperander("a") - if err != nil { - return nil, err - } - argB, err := resolver.GetResOperander("b") - if err != nil { - return nil, err - } - - h := &GenericZeroHinter{ - Name: "IsLeFelt", - Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { - //> memory[ap] = 0 if (ids.a % PRIME) <= (ids.b % PRIME) else 1 - apAddr := vm.Context.AddressAp() - - a, err := argA.Resolve(vm) - if err != nil { - return err - } - aFelt, err := a.FieldElement() - if err != nil { - return err - } - b, err := argB.Resolve(vm) - if err != nil { - return err - } - bFelt, err := b.FieldElement() - if err != nil { - return err - } - - var v memory.MemoryValue - if utils.FeltLe(aFelt, bFelt) { - v = memory.MemoryValueFromFieldElement(&utils.FeltZero) - } else { - v = memory.MemoryValueFromFieldElement(&utils.FeltOne) - } - return vm.Memory.WriteToAddress(&apAddr, &v) - }, - } - return h, nil -} - -func createAssertLtFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - argA, err := resolver.GetResOperander("a") - if err != nil { - return nil, err - } - argB, err := resolver.GetResOperander("b") - if err != nil { - return nil, err - } - - h := &GenericZeroHinter{ - Name: "AssertLtFelt", - Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { - //> from starkware.cairo.common.math_utils import assert_integer - //> assert_integer(ids.a) - //> assert_integer(ids.b) - //> assert (ids.a % PRIME) < (ids.b % PRIME), - //> f'a = {ids.a % PRIME} is not less than b = {ids.b % PRIME}.' - a, err := argA.Resolve(vm) - if err != nil { - return err - } - aFelt, err := a.FieldElement() - if err != nil { - return err - } - b, err := argB.Resolve(vm) - if err != nil { - return err - } - bFelt, err := b.FieldElement() - if err != nil { - return err - } - - if !utils.FeltLt(aFelt, bFelt) { - return fmt.Errorf("a = %v is not less than b = %v", aFelt, bFelt) - } - return nil - }, - } - return h, nil -} - func createTestAssignHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { arg, err := resolver.GetReference("a") if err != nil { @@ -198,122 +106,6 @@ func createTestAssignHinter(resolver hintReferenceResolver) (hinter.Hinter, erro return h, nil } -func createAssertLeFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - a, err := resolver.GetResOperander("a") - if err != nil { - return nil, err - } - b, err := resolver.GetResOperander("b") - if err != nil { - return nil, err - } - rangeCheckPtr, err := resolver.GetResOperander("range_check_ptr") - if err != nil { - return nil, err - } - - h := &core.AssertLeFindSmallArc{ - A: a, - B: b, - RangeCheckPtr: rangeCheckPtr, - } - return h, nil -} - -func createAssertLeFeltExcluded0Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - return &core.AssertLeIsFirstArcExcluded{SkipExcludeAFlag: hinter.ApCellRef(0)}, nil -} - -func createAssertLeFeltExcluded1Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - return &core.AssertLeIsSecondArcExcluded{SkipExcludeBMinusA: hinter.ApCellRef(0)}, nil -} - -func createAssertLeFeltExcluded2Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - // This hint is Cairo0-specific. - // It only does a python-scoped variable named "excluded" assert. - // We store that variable inside a hinter context. - h := &GenericZeroHinter{ - Name: "AssertLeFeltExcluded2", - Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { - if ctx.ExcludedArc != 2 { - return fmt.Errorf("assertion `excluded == 2` failed") - } - return nil - }, - } - return h, nil -} - -func createIsNNHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - argA, err := resolver.GetResOperander("a") - if err != nil { - return nil, err - } - - h := &GenericZeroHinter{ - Name: "IsNN", - Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { - apAddr := vm.Context.AddressAp() - //> memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1 - a, err := argA.Resolve(vm) - if err != nil { - return err - } - // aFelt is already modulo PRIME, no need to adjust it. - aFelt, err := a.FieldElement() - if err != nil { - return err - } - // range_check_builtin.bound is utils.FeltMax128 (1 << 128). - var v memory.MemoryValue - if utils.FeltLt(aFelt, &utils.FeltMax128) { - v = memory.MemoryValueFromFieldElement(&utils.FeltZero) - } else { - v = memory.MemoryValueFromFieldElement(&utils.FeltOne) - } - return vm.Memory.WriteToAddress(&apAddr, &v) - }, - } - return h, nil -} - -func createIsNNOutOfRangeHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - // This hint is executed for the negative values. - // If the value was non-negative, it's usually handled by the IsNN hint. - - argA, err := resolver.GetResOperander("a") - if err != nil { - return nil, err - } - - h := &GenericZeroHinter{ - Name: "IsNNOutOfRange", - Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { - apAddr := vm.Context.AddressAp() - //> memory[ap] = 0 if 0 <= ((-ids.a - 1) % PRIME) < range_check_builtin.bound else 1 - a, err := argA.Resolve(vm) - if err != nil { - return err - } - aFelt, err := a.FieldElement() - if err != nil { - return err - } - var lhs fp.Element - lhs.Sub(&utils.FeltZero, aFelt) //> -ids.a - lhs.Sub(&lhs, &utils.FeltOne) - var v memory.MemoryValue - if utils.FeltLt(aFelt, &utils.FeltMax128) { - v = memory.MemoryValueFromFieldElement(&utils.FeltZero) - } else { - v = memory.MemoryValueFromFieldElement(&utils.FeltOne) - } - return vm.Memory.WriteToAddress(&apAddr, &v) - }, - } - return h, nil -} - func getParameters(zeroProgram *zero.ZeroProgram, hint zero.Hint, hintPC uint64) (hintReferenceResolver, error) { resolver := NewReferenceResolver() diff --git a/pkg/hintrunner/zero/zerohint_math.go b/pkg/hintrunner/zero/zerohint_math.go new file mode 100644 index 000000000..19c634bff --- /dev/null +++ b/pkg/hintrunner/zero/zerohint_math.go @@ -0,0 +1,227 @@ +package zero + +import ( + "fmt" + + "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/utils" + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +func newIsLeFeltHint(a, b hinter.ResOperander) hinter.Hinter { + return &GenericZeroHinter{ + Name: "IsLeFelt", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + //> memory[ap] = 0 if (ids.a % PRIME) <= (ids.b % PRIME) else 1 + apAddr := vm.Context.AddressAp() + + a, err := a.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + b, err := b.Resolve(vm) + if err != nil { + return err + } + bFelt, err := b.FieldElement() + if err != nil { + return err + } + + var v memory.MemoryValue + if utils.FeltLe(aFelt, bFelt) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } +} + +func createIsLeFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + a, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + b, err := resolver.GetResOperander("b") + if err != nil { + return nil, err + } + return newIsLeFeltHint(a, b), nil +} + +func newAssertLtFeltHint(a, b hinter.ResOperander) hinter.Hinter { + return &GenericZeroHinter{ + Name: "AssertLtFelt", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + //> from starkware.cairo.common.math_utils import assert_integer + //> assert_integer(ids.a) + //> assert_integer(ids.b) + //> assert (ids.a % PRIME) < (ids.b % PRIME), + //> f'a = {ids.a % PRIME} is not less than b = {ids.b % PRIME}.' + a, err := a.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + b, err := b.Resolve(vm) + if err != nil { + return err + } + bFelt, err := b.FieldElement() + if err != nil { + return err + } + + if !utils.FeltLt(aFelt, bFelt) { + return fmt.Errorf("a = %v is not less than b = %v", aFelt, bFelt) + } + return nil + }, + } +} + +func createAssertLtFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + a, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + b, err := resolver.GetResOperander("b") + if err != nil { + return nil, err + } + return newAssertLtFeltHint(a, b), nil +} + +func newAssertLeFeltHint(a, b, rangeCheckPtr hinter.ResOperander) hinter.Hinter { + return &core.AssertLeFindSmallArc{ + A: a, + B: b, + RangeCheckPtr: rangeCheckPtr, + } +} + +func createAssertLeFeltHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + a, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + b, err := resolver.GetResOperander("b") + if err != nil { + return nil, err + } + rangeCheckPtr, err := resolver.GetResOperander("range_check_ptr") + if err != nil { + return nil, err + } + return newAssertLeFeltHint(a, b, rangeCheckPtr), nil +} + +func createAssertLeFeltExcluded0Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + return &core.AssertLeIsFirstArcExcluded{SkipExcludeAFlag: hinter.ApCellRef(0)}, nil +} + +func createAssertLeFeltExcluded1Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + return &core.AssertLeIsSecondArcExcluded{SkipExcludeBMinusA: hinter.ApCellRef(0)}, nil +} + +func createAssertLeFeltExcluded2Hinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + // This hint is Cairo0-specific. + // It only does a python-scoped variable named "excluded" assert. + // We store that variable inside a hinter context. + h := &GenericZeroHinter{ + Name: "AssertLeFeltExcluded2", + Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { + if ctx.ExcludedArc != 2 { + return fmt.Errorf("assertion `excluded == 2` failed") + } + return nil + }, + } + return h, nil +} + +func newIsNNHint(a hinter.ResOperander) hinter.Hinter { + return &GenericZeroHinter{ + Name: "IsNN", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + apAddr := vm.Context.AddressAp() + //> memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1 + a, err := a.Resolve(vm) + if err != nil { + return err + } + // aFelt is already modulo PRIME, no need to adjust it. + aFelt, err := a.FieldElement() + if err != nil { + return err + } + // range_check_builtin.bound is utils.FeltMax128 (1 << 128). + var v memory.MemoryValue + if utils.FeltLt(aFelt, &utils.FeltMax128) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } +} + +func createIsNNHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + a, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + return newIsNNHint(a), nil +} + +func newIsNNOutOfRangeHint(a hinter.ResOperander) hinter.Hinter { + return &GenericZeroHinter{ + Name: "IsNNOutOfRange", + Op: func(vm *VM.VirtualMachine, _ *hinter.HintRunnerContext) error { + apAddr := vm.Context.AddressAp() + //> memory[ap] = 0 if 0 <= ((-ids.a - 1) % PRIME) < range_check_builtin.bound else 1 + a, err := a.Resolve(vm) + if err != nil { + return err + } + aFelt, err := a.FieldElement() + if err != nil { + return err + } + var lhs fp.Element + lhs.Sub(&utils.FeltZero, aFelt) //> -ids.a + lhs.Sub(&lhs, &utils.FeltOne) + var v memory.MemoryValue + if utils.FeltLt(aFelt, &utils.FeltMax128) { + v = memory.MemoryValueFromFieldElement(&utils.FeltZero) + } else { + v = memory.MemoryValueFromFieldElement(&utils.FeltOne) + } + return vm.Memory.WriteToAddress(&apAddr, &v) + }, + } +} + +func createIsNNOutOfRangeHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + // This hint is executed for the negative values. + // If the value was non-negative, it's usually handled by the IsNN hint. + + a, err := resolver.GetResOperander("a") + if err != nil { + return nil, err + } + return newIsNNOutOfRangeHint(a), nil +} diff --git a/pkg/hintrunner/zero/zerohint_math_test.go b/pkg/hintrunner/zero/zerohint_math_test.go new file mode 100644 index 000000000..b188dcdc7 --- /dev/null +++ b/pkg/hintrunner/zero/zerohint_math_test.go @@ -0,0 +1,192 @@ +package zero + +import ( + "testing" + + "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" + "github.com/NethermindEth/cairo-vm-go/pkg/utils" +) + +func TestZeroHintMath(t *testing.T) { + runHinterTests(t, map[string][]hintTestCase{ + "IsLeFelt": { + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltUint64(0)}, + {Name: "b", Kind: apRelative, Value: feltInt64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsLeFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + check: apValueEquals(feltUint64(0)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltUint64(1)}, + {Name: "b", Kind: immediate, Value: feltUint64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsLeFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + check: apValueEquals(feltUint64(1)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltUint64(0)}, + {Name: "b", Kind: apRelative, Value: feltUint64(1)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsLeFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + check: apValueEquals(feltUint64(0)), + }, + }, + + "AssertLtFelt": { + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(0)}, + {Name: "b", Kind: apRelative, Value: feltInt64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newAssertLtFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + errCheck: errorTextContains("a = 0 is not less than b = 0"), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: immediate, Value: feltInt64(1)}, + {Name: "b", Kind: apRelative, Value: feltInt64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newAssertLtFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + errCheck: errorTextContains("a = 1 is not less than b = 0"), + }, + + { + // -10 felt is 3618502788666131213697322783095070105623107215331596699973092056135872020467 + // and it will not be less than 14 in Cairo as well. + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(-10)}, + {Name: "b", Kind: immediate, Value: feltInt64(14)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newAssertLtFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + errCheck: errorTextContains("a = -10 is not less than b = 14"), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: fpRelative, Value: feltInt64(1)}, + {Name: "b", Kind: fpRelative, Value: feltInt64(10)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newAssertLtFeltHint(ctx.operanders["a"], ctx.operanders["b"]) + }, + errCheck: errorIsNil, + }, + }, + + "IsNN": { + // is_nn would return 1 for non-negative values, but the + // hint itself writes 0 in this case (it's used as a jump condition). + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(2421)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(0)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(0)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(-2)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(1)), + }, + }, + + "IsNNOutOfRange": { + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(0)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltInt64(1)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(0)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: fpRelative, Value: feltInt64(-1)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(1)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: &utils.FeltMax128}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(1)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltAdd(&utils.FeltMax128, feltInt64(1))}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(1)), + }, + + { + operanders: []*hintOperander{ + {Name: "a", Kind: apRelative, Value: feltAdd(&utils.FeltMax128, feltInt64(-1))}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newIsNNOutOfRangeHint(ctx.operanders["a"]) + }, + check: apValueEquals(feltUint64(0)), + }, + }, + }) +} diff --git a/pkg/hintrunner/zero/zerohint_test.go b/pkg/hintrunner/zero/zerohint_test.go new file mode 100644 index 000000000..9d3b5c783 --- /dev/null +++ b/pkg/hintrunner/zero/zerohint_test.go @@ -0,0 +1,181 @@ +package zero + +import ( + "fmt" + "testing" + + "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/hinter" + runnerutil "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/utils" + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/stretchr/testify/require" +) + +// This file defines the common testing functions. +// +// To add new tests associated with another hints file, +// look at the zerohint_math_test.go for an example. + +type hintTestContext struct { + vm *VM.VirtualMachine + runnerContext *hinter.HintRunnerContext + + operanders map[string]hinter.ResOperander +} + +// hintTestCase describes a single zero hint test case. +// +// It can either check for an error (assert-style tests) +// or for a VM state after the execution. +// +// Both of these tests have a trivial use cases that are covered +// by helper functions. If you need to check for multiple +// memory locations and/or probe some complicated VM/runner state, +// a custom lambda can be used. +type hintTestCase struct { + vminit func(vm *VM.VirtualMachine) + operanders []*hintOperander + makeHinter func(ctx *hintTestContext) hinter.Hinter + + // Every test case should have exactly one of these functions assigned. + // Assigning both is invalid, assigning none is not valid either. + check func(t *testing.T, ctx *hintTestContext) + errCheck func(t *testing.T, ctx *hintTestContext, err error) +} + +type hintOperander struct { + Name string + Value *fp.Element + Kind hintOperanderKind + + // These fields are assigned automatically by the test runner. + memoryOffset uint64 +} + +// hintOperanderKind defines how the operand is going to be constructed. +// The same Value can be accessed in various ways: it could have an immediate +// value as its source, or it could be stored somewhere in the VMs memory +// (and there are several ways to address that memory as well). +type hintOperanderKind int + +// These constants don't have a proper prefix to make it more pleasant to use them +// while declaring the test tables. +// +// For most of the tests, the operander kind doesn't matter that much. +// But some hints may require operanders that can successfully perform Get() +// to retrieve an address. +const ( + // [ap+offset] | Deref{ApCellRef(offset)} + // Requires {Name, Kind=apRelative, Value} + apRelative hintOperanderKind = iota + + // [fp+offset] | Deref{FpCellRef(offset)} + // Requires {Name, Kind=fpRelative, Value} + fpRelative + + // $value | Immediate($value) + // Requires {Name, Kind=immediate, Value} + immediate +) + +func runHinterTests(t *testing.T, tests map[string][]hintTestCase) { + runTest := func(t *testing.T, tc hintTestCase) { + // Establish an invariant that only one of the check functions is present. + if tc.check == nil && tc.errCheck == nil { + t.Fatalf("sanity check failed: tc.check and tc.errCheck can't both be nil") + } + if tc.check != nil && tc.errCheck != nil { + t.Fatalf("sanity check failed: tc.check and tc.errCheck can't be used together") + } + + vm := VM.DefaultVirtualMachine() + ctx := &hinter.HintRunnerContext{} + if tc.vminit != nil { + tc.vminit(vm) + } + + testCtx := &hintTestContext{ + vm: vm, + runnerContext: ctx, + operanders: make(map[string]hinter.ResOperander), + } + + // There are always a few extra values on the memory stack + // above FP to make AP-based and FP-based addressing makes more sense. + // These elements are identical to their index: mem[0] is 0, mem[1] is 1. + // + // Since these values are *below* FP, they can be considered to be arguments + // to the function; accessed as [fp-1], etc. + extraValues := []*fp.Element{ + feltUint64(0), + feltUint64(1), + feltUint64(2), + feltUint64(3), + } + for _, v := range extraValues { + runnerutil.WriteTo(vm, VM.ExecutionSegment, vm.Context.Ap, memory.MemoryValueFromFieldElement(v)) + vm.Context.Ap++ + } + // FP points *after* the last extra value. + vm.Context.Fp = uint64(len(extraValues)) + + for _, o := range tc.operanders { + switch o.Kind { + case apRelative, fpRelative: + o.memoryOffset = vm.Context.Ap + runnerutil.WriteTo(vm, VM.ExecutionSegment, vm.Context.Ap, memory.MemoryValueFromFieldElement(o.Value)) + vm.Context.Ap++ + + case immediate: + // Nothing to do. + + default: + panic("unexpected operander kind") + } + } + + // Now that we filled the memory with values, we can + // compute the relative addresses for operanders. + for _, o := range tc.operanders { + switch o.Kind { + case apRelative: + relOffset := int(vm.Context.Ap - o.memoryOffset) + testCtx.operanders[o.Name] = &hinter.Deref{ + Deref: hinter.ApCellRef(-relOffset), + } + + case fpRelative: + relOffset := int(vm.Context.Fp - o.memoryOffset) + testCtx.operanders[o.Name] = &hinter.Deref{ + Deref: hinter.FpCellRef(relOffset), + } + + case immediate: + testCtx.operanders[o.Name] = hinter.Immediate(*o.Value) + } + } + + h := tc.makeHinter(testCtx) + + err := h.Execute(vm, ctx) + + if tc.errCheck != nil { + // Error checking test. + tc.errCheck(t, testCtx, err) + return + } + + // VM state checking test. + require.Nil(t, err) + tc.check(t, testCtx) + } + + for testGroup, cases := range tests { + for i, tc := range cases { + t.Run(fmt.Sprintf("%s_%d", testGroup, i), func(t *testing.T) { + runTest(t, tc) + }) + } + } +} diff --git a/pkg/hintrunner/zero/zerohint_utils_test.go b/pkg/hintrunner/zero/zerohint_utils_test.go new file mode 100644 index 000000000..fc9ba1fe3 --- /dev/null +++ b/pkg/hintrunner/zero/zerohint_utils_test.go @@ -0,0 +1,50 @@ +package zero + +import ( + "testing" + + runnerutil "github.com/NethermindEth/cairo-vm-go/pkg/hintrunner/utils" + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/stretchr/testify/require" +) + +func feltInt64(v int64) *fp.Element { + return new(fp.Element).SetInt64(v) +} + +func feltUint64(v uint64) *fp.Element { + return new(fp.Element).SetUint64(v) +} + +func feltAdd(x, y *fp.Element) *fp.Element { + return new(fp.Element).Add(x, y) +} + +func apValueEquals(expected *fp.Element) func(t *testing.T, ctx *hintTestContext) { + return func(t *testing.T, ctx *hintTestContext) { + actual := runnerutil.ReadFrom(ctx.vm, VM.ExecutionSegment, ctx.vm.Context.Ap) + actualFelt, err := actual.FieldElement() + if err != nil { + t.Fatal(err) + } + if expected.Cmp(actualFelt) != 0 { + t.Fatalf("ap values mismatch:\nhave: %v\nwant: %v", actualFelt, expected) + } + } +} + +func errorTextContains(s string) func(t *testing.T, ctx *hintTestContext, err error) { + return func(t *testing.T, ctx *hintTestContext, err error) { + if err == nil { + t.Fatalf("expected an error containing %q, got nil err", s) + } + require.ErrorContains(t, err, s) + } +} + +func errorIsNil(t *testing.T, ctx *hintTestContext, err error) { + if err != nil { + t.Fatalf("expected a nil error, got: %v", err) + } +}