From c30c19094c9c2df6f02436e0b426b9e66fc8b3e8 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Mon, 28 Aug 2023 19:07:20 -0400 Subject: [PATCH 1/9] prepare vm to accept a runner --- pkg/hintrunner/error.go | 40 +++++++++++ pkg/hintrunner/hint.go | 31 +++++++++ pkg/hintrunner/hintrunner.go | 37 ++++++++++ pkg/hintrunner/operand.go | 126 ++++++++++++++++++++++++++++++++++ pkg/vm/memory/memory_value.go | 2 +- pkg/vm/vm.go | 47 ++++++++----- pkg/vm/vm_test.go | 6 +- 7 files changed, 268 insertions(+), 21 deletions(-) create mode 100644 pkg/hintrunner/error.go create mode 100644 pkg/hintrunner/hint.go create mode 100644 pkg/hintrunner/hintrunner.go create mode 100644 pkg/hintrunner/operand.go diff --git a/pkg/hintrunner/error.go b/pkg/hintrunner/error.go new file mode 100644 index 000000000..c7e04cffc --- /dev/null +++ b/pkg/hintrunner/error.go @@ -0,0 +1,40 @@ +package hintrunner + +import "fmt" + +// HintRunnerError represents error ocurring during the hint runner +type HintRunnerError struct { + err error +} + +func NewHintRunnerError(err error) *HintRunnerError { + return &HintRunnerError{err} +} + +func (e *HintRunnerError) Error() string { + return fmt.Sprintf("error in hint runner: %s", e.err.Error()) +} + +func (e *HintRunnerError) Unwrap() error { + return e.err +} + +// HintError stores information about the hint being executed as well as its cause +type HintError struct { + hintName string + err error +} + +func NewHintError(hintName string, err error) *HintError { + return &HintError{hintName, err} +} + +func (e *HintError) Error() string { + return fmt.Sprintf("error executing hint %s: %s", e.hintName, e.err.Error()) +} + +func (e *HintError) Unwrap() error { + return e.err +} + +// todo(rodro): Should add custom error for operand? diff --git a/pkg/hintrunner/hint.go b/pkg/hintrunner/hint.go new file mode 100644 index 000000000..202c22537 --- /dev/null +++ b/pkg/hintrunner/hint.go @@ -0,0 +1,31 @@ +package hintrunner + +import ( + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" +) + +type Hinter interface { + Execute(vm *VM.VirtualMachine) *HintError +} + +type AllocSegment struct { + dst CellRefer +} + +func (h AllocSegment) Execute(vm *VM.VirtualMachine) *HintError { + segmentIndex := vm.MemoryManager.Memory.AllocateEmptySegment() + memAddress := memory.MemoryValueFromSegmentAndOffset(segmentIndex, 0) + + cell, err := h.dst.Get(vm) + if err != nil { + return NewHintError("AllocSegment", err) + } + + err = cell.Write(memAddress) + if err != nil { + return NewHintError("AllocSegment", err) + } + + return nil +} diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go new file mode 100644 index 000000000..7a68a6270 --- /dev/null +++ b/pkg/hintrunner/hintrunner.go @@ -0,0 +1,37 @@ +package hintrunner + +import ( + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "golang.org/x/exp/constraints" +) + +// todo: Can two or more hints be assigned to a specific PC? +type HintRunner struct { + // A mapping from program counter to hint implementation + hints map[uint64]Hinter +} + +func CreateHintRunner() *HintRunner { + return nil +} + +func (hr HintRunner) RunHint(vm *VM.VirtualMachine) *HintRunnerError { + hint := hr.hints[vm.Context.Pc] + if hint == nil { + return nil + } + + err := hint.Execute(vm) + if err != nil { + return NewHintRunnerError(err) + } + return nil +} + +// move this function to a possible util packages +func Abs[T constraints.Signed](num T) T { + if num >= 0 { + return num + } + return num * -1 +} diff --git a/pkg/hintrunner/operand.go b/pkg/hintrunner/operand.go new file mode 100644 index 000000000..f6ac5df12 --- /dev/null +++ b/pkg/hintrunner/operand.go @@ -0,0 +1,126 @@ +package hintrunner + +import ( + "fmt" + "math/big" + + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +// +// All CellRef definitions + +type CellRefer interface { + Get(vm *VM.VirtualMachine) (*memory.Cell, error) +} + +type ApCellRef int16 + +type FpCellRef int16 + +func (ap ApCellRef) Get(vm *VM.VirtualMachine) (*memory.Cell, error) { + // todo(rodro): fix maths with safemath from ilia + offset := vm.Context.Ap + uint64(ap) + return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, offset) +} + +func (fp FpCellRef) Get(vm *VM.VirtualMachine) (*memory.Cell, error) { + // todo(rodro): fix maths with safemath from ilia + offset := vm.Context.Fp + uint64(fp) + return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, offset) +} + +// +// All ResOperand definitions + +type ResOperander interface { + Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) +} + +type Deref struct { + deref CellRefer +} + +type DoubleDeref struct { + deref Deref + offset int16 +} + +type Immediate big.Int + +type Operator uint8 + +const ( + Add Operator = iota + Mul +) + +type BinaryOp struct { + operator Operator + lhs CellRefer + rhs ResOperander // (except DoubleDeref and BinaryOp) +} + +func (deref Deref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { + cell, err := deref.deref.Get(vm) + if err != nil { + return nil, err + } + return cell.Read(), nil +} + +func (dderef DoubleDeref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { + cell, err := dderef.deref.deref.Get(vm) + if err != nil { + return nil, err + } + lhs := cell.Read() + + var res *memory.MemoryValue + if dderef.offset >= 0 { + rhs := memory.MemoryValueFromInt(dderef.offset) + res, err = memory.EmptyMemoryValueAs(lhs.IsAddress()).Add(lhs, rhs) + } else { + rhs := memory.MemoryValueFromInt(Abs(dderef.offset)) + res, err = memory.EmptyMemoryValueAs(lhs.IsAddress()).Sub(lhs, rhs) + } + if err != nil { + return nil, err + } + + return res, nil +} + +func (imm Immediate) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { + felt := &f.Element{} + bigInt := (big.Int)(imm) + // todo(rodro): do we require to check that big int is lesser than P, or do we + // just take: big_int `mod` P? + felt.SetBigInt(&bigInt) + + return memory.MemoryValueFromFieldElement(felt), nil +} + +func (bop BinaryOp) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { + cell, err := bop.lhs.Get(vm) + if err != nil { + return nil, err + } + lhs := cell.Read() + + rhs, err := bop.rhs.Resolve(vm) + if err != nil { + return nil, err + } + + switch bop.operator { + case Add: + return memory.EmptyMemoryValueAs(lhs.IsAddress()).Add(lhs, rhs) + case Mul: + return memory.EmptyMemoryValueAsFelt().Mul(lhs, rhs) + } + + return nil, fmt.Errorf("Unknown operator: %d", bop.operator) +} diff --git a/pkg/vm/memory/memory_value.go b/pkg/vm/memory/memory_value.go index 2bc417930..dc79a67fc 100644 --- a/pkg/vm/memory/memory_value.go +++ b/pkg/vm/memory/memory_value.go @@ -115,7 +115,7 @@ func MemoryValueFromInt[T constraints.Integer](v T) *MemoryValue { } } -func MemoryValueFromSegmentAndOffset[T constraints.Integer](segmentIndex T, offset T) *MemoryValue { +func MemoryValueFromSegmentAndOffset[T constraints.Integer](segmentIndex, offset T) *MemoryValue { return &MemoryValue{ address: &MemoryAddress{SegmentIndex: uint64(segmentIndex), Offset: uint64(offset)}, } diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index dfe3c7eb5..f087d5659 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -8,16 +8,26 @@ import ( ) const ( - programSegment = iota - executionSegment + ProgramSegment = iota + ExecutionSegment ) +// Required by the VM to run hints. +// +// HintRunner is defined as an external component of the VM so any user +// could define its own, allowing the use custom hints +type HintRunner interface { + RunHint(vm *VirtualMachine) error +} + +// Represents the current execution context of the vm type Context struct { Fp uint64 Ap uint64 Pc uint64 } +// This type represents the current execution context of the vm type VirtualMachineConfig struct { Trace bool // Todo(rodro): Update this property to include all builtins @@ -59,8 +69,15 @@ func NewVirtualMachine(programBytecode []*f.Element, config VirtualMachineConfig // todo(rodro): how to know when te execute a hint or normal instruction -func (vm *VirtualMachine) RunStep() error { - memoryValue, err := vm.MemoryManager.Memory.Read(programSegment, vm.Context.Pc) +func (vm *VirtualMachine) RunStep(hintRunner HintRunner) error { + // Run hint + err := hintRunner.RunHint(vm) + if err != nil { + return fmt.Errorf("cannot run hint at %d: %w", vm.Context.Pc, err) + } + + // Decode and execute instruction + memoryValue, err := vm.MemoryManager.Memory.Read(ProgramSegment, vm.Context.Pc) if err != nil { return fmt.Errorf("cannot load step at %d: %w", vm.Context.Pc, err) } @@ -83,9 +100,9 @@ func (vm *VirtualMachine) RunStep() error { vm.Step++ return nil } -func (vm *VirtualMachine) RunStepAt(pc uint64) error { +func (vm *VirtualMachine) RunStepAt(hinter HintRunner, pc uint64) error { vm.Context.Pc = pc - return vm.RunStep() + return vm.RunStep(hinter) } func (vm *VirtualMachine) RunInstruction(instruction *Instruction) error { @@ -144,10 +161,6 @@ func (vm *VirtualMachine) RunInstruction(instruction *Instruction) error { return nil } -func (vm *VirtualMachine) RunHint() error { - return nil -} - func (vm *VirtualMachine) getCellDst(instruction *Instruction) (*mem.Cell, error) { var dstRegister uint64 if instruction.DstRegister == Ap { @@ -157,7 +170,7 @@ func (vm *VirtualMachine) getCellDst(instruction *Instruction) (*mem.Cell, error } // todo(rodro): fix this math - return vm.MemoryManager.Memory.Peek(executionSegment, dstRegister+uint64(instruction.OffDest)) + return vm.MemoryManager.Memory.Peek(ExecutionSegment, dstRegister+uint64(instruction.OffDest)) } func (vm *VirtualMachine) getCellOp0(instruction *Instruction) (*mem.Cell, error) { @@ -169,7 +182,7 @@ func (vm *VirtualMachine) getCellOp0(instruction *Instruction) (*mem.Cell, error } // todo(rodro): fix this math offset := op0Register + uint64(instruction.OffOp0) - return vm.MemoryManager.Memory.Peek(executionSegment, offset) + return vm.MemoryManager.Memory.Peek(ExecutionSegment, offset) } func (vm *VirtualMachine) getCellOp1(instruction *Instruction, op0Cell *mem.Cell) (*mem.Cell, error) { @@ -183,11 +196,11 @@ func (vm *VirtualMachine) getCellOp1(instruction *Instruction, op0Cell *mem.Cell } op1Address = mem.CreateMemoryAddress(op0Address.SegmentIndex, op0Address.Offset) case Imm: - op1Address = mem.CreateMemoryAddress(programSegment, vm.Context.Pc) + op1Address = mem.CreateMemoryAddress(ProgramSegment, vm.Context.Pc) case FpPlusOffOp1: - op1Address = mem.CreateMemoryAddress(executionSegment, vm.Context.Fp) + op1Address = mem.CreateMemoryAddress(ExecutionSegment, vm.Context.Fp) case ApPlusOffOp1: - op1Address = mem.CreateMemoryAddress(executionSegment, vm.Context.Ap) + op1Address = mem.CreateMemoryAddress(ExecutionSegment, vm.Context.Ap) } // todo(rodro): fix this math op1Address.Offset += uint64(instruction.OffOp1) @@ -271,14 +284,14 @@ func (vm *VirtualMachine) opcodeAssertions( switch instruction.Opcode { case Call: // Store at [ap] the current fp - err := dstCell.Write(mem.MemoryValueFromSegmentAndOffset(executionSegment, vm.Context.Fp)) + err := dstCell.Write(mem.MemoryValueFromSegmentAndOffset(ExecutionSegment, vm.Context.Fp)) if err != nil { return err } // Write in [ap + 1] the instruction to execute err = op0Cell.Write( - mem.MemoryValueFromSegmentAndOffset(programSegment, vm.Context.Pc+uint64(instruction.Size())), + mem.MemoryValueFromSegmentAndOffset(ProgramSegment, vm.Context.Pc+uint64(instruction.Size())), ) if err != nil { return err diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 4e7246ff5..0b718b10f 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -21,8 +21,8 @@ func TestVMCreation(t *testing.T) { require.NoError(t, err) assert.Len(t, vm.MemoryManager.Memory.Segments, 2) - assert.Len(t, vm.MemoryManager.Memory.Segments[programSegment].Data, len(dummyBytecode)) - assert.Empty(t, vm.MemoryManager.Memory.Segments[executionSegment].Data) + assert.Len(t, vm.MemoryManager.Memory.Segments[ProgramSegment].Data, len(dummyBytecode)) + assert.Empty(t, vm.MemoryManager.Memory.Segments[ExecutionSegment].Data) } // todo(rodro): test all possible ways of: @@ -278,7 +278,7 @@ func (vm *VirtualMachine) TestUpdateFp(t *testing.T) { } func writeToDataSegment(vm *VirtualMachine, index uint64, value *mem.MemoryValue) { - err := vm.MemoryManager.Memory.Write(executionSegment, index, value) + err := vm.MemoryManager.Memory.Write(ExecutionSegment, index, value) if err != nil { panic("error in test util: writeToDataSegment") } From d6006bc43844d99b8cb109955d62f43100f1af83 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 11:24:19 -0400 Subject: [PATCH 2/9] Add initial tests --- pkg/hintrunner/hint_test.go | 1 + pkg/hintrunner/operand.go | 4 +- pkg/hintrunner/operand_test.go | 89 ++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 pkg/hintrunner/hint_test.go create mode 100644 pkg/hintrunner/operand_test.go diff --git a/pkg/hintrunner/hint_test.go b/pkg/hintrunner/hint_test.go new file mode 100644 index 000000000..3b4d8206c --- /dev/null +++ b/pkg/hintrunner/hint_test.go @@ -0,0 +1 @@ +package hintrunner diff --git a/pkg/hintrunner/operand.go b/pkg/hintrunner/operand.go index f6ac5df12..264dc6ac3 100644 --- a/pkg/hintrunner/operand.go +++ b/pkg/hintrunner/operand.go @@ -44,7 +44,7 @@ type Deref struct { } type DoubleDeref struct { - deref Deref + deref CellRefer offset int16 } @@ -72,7 +72,7 @@ func (deref Deref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { } func (dderef DoubleDeref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { - cell, err := dderef.deref.deref.Get(vm) + cell, err := dderef.deref.Get(vm) if err != nil { return nil, err } diff --git a/pkg/hintrunner/operand_test.go b/pkg/hintrunner/operand_test.go new file mode 100644 index 000000000..c110b515a --- /dev/null +++ b/pkg/hintrunner/operand_test.go @@ -0,0 +1,89 @@ +package hintrunner + +import ( + "testing" + + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" + "github.com/stretchr/testify/require" +) + +func TestGetAp(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 5 + writeTo(vm, VM.ExecutionSegment, vm.Context.Ap+7, memory.MemoryValueFromInt(11)) + + var apCell ApCellRef = 7 + cell, err := apCell.Get(vm) + + require.NoError(t, err) + + value := cell.Read() + require.Equal(t, memory.MemoryValueFromInt(11), value) +} + +func TestGetFp(t *testing.T) { + // todo(rodro): do this with a negative offset, when safemath merged +} + +func TestResolveDeref(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 5 + writeTo(vm, VM.ExecutionSegment, vm.Context.Ap+7, memory.MemoryValueFromInt(11)) + + var apCell ApCellRef = 7 + deref := Deref{apCell} + + value, err := deref.Resolve(vm) + + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromInt(11), value) +} + +func TestResolveDoubleDerefPositiveOffset(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 5 + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Ap+7, + memory.MemoryValueFromSegmentAndOffset(VM.ExecutionSegment, 0), + ) + writeTo( + vm, + VM.ExecutionSegment, 14, + memory.MemoryValueFromInt(13), + ) + + var apCell ApCellRef = 7 + dderf := DoubleDeref{apCell, 14} + + value, err := dderf.Resolve(vm) + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromInt(13), value) +} + +func TestResolveDoubleDerefNegativeOffset(t *testing.T) { + // todo(rodro): do this with a negative offset, when safemath merged +} + +func TestResolveImmediate(t *testing.T) { + +} + +func TestResolveAddOp(t *testing.T) { + +} + +func TestResolveMulOp(t *testing.T) { + +} + +func defaultVirtualMachine() *VM.VirtualMachine { + vm, _ := VM.NewVirtualMachine(make([]*f.Element, 0), VM.VirtualMachineConfig{}) + return vm +} + +func writeTo(vm *VM.VirtualMachine, segment uint64, offset uint64, val *memory.MemoryValue) { + _ = vm.MemoryManager.Memory.Write(segment, offset, val) +} From b650d4739eaf97a1502fee1305d8000769ae245e Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:07:36 -0400 Subject: [PATCH 3/9] Add tests for hintrunner --- pkg/hintrunner/error.go | 17 ++++- pkg/hintrunner/hint_test.go | 41 ++++++++++++ pkg/hintrunner/operand.go | 69 ++++++++++++++------ pkg/hintrunner/operand_test.go | 112 ++++++++++++++++++++++++++++++++- pkg/safemath/safemath.go | 6 +- 5 files changed, 221 insertions(+), 24 deletions(-) diff --git a/pkg/hintrunner/error.go b/pkg/hintrunner/error.go index c7e04cffc..6244e5a53 100644 --- a/pkg/hintrunner/error.go +++ b/pkg/hintrunner/error.go @@ -37,4 +37,19 @@ func (e *HintError) Unwrap() error { return e.err } -// todo(rodro): Should add custom error for operand? +type OperandError struct { + operandName string + err error +} + +func NewOperandError(operandName string, err error) *OperandError { + return &OperandError{operandName, err} +} + +func (e *OperandError) Error() string { + return fmt.Sprintf("failed to get/resolve operand %s: %s", e.operandName, e.err.Error()) +} + +func (e *OperandError) Unwrap() error { + return e.err +} diff --git a/pkg/hintrunner/hint_test.go b/pkg/hintrunner/hint_test.go index 3b4d8206c..908725f8f 100644 --- a/pkg/hintrunner/hint_test.go +++ b/pkg/hintrunner/hint_test.go @@ -1 +1,42 @@ package hintrunner + +import ( + "testing" + + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + "github.com/stretchr/testify/require" +) + +func TestAllocSegment(t *testing.T) { + vm := defaultVirtualMachine() + + vm.Context.Ap = 3 + vm.Context.Fp = 0 + + var ap ApCellRef = 5 + var fp FpCellRef = 9 + + alloc1 := AllocSegment{ap} + alloc2 := AllocSegment{fp} + + err := alloc1.Execute(vm) + t.Log(err) + require.Nil(t, err) + require.Equal(t, 3, len(vm.MemoryManager.Memory.Segments)) + require.Equal( + t, + memory.MemoryValueFromSegmentAndOffset(2, 0), + readFrom(vm, VM.ExecutionSegment, vm.Context.Ap+5), + ) + + err = alloc2.Execute(vm) + require.Nil(t, err) + require.Equal(t, 4, len(vm.MemoryManager.Memory.Segments)) + require.Equal( + t, + memory.MemoryValueFromSegmentAndOffset(3, 0), + readFrom(vm, VM.ExecutionSegment, vm.Context.Fp+9), + ) + +} diff --git a/pkg/hintrunner/operand.go b/pkg/hintrunner/operand.go index 264dc6ac3..d3de79a37 100644 --- a/pkg/hintrunner/operand.go +++ b/pkg/hintrunner/operand.go @@ -4,11 +4,21 @@ import ( "fmt" "math/big" + "github.com/NethermindEth/cairo-vm-go/pkg/safemath" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) +const ( + apCellRefName = "ApCellRef" + fpCellRefName = "FpCellRef" + derefName = "Deref" + doubleDerefName = "DoubleDeref" + immediateName = "Immediate" + binOpName = "BinaryOperator" +) + // // All CellRef definitions @@ -21,15 +31,25 @@ type ApCellRef int16 type FpCellRef int16 func (ap ApCellRef) Get(vm *VM.VirtualMachine) (*memory.Cell, error) { - // todo(rodro): fix maths with safemath from ilia - offset := vm.Context.Ap + uint64(ap) - return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, offset) + res, overflow := safemath.SafeOffset(vm.Context.Ap, int16(ap)) + if overflow { + return nil, NewOperandError( + apCellRefName, + fmt.Errorf("%d + %d is outside of the [0, 2**64) range", vm.Context.Ap, ap), + ) + } + return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, res) } func (fp FpCellRef) Get(vm *VM.VirtualMachine) (*memory.Cell, error) { - // todo(rodro): fix maths with safemath from ilia - offset := vm.Context.Fp + uint64(fp) - return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, offset) + res, overflow := safemath.SafeOffset(vm.Context.Fp, int16(fp)) + if overflow { + return nil, NewOperandError( + fpCellRefName, + fmt.Errorf("%d + %d is outside of the [0, 2**64) range", vm.Context.Ap, fp), + ) + } + return vm.MemoryManager.Memory.Peek(VM.ExecutionSegment, res) } // @@ -66,7 +86,7 @@ type BinaryOp struct { func (deref Deref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { cell, err := deref.deref.Get(vm) if err != nil { - return nil, err + return nil, NewOperandError(derefName, err) } return cell.Read(), nil } @@ -74,25 +94,35 @@ func (deref Deref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { func (dderef DoubleDeref) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { cell, err := dderef.deref.Get(vm) if err != nil { - return nil, err + return nil, NewOperandError(doubleDerefName, err) } lhs := cell.Read() - var res *memory.MemoryValue - if dderef.offset >= 0 { - rhs := memory.MemoryValueFromInt(dderef.offset) - res, err = memory.EmptyMemoryValueAs(lhs.IsAddress()).Add(lhs, rhs) - } else { - rhs := memory.MemoryValueFromInt(Abs(dderef.offset)) - res, err = memory.EmptyMemoryValueAs(lhs.IsAddress()).Sub(lhs, rhs) + // Double deref implies the first value read must be an address + address, err := lhs.ToMemoryAddress() + if err != nil { + return nil, NewOperandError(doubleDerefName, err) + } + + newOffset, overflow := safemath.SafeOffset(address.Offset, dderef.offset) + if overflow { + return nil, NewOperandError( + doubleDerefName, + safemath.NewSafeOffsetError(address.Offset, dderef.offset), + ) } + resAddr := memory.NewMemoryAddress(address.SegmentIndex, newOffset) + + value, err := vm.MemoryManager.Memory.ReadFromAddress(resAddr) if err != nil { - return nil, err + return nil, NewOperandError(doubleDerefName, err) } - return res, nil + return value, nil } +// todo(rodro): Specs from Starkware stablish this can be uint256 and not a felt. +// Should we respect that, or go straight to felt? func (imm Immediate) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) { felt := &f.Element{} bigInt := (big.Int)(imm) @@ -122,5 +152,8 @@ func (bop BinaryOp) Resolve(vm *VM.VirtualMachine) (*memory.MemoryValue, error) return memory.EmptyMemoryValueAsFelt().Mul(lhs, rhs) } - return nil, fmt.Errorf("Unknown operator: %d", bop.operator) + return nil, NewOperandError( + "BinaryOp", + fmt.Errorf("unknown binary operator id: %d", bop.operator), + ) } diff --git a/pkg/hintrunner/operand_test.go b/pkg/hintrunner/operand_test.go index c110b515a..e2e67a7f6 100644 --- a/pkg/hintrunner/operand_test.go +++ b/pkg/hintrunner/operand_test.go @@ -1,6 +1,7 @@ package hintrunner import ( + "math/big" "testing" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" @@ -24,7 +25,17 @@ func TestGetAp(t *testing.T) { } func TestGetFp(t *testing.T) { - // todo(rodro): do this with a negative offset, when safemath merged + vm := defaultVirtualMachine() + vm.Context.Fp = 15 + writeTo(vm, VM.ExecutionSegment, vm.Context.Fp-7, memory.MemoryValueFromInt(11)) + + var fpCell FpCellRef = -7 + cell, err := fpCell.Get(vm) + + require.NoError(t, err) + + value := cell.Read() + require.Equal(t, memory.MemoryValueFromInt(11), value) } func TestResolveDeref(t *testing.T) { @@ -64,18 +75,110 @@ func TestResolveDoubleDerefPositiveOffset(t *testing.T) { } func TestResolveDoubleDerefNegativeOffset(t *testing.T) { - // todo(rodro): do this with a negative offset, when safemath merged + vm := defaultVirtualMachine() + vm.Context.Ap = 5 + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Ap+7, + memory.MemoryValueFromSegmentAndOffset(VM.ExecutionSegment, 20), + ) + writeTo( + vm, + VM.ExecutionSegment, 6, + memory.MemoryValueFromInt(13), + ) + + var apCell ApCellRef = 7 + dderf := DoubleDeref{apCell, -14} + + value, err := dderf.Resolve(vm) + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromInt(13), value) } func TestResolveImmediate(t *testing.T) { + // Immediate does not need the vm for resolving itself + var vm *VM.VirtualMachine = nil + imm := Immediate(*big.NewInt(99)) + + solved, err := imm.Resolve(vm) + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromInt(99), solved) } func TestResolveAddOp(t *testing.T) { + vm := defaultVirtualMachine() + // Set the information used by the lhs + vm.Context.Fp = 0 + vm.Context.Ap = 5 + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Ap+7, + memory.MemoryValueFromSegmentAndOffset(4, 29), + ) + // Set the information used by the rhs + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Fp+20, + memory.MemoryValueFromInt(30), + ) + + // lhs + var ap ApCellRef = 7 + // Rhs + var fp FpCellRef = 20 + deref := Deref{fp} + + operator := Add + + bop := BinaryOp{ + operator: operator, + lhs: ap, + rhs: deref, + } + + res, err := bop.Resolve(vm) + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromSegmentAndOffset(4, 59), res) } func TestResolveMulOp(t *testing.T) { + vm := defaultVirtualMachine() + // Set the information used by the lhs + vm.Context.Fp = 0 + vm.Context.Ap = 5 + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Ap+7, + memory.MemoryValueFromInt(100), + ) + // Set the information used by the rhs + writeTo( + vm, + VM.ExecutionSegment, vm.Context.Fp+20, + memory.MemoryValueFromInt(5), + ) + + // lhs + var ap ApCellRef = 7 + + // Rhs + var fp FpCellRef = 20 + deref := Deref{fp} + + operator := Mul + + bop := BinaryOp{ + operator: operator, + lhs: ap, + rhs: deref, + } + + res, err := bop.Resolve(vm) + require.NoError(t, err) + require.Equal(t, memory.MemoryValueFromInt(500), res) } @@ -87,3 +190,8 @@ func defaultVirtualMachine() *VM.VirtualMachine { func writeTo(vm *VM.VirtualMachine, segment uint64, offset uint64, val *memory.MemoryValue) { _ = vm.MemoryManager.Memory.Write(segment, offset, val) } + +func readFrom(vm *VM.VirtualMachine, segment uint64, offset uint64) *memory.MemoryValue { + val, _ := vm.MemoryManager.Memory.Read(segment, offset) + return val +} diff --git a/pkg/safemath/safemath.go b/pkg/safemath/safemath.go index 98599fc2b..18628637e 100644 --- a/pkg/safemath/safemath.go +++ b/pkg/safemath/safemath.go @@ -26,12 +26,12 @@ func SafeOffset(x uint64, y int16) (res uint64, isOverflow bool) { res = x + enlargedY // Why does this work? // Let's proceed by cases on the most significant bit of (MSB(x)). - // If MSB(x) == 1 and enlargedY < 0 (MSB(enlargedY) == 1) then overflow doesn't happen. - // Let's consider the case enlargedY >= 0 (MSB(enlargedY) == 0). + // If MSB(x) == 1 and y < 0 (MSB(y) == 1) then overflow doesn't happen. + // Let's consider the case y >= 0 (MSB(y) == 0). // In that case we can only wrap up by going to the begining of uint64 range making the MSB(res) = 0. // This is the second disjunct of the disjunctive formula. // - // In the same fashion the case MSB(x) == 0 and MSB(enlargedY) == 1 is reasoned about. + // In the same fashion the case MSB(x) == 0 and MSB(y) == 1 is reasoned about. // // Finally, we boil everything down to MSBs by rotating and anding with ...000001. isOverflow = bits.RotateLeft64((^x&enlargedY&res)|(x & ^enlargedY & ^res), 1)&0x1 != 0 From be0d8c01deb60b5dea53909aa46ecc9259627b4f Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:08:05 -0400 Subject: [PATCH 4/9] Add safemath error --- pkg/safemath/error.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 pkg/safemath/error.go diff --git a/pkg/safemath/error.go b/pkg/safemath/error.go new file mode 100644 index 000000000..f479fc117 --- /dev/null +++ b/pkg/safemath/error.go @@ -0,0 +1,21 @@ +package safemath + +import "fmt" + +type SafeMathError struct { + msg string +} + +func NewSafeOffsetError(a uint64, b int16) *SafeMathError { + return &SafeMathError{ + msg: fmt.Sprintf("offset calculation of %d using %d is out of [0, 2**64) range", a, b), + } +} + +func (e *SafeMathError) Error() string { + return fmt.Sprintf("math error: %s", e.msg) +} + +func (e *SafeMathError) Unwrap() error { + return nil +} From c7c417a84444ddb8ff4b1b5365e73063604636fd Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:08:39 -0400 Subject: [PATCH 5/9] Rename CreateMemoryAddress to NewMemoryAddress --- pkg/vm/memory/memory_value.go | 18 ++++++++++++++++-- pkg/vm/vm.go | 8 ++++---- pkg/vm/vm_test.go | 8 ++++---- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/vm/memory/memory_value.go b/pkg/vm/memory/memory_value.go index dc79a67fc..1649c690b 100644 --- a/pkg/vm/memory/memory_value.go +++ b/pkg/vm/memory/memory_value.go @@ -16,7 +16,7 @@ type MemoryAddress struct { } // Creates a new memory address -func CreateMemoryAddress(segment uint64, offset uint64) *MemoryAddress { +func NewMemoryAddress(segment uint64, offset uint64) *MemoryAddress { return &MemoryAddress{SegmentIndex: segment, Offset: offset} } @@ -46,6 +46,13 @@ func (address *MemoryAddress) Sub(lhs *MemoryAddress, rhs any) (*MemoryAddress, // Then update offset accordingly switch t := rhs.(type) { + case uint64: + rhs64 := rhs.(uint64) + if rhs64 > lhs.Offset { + return nil, fmt.Errorf("rhs offset greater than lhs offset") + } + address.Offset = lhs.Offset - rhs64 + return address, nil case *f.Element: feltRhs := rhs.(*f.Element) if !feltRhs.IsUint64() { @@ -55,7 +62,11 @@ func (address *MemoryAddress) Sub(lhs *MemoryAddress, rhs any) (*MemoryAddress, feltRhs.String(), ) } - address.Offset = lhs.Offset - feltRhs.Uint64() + feltRhs64 := feltRhs.Uint64() + if feltRhs64 > lhs.Offset { + return nil, fmt.Errorf("rhs offset greater than lhs offset") + } + address.Offset = lhs.Offset - feltRhs64 return address, nil case *MemoryAddress: addressRhs := rhs.(*MemoryAddress) @@ -66,6 +77,9 @@ func (address *MemoryAddress) Sub(lhs *MemoryAddress, rhs any) (*MemoryAddress, lhs.String(), ) } + if addressRhs.Offset > lhs.Offset { + return nil, fmt.Errorf("rhs offset greater than lhs offset") + } address.Offset = lhs.Offset - addressRhs.Offset return address, nil default: diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index 0b9bc05ff..6eb4b5fd3 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -201,13 +201,13 @@ func (vm *VirtualMachine) getCellOp1(instruction *Instruction, op0Cell *mem.Cell if err != nil { return nil, fmt.Errorf("expected op0 to be an address: %w", err) } - op1Address = mem.CreateMemoryAddress(op0Address.SegmentIndex, op0Address.Offset) + op1Address = mem.NewMemoryAddress(op0Address.SegmentIndex, op0Address.Offset) case Imm: - op1Address = mem.CreateMemoryAddress(ProgramSegment, vm.Context.Pc) + op1Address = mem.NewMemoryAddress(ProgramSegment, vm.Context.Pc) case FpPlusOffOp1: - op1Address = mem.CreateMemoryAddress(ExecutionSegment, vm.Context.Fp) + op1Address = mem.NewMemoryAddress(ExecutionSegment, vm.Context.Fp) case ApPlusOffOp1: - op1Address = mem.CreateMemoryAddress(ExecutionSegment, vm.Context.Ap) + op1Address = mem.NewMemoryAddress(ExecutionSegment, vm.Context.Ap) } addr, isOverflow := safemath.SafeOffset(op1Address.Offset, instruction.OffOp1) diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index 0b718b10f..3ee37cfd6 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -178,7 +178,7 @@ func TestComputeAddRes(t *testing.T) { cellOp0 := &mem.Cell{ Accessed: true, Value: mem.MemoryValueFromMemoryAddress( - mem.CreateMemoryAddress(2, 10), + mem.NewMemoryAddress(2, 10), ), } @@ -191,7 +191,7 @@ func TestComputeAddRes(t *testing.T) { require.NoError(t, err) expected := mem.MemoryValueFromMemoryAddress( - mem.CreateMemoryAddress(2, 25), + mem.NewMemoryAddress(2, 25), ) assert.Equal(t, expected, res) @@ -206,7 +206,7 @@ func (vm *VirtualMachine) TestOpcodeAssertionAssertEq(t *testing.T) { } dstCell := mem.Cell{} - res := mem.MemoryValueFromMemoryAddress(mem.CreateMemoryAddress(2, 10)) + res := mem.MemoryValueFromMemoryAddress(mem.NewMemoryAddress(2, 10)) err = vm.opcodeAssertions(&instruction, &dstCell, nil, res) require.NoError(t, err) @@ -214,7 +214,7 @@ func (vm *VirtualMachine) TestOpcodeAssertionAssertEq(t *testing.T) { t, mem.Cell{ Accessed: true, - Value: mem.MemoryValueFromMemoryAddress(mem.CreateMemoryAddress(2, 10))}, + Value: mem.MemoryValueFromMemoryAddress(mem.NewMemoryAddress(2, 10))}, dstCell, ) } From d9c05cf0179e92f327b667ba53ff868e69ecd70e Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:12:48 -0400 Subject: [PATCH 6/9] remove unused abs function --- pkg/hintrunner/hintrunner.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go index 7a68a6270..1fc962bec 100644 --- a/pkg/hintrunner/hintrunner.go +++ b/pkg/hintrunner/hintrunner.go @@ -2,7 +2,6 @@ package hintrunner import ( VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" - "golang.org/x/exp/constraints" ) // todo: Can two or more hints be assigned to a specific PC? @@ -27,11 +26,3 @@ func (hr HintRunner) RunHint(vm *VM.VirtualMachine) *HintRunnerError { } return nil } - -// move this function to a possible util packages -func Abs[T constraints.Signed](num T) T { - if num >= 0 { - return num - } - return num * -1 -} From 1b7421b0d5424f41524c33c0a9644f048985265c Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:35:25 -0400 Subject: [PATCH 7/9] reorg and refactor --- pkg/hintrunner/hint.go | 4 ++-- pkg/hintrunner/hint_test.go | 1 - pkg/hintrunner/operand_test.go | 15 --------------- pkg/hintrunner/testutils.go | 21 +++++++++++++++++++++ 4 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 pkg/hintrunner/testutils.go diff --git a/pkg/hintrunner/hint.go b/pkg/hintrunner/hint.go index 202c22537..ee4e7f461 100644 --- a/pkg/hintrunner/hint.go +++ b/pkg/hintrunner/hint.go @@ -13,11 +13,11 @@ type AllocSegment struct { dst CellRefer } -func (h AllocSegment) Execute(vm *VM.VirtualMachine) *HintError { +func (hint AllocSegment) Execute(vm *VM.VirtualMachine) *HintError { segmentIndex := vm.MemoryManager.Memory.AllocateEmptySegment() memAddress := memory.MemoryValueFromSegmentAndOffset(segmentIndex, 0) - cell, err := h.dst.Get(vm) + cell, err := hint.dst.Get(vm) if err != nil { return NewHintError("AllocSegment", err) } diff --git a/pkg/hintrunner/hint_test.go b/pkg/hintrunner/hint_test.go index 908725f8f..362f69c9e 100644 --- a/pkg/hintrunner/hint_test.go +++ b/pkg/hintrunner/hint_test.go @@ -10,7 +10,6 @@ import ( func TestAllocSegment(t *testing.T) { vm := defaultVirtualMachine() - vm.Context.Ap = 3 vm.Context.Fp = 0 diff --git a/pkg/hintrunner/operand_test.go b/pkg/hintrunner/operand_test.go index e2e67a7f6..4e8359461 100644 --- a/pkg/hintrunner/operand_test.go +++ b/pkg/hintrunner/operand_test.go @@ -6,7 +6,6 @@ import ( VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" - f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" "github.com/stretchr/testify/require" ) @@ -181,17 +180,3 @@ func TestResolveMulOp(t *testing.T) { require.Equal(t, memory.MemoryValueFromInt(500), res) } - -func defaultVirtualMachine() *VM.VirtualMachine { - vm, _ := VM.NewVirtualMachine(make([]*f.Element, 0), VM.VirtualMachineConfig{}) - return vm -} - -func writeTo(vm *VM.VirtualMachine, segment uint64, offset uint64, val *memory.MemoryValue) { - _ = vm.MemoryManager.Memory.Write(segment, offset, val) -} - -func readFrom(vm *VM.VirtualMachine, segment uint64, offset uint64) *memory.MemoryValue { - val, _ := vm.MemoryManager.Memory.Read(segment, offset) - return val -} diff --git a/pkg/hintrunner/testutils.go b/pkg/hintrunner/testutils.go new file mode 100644 index 000000000..83a77dcf9 --- /dev/null +++ b/pkg/hintrunner/testutils.go @@ -0,0 +1,21 @@ +package hintrunner + +import ( + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" +) + +func defaultVirtualMachine() *VM.VirtualMachine { + vm, _ := VM.NewVirtualMachine(make([]*f.Element, 0), VM.VirtualMachineConfig{}) + return vm +} + +func writeTo(vm *VM.VirtualMachine, segment uint64, offset uint64, val *memory.MemoryValue) { + _ = vm.MemoryManager.Memory.Write(segment, offset, val) +} + +func readFrom(vm *VM.VirtualMachine, segment uint64, offset uint64) *memory.MemoryValue { + val, _ := vm.MemoryManager.Memory.Read(segment, offset) + return val +} From afb6d2a22317edc16fb731083eda6fab0fc54253 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 16:35:39 -0400 Subject: [PATCH 8/9] Add tests for hintrunner --- pkg/hintrunner/hintrunner.go | 4 +-- pkg/hintrunner/hintrunner_test.go | 47 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 pkg/hintrunner/hintrunner_test.go diff --git a/pkg/hintrunner/hintrunner.go b/pkg/hintrunner/hintrunner.go index 1fc962bec..c91abd147 100644 --- a/pkg/hintrunner/hintrunner.go +++ b/pkg/hintrunner/hintrunner.go @@ -10,8 +10,8 @@ type HintRunner struct { hints map[uint64]Hinter } -func CreateHintRunner() *HintRunner { - return nil +func CreateHintRunner(hints map[uint64]Hinter) HintRunner { + return HintRunner{hints} } func (hr HintRunner) RunHint(vm *VM.VirtualMachine) *HintRunnerError { diff --git a/pkg/hintrunner/hintrunner_test.go b/pkg/hintrunner/hintrunner_test.go new file mode 100644 index 000000000..4e49278ac --- /dev/null +++ b/pkg/hintrunner/hintrunner_test.go @@ -0,0 +1,47 @@ +package hintrunner + +import ( + "testing" + + VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" + "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + "github.com/stretchr/testify/require" +) + +func TestExistingHint(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 3 + + var ap ApCellRef = 5 + allocHint := AllocSegment{ap} + + hr := CreateHintRunner(map[uint64]Hinter{ + 10: allocHint, + }) + + vm.Context.Pc = 10 + err := hr.RunHint(vm) + require.Nil(t, err) + require.Equal( + t, + memory.MemoryValueFromSegmentAndOffset(2, 0), + readFrom(vm, VM.ExecutionSegment, vm.Context.Ap+5), + ) +} + +func TestNoHint(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 3 + + var ap ApCellRef = 5 + allocHint := AllocSegment{ap} + + hr := CreateHintRunner(map[uint64]Hinter{ + 10: allocHint, + }) + + vm.Context.Pc = 100 + err := hr.RunHint(vm) + require.Nil(t, err) + require.Equal(t, 2, len(vm.MemoryManager.Memory.Segments)) +} From faedce83313fef69e0540bf653f4324165a8239f Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Tue, 29 Aug 2023 17:22:54 -0400 Subject: [PATCH 9/9] Add TestLessThan hint --- pkg/hintrunner/error.go | 1 + pkg/hintrunner/hint.go | 54 +++++++++++++++++++++++++++++++++-- pkg/hintrunner/hint_test.go | 57 +++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/pkg/hintrunner/error.go b/pkg/hintrunner/error.go index 6244e5a53..b8ae9c81b 100644 --- a/pkg/hintrunner/error.go +++ b/pkg/hintrunner/error.go @@ -37,6 +37,7 @@ func (e *HintError) Unwrap() error { return e.err } +// OperandError is returned when the error is detected during an operand get/resolve execution type OperandError struct { operandName string err error diff --git a/pkg/hintrunner/hint.go b/pkg/hintrunner/hint.go index ee4e7f461..14d06cd81 100644 --- a/pkg/hintrunner/hint.go +++ b/pkg/hintrunner/hint.go @@ -3,12 +3,15 @@ package hintrunner import ( VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" "github.com/NethermindEth/cairo-vm-go/pkg/vm/memory" + f "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) type Hinter interface { Execute(vm *VM.VirtualMachine) *HintError } +const allocSegmentName = "AllocSegment" + type AllocSegment struct { dst CellRefer } @@ -19,12 +22,59 @@ func (hint AllocSegment) Execute(vm *VM.VirtualMachine) *HintError { cell, err := hint.dst.Get(vm) if err != nil { - return NewHintError("AllocSegment", err) + return NewHintError(allocSegmentName, err) } err = cell.Write(memAddress) if err != nil { - return NewHintError("AllocSegment", err) + return NewHintError(allocSegmentName, err) + } + + return nil +} + +const testLessThanName = "TestLessThan" + +type TestLessThan struct { + dst CellRefer + lhs ResOperander + rhs ResOperander +} + +func (hint TestLessThan) Execute(vm *VM.VirtualMachine) *HintError { + lhsVal, err := hint.lhs.Resolve(vm) + if err != nil { + return NewHintError(testLessThanName, err) + } + + rhsVal, err := hint.rhs.Resolve(vm) + if err != nil { + return NewHintError(testLessThanName, err) + } + + lhsFelt, err := lhsVal.ToFieldElement() + if err != nil { + return NewHintError(testLessThanName, err) + } + + rhsFelt, err := rhsVal.ToFieldElement() + if err != nil { + return NewHintError(testLessThanName, err) + } + + resFelt := f.Element{} + if lhsFelt.Cmp(rhsFelt) <= 0 { + resFelt.SetOne() + } + + dstCell, err := hint.dst.Get(vm) + if err != nil { + return NewHintError(testLessThanName, err) + } + + err = dstCell.Write(memory.MemoryValueFromFieldElement(&resFelt)) + if err != nil { + return NewHintError(testLessThanName, err) } return nil diff --git a/pkg/hintrunner/hint_test.go b/pkg/hintrunner/hint_test.go index 362f69c9e..3178e20eb 100644 --- a/pkg/hintrunner/hint_test.go +++ b/pkg/hintrunner/hint_test.go @@ -1,6 +1,7 @@ package hintrunner import ( + "math/big" "testing" VM "github.com/NethermindEth/cairo-vm-go/pkg/vm" @@ -39,3 +40,59 @@ func TestAllocSegment(t *testing.T) { ) } + +func TestTestLessThanFalse(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 0 + vm.Context.Fp = 0 + writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(17)) + + var dst ApCellRef = 1 + + lhs := Immediate(*big.NewInt(32)) + + var rhsRef FpCellRef = 0 + rhs := Deref{rhsRef} + + hint := TestLessThan{ + dst: dst, + lhs: lhs, + rhs: rhs, + } + + err := hint.Execute(vm) + require.Nil(t, err) + require.Equal( + t, + memory.EmptyMemoryValueAsFelt(), + readFrom(vm, VM.ExecutionSegment, 1), + ) +} + +func TestTestLessThanTrue(t *testing.T) { + vm := defaultVirtualMachine() + vm.Context.Ap = 0 + vm.Context.Fp = 0 + writeTo(vm, VM.ExecutionSegment, 0, memory.MemoryValueFromInt(23)) + + var dst ApCellRef = 1 + + lhs := Immediate(*big.NewInt(13)) + + var rhsRef FpCellRef = 0 + rhs := Deref{rhsRef} + + hint := TestLessThan{ + dst: dst, + lhs: lhs, + rhs: rhs, + } + + err := hint.Execute(vm) + require.Nil(t, err) + require.Equal( + t, + memory.MemoryValueFromInt(1), + readFrom(vm, VM.ExecutionSegment, 1), + ) +}