From 598576d44be76264970d9cfa736c03bdf7059b01 Mon Sep 17 00:00:00 2001 From: Tristan <122918260+TAdev0@users.noreply.github.com> Date: Fri, 28 Jun 2024 08:00:28 +0200 Subject: [PATCH] feat: `usort` hints integration tests (#459) * usort integration test * multiple bugs corrected * remove compiled.json * usort integration test final commit * put everything in order * usort ok * improve usort test content * fmt * fmt --------- Co-authored-by: Shourya Goel --- .../cairo_zero_hint_tests/usort.small.cairo | 59 ++++ pkg/hintrunner/zero/hintcode.go | 39 ++- pkg/hintrunner/zero/zerohint_usort.go | 191 ++++++------ pkg/hintrunner/zero/zerohint_usort_test.go | 281 +++++++++--------- pkg/hintrunner/zero/zerohint_utils_test.go | 8 + 5 files changed, 320 insertions(+), 258 deletions(-) create mode 100644 integration_tests/cairo_zero_hint_tests/usort.small.cairo diff --git a/integration_tests/cairo_zero_hint_tests/usort.small.cairo b/integration_tests/cairo_zero_hint_tests/usort.small.cairo new file mode 100644 index 000000000..f5b4334ce --- /dev/null +++ b/integration_tests/cairo_zero_hint_tests/usort.small.cairo @@ -0,0 +1,59 @@ +%builtins range_check +from starkware.cairo.common.usort import usort +from starkware.cairo.common.alloc import alloc + +func main{range_check_ptr}() -> () { + alloc_locals; + let (input_array: felt*) = alloc(); + assert input_array[0] = 8; + assert input_array[1] = 9; + assert input_array[2] = 7; + + let (output_len, output, multiplicities) = usort(input_len=3, input=input_array); + + assert output_len = 3; + assert output[0] = 7; + assert output[1] = 8; + assert output[2] = 9; + + assert multiplicities[0] = 1; + assert multiplicities[1] = 1; + assert multiplicities[2] = 1; + + let (input_array: felt*) = alloc(); + assert input_array[0] = 11; + assert input_array[1] = 24; + assert input_array[2] = 99; + assert input_array[3] = 2; + assert input_array[4] = 66; + assert input_array[5] = 49; + assert input_array[6] = 11; + assert input_array[7] = 23; + assert input_array[8] = 88; + assert input_array[9] = 7; + + let (output_len, output, multiplicities) = usort(input_len=10, input=input_array); + + assert output_len = 9; + assert output[0] = 2; + assert output[1] = 7; + assert output[2] = 11; + assert output[3] = 23; + assert output[4] = 24; + assert output[5] = 49; + assert output[6] = 66; + assert output[7] = 88; + assert output[8] = 99; + + assert multiplicities[0] = 1; + assert multiplicities[1] = 1; + assert multiplicities[2] = 2; + assert multiplicities[3] = 1; + assert multiplicities[4] = 1; + assert multiplicities[5] = 1; + assert multiplicities[6] = 1; + assert multiplicities[7] = 1; + assert multiplicities[8] = 1; + + return (); +} diff --git a/pkg/hintrunner/zero/hintcode.go b/pkg/hintrunner/zero/hintcode.go index ba48f5db8..5e58d7550 100644 --- a/pkg/hintrunner/zero/hintcode.go +++ b/pkg/hintrunner/zero/hintcode.go @@ -59,26 +59,25 @@ const ( uint256MulDivModCode string = "a = (ids.a.high << 128) + ids.a.low\nb = (ids.b.high << 128) + ids.b.low\ndiv = (ids.div.high << 128) + ids.div.low\nquotient, remainder = divmod(a * b, div)\n\nids.quotient_low.low = quotient & ((1 << 128) - 1)\nids.quotient_low.high = (quotient >> 128) & ((1 << 128) - 1)\nids.quotient_high.low = (quotient >> 256) & ((1 << 128) - 1)\nids.quotient_high.high = quotient >> 384\nids.remainder.low = remainder & ((1 << 128) - 1)\nids.remainder.high = remainder >> 128" // ------ Usort hints related code ------ - usortBodyCode string = ` - from collections import defaultdict - - input_ptr = ids.input - input_len = int(ids.input_len) - if __usort_max_size is not None: - assert input_len <= __usort_max_size, ( - f"usort() can only be used with input_len<={__usort_max_size}. " - f"Got: input_len={input_len}." - ) - - positions_dict = defaultdict(list) - for i in range(input_len): - val = memory[input_ptr + i] - positions_dict[val].append(i) - - output = sorted(positions_dict.keys()) - ids.output_len = len(output) - ids.output = segments.gen_arg(output) - ids.multiplicities = segments.gen_arg([len(positions_dict[k]) for k in output])` + usortBodyCode string = `from collections import defaultdict + +input_ptr = ids.input +input_len = int(ids.input_len) +if __usort_max_size is not None: + assert input_len <= __usort_max_size, ( + f"usort() can only be used with input_len<={__usort_max_size}. " + f"Got: input_len={input_len}." + ) + +positions_dict = defaultdict(list) +for i in range(input_len): + val = memory[input_ptr + i] + positions_dict[val].append(i) + +output = sorted(positions_dict.keys()) +ids.output_len = len(output) +ids.output = segments.gen_arg(output) +ids.multiplicities = segments.gen_arg([len(positions_dict[k]) for k in output])` usortEnterScopeCode string = "vm_enter_scope(dict(__usort_max_size = globals().get('__usort_max_size')))" usortVerifyMultiplicityAssertCode string = "assert len(positions) == 0" usortVerifyCode string = "last_pos = 0\npositions = positions_dict[ids.value][::-1]" diff --git a/pkg/hintrunner/zero/zerohint_usort.go b/pkg/hintrunner/zero/zerohint_usort.go index 85a1bb59b..5a6d8bc8f 100644 --- a/pkg/hintrunner/zero/zerohint_usort.go +++ b/pkg/hintrunner/zero/zerohint_usort.go @@ -12,33 +12,30 @@ import ( "github.com/consensys/gnark-crypto/ecc/stark-curve/fp" ) -func createUsortBodyHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { - input, err := resolver.GetResOperander("input") - if err != nil { - return nil, err - } - - input_len, err := resolver.GetResOperander("input_len") - if err != nil { - return nil, err - } - - output, err := resolver.GetResOperander("output") - if err != nil { - return nil, err - } +// UsortEnterScope hint enters a new scope with `__usort_max_size` value +// +// `newUsortEnterScopeHint` doesn't take any operander as argument +// +// `newUsortEnterScopeHint` gets `__usort_max_size` value from the current +// scope and enters a new scope with this same value +func newUsortEnterScopeHint() hinter.Hinter { + return &GenericZeroHinter{ + Name: "UsortEnterScope", + Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { + //> vm_enter_scope(dict(__usort_max_size = globals().get('__usort_max_size'))) + usortMaxSize := uint64(1 << 20) - output_len, err := resolver.GetResOperander("output_len") - if err != nil { - return nil, err - } + ctx.ScopeManager.EnterScope(map[string]any{ + "__usort_max_size": usortMaxSize, + }) - multiplicities, err := resolver.GetResOperander("multiplicities") - if err != nil { - return nil, err + return nil + }, } +} - return newUsortBodyHint(input, input_len, output, output_len, multiplicities), nil +func createUsortEnterScopeHinter() (hinter.Hinter, error) { + return newUsortEnterScopeHint(), nil } // UsortBody hint sorts the input array of field elements. The sorting results in generation of output array without duplicates and multiplicites array, where each element represents the number of times the corresponding element in the output array appears in the input array. The output and multiplicities arrays are written to the new, separate segments in memory. @@ -109,6 +106,11 @@ func newUsortBodyHint(input, inputLen, output, outputLen, multiplicities hinter. } } + err = ctx.ScopeManager.AssignVariable("positions_dict", positionsDict) + if err != nil { + return err + } + outputArray := make([]fp.Element, len(positionsDict)) iterator := 0 for key := range positionsDict { @@ -189,70 +191,33 @@ func newUsortBodyHint(input, inputLen, output, outputLen, multiplicities hinter. } } -// UsortEnterScope hint enters a new scope with `__usort_max_size` value -// -// `newUsortEnterScopeHint` doesn't take any operander as argument -// -// `newUsortEnterScopeHint` gets `__usort_max_size` value from the current -// scope and enters a new scope with this same value -func newUsortEnterScopeHint() hinter.Hinter { - return &GenericZeroHinter{ - Name: "UsortEnterScope", - Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { - //> vm_enter_scope(dict(__usort_max_size = globals().get('__usort_max_size'))) - usortMaxSize, err := ctx.ScopeManager.GetVariableValue("__usort_max_size") - if err != nil { - return err - } - - ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": usortMaxSize, - }) - - return nil - }, +func createUsortBodyHinter(resolver hintReferenceResolver) (hinter.Hinter, error) { + input, err := resolver.GetResOperander("input") + if err != nil { + return nil, err } -} - -func createUsortEnterScopeHinter() (hinter.Hinter, error) { - return newUsortEnterScopeHint(), nil -} -// UsortVerifyMultiplicityAssert hint checks that the `positions` variable in scope -// doesn't contain any value -// -// `newUsortVerifyMultiplicityAssertHint` doesn't take any operander as argument -// -// This hint is used when sorting an array of field elements while removing duplicates -// in `usort` Cairo function -func newUsortVerifyMultiplicityAssertHint() hinter.Hinter { - return &GenericZeroHinter{ - Name: "UsortVerifyMultiplicityAssert", - Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { - //> assert len(positions) == 0 - - positionsInterface, err := ctx.ScopeManager.GetVariableValue("positions") - - if err != nil { - return err - } + input_len, err := resolver.GetResOperander("input_len") + if err != nil { + return nil, err + } - positions, ok := positionsInterface.([]uint64) - if !ok { - return fmt.Errorf("casting positions into an array failed") - } + output, err := resolver.GetResOperander("output") + if err != nil { + return nil, err + } - if len(positions) != 0 { - return fmt.Errorf("assertion `len(positions) == 0` failed") - } + output_len, err := resolver.GetResOperander("output_len") + if err != nil { + return nil, err + } - return nil - }, + multiplicities, err := resolver.GetResOperander("multiplicities") + if err != nil { + return nil, err } -} -func createUsortVerifyMultiplicityAssertHinter() (hinter.Hinter, error) { - return newUsortEnterScopeHint(), nil + return newUsortBodyHint(input, input_len, output, output_len, multiplicities), nil } // UsortVerify hint prepares for verifying the presence of duplicates of @@ -273,28 +238,29 @@ func newUsortVerifyHint(value hinter.ResOperander) hinter.Hinter { //> positions = positions_dict[ids.value][::-1] positionsDictInterface, err := ctx.ScopeManager.GetVariableValue("positions_dict") - if err != nil { return err } positionsDict, ok := positionsDictInterface.(map[fp.Element][]uint64) - if !ok { return fmt.Errorf("casting positions_dict into an dictionary failed") } value, err := hinter.ResolveAsFelt(vm, value) - if err != nil { return err } - positions := positionsDict[*value] + positionsToCopy := positionsDict[*value] + + positions := make([]uint64, len(positionsToCopy)) + copy(positions, positionsToCopy) + utils.Reverse(positions) return ctx.ScopeManager.AssignVariables(map[string]any{ - "last_pos": 0, + "last_pos": uint64(0), "positions": positions, }) }, @@ -333,9 +299,9 @@ func newUsortVerifyMultiplicityBodyHint(nextItemIndex hinter.ResOperander) hinte return err } - positions, ok := positionsInterface.([]fp.Element) + positions, ok := positionsInterface.([]uint64) if !ok { - return fmt.Errorf("cannot cast positionsInterface to []fp.Element") + return fmt.Errorf("cannot cast positionsInterface to []uint64") } currentPos, err := utils.Pop(&positions) @@ -348,20 +314,19 @@ func newUsortVerifyMultiplicityBodyHint(nextItemIndex hinter.ResOperander) hinte return err } - lastPos, err := ctx.ScopeManager.GetVariableValue("last_pos") + lastPosition, err := ctx.ScopeManager.GetVariableValue("last_pos") if err != nil { return err } - lastPosFelt, ok := lastPos.(fp.Element) + lastPos, ok := lastPosition.(uint64) if !ok { - return fmt.Errorf("cannot cast last_pos to felt") + return fmt.Errorf("cannot cast last_pos to uint64") } // Calculate `next_item_index` memory value - var newNextItemIndexValue fp.Element - newNextItemIndexValue.Sub(¤tPos, &lastPosFelt) - newNextItemIndexMemoryValue := memory.MemoryValueFromFieldElement(&newNextItemIndexValue) + newNextItemIndexValue := currentPos - lastPos + newNextItemIndexMemoryValue := memory.MemoryValueFromUint(newNextItemIndexValue) // Save `next_item_index` value in address addrNextItemIndex, err := nextItemIndex.GetAddress(vm) @@ -369,12 +334,12 @@ func newUsortVerifyMultiplicityBodyHint(nextItemIndex hinter.ResOperander) hinte return err } - err = vm.Memory.WriteToAddress(&addrNextItemIndex, &newNextItemIndexMemoryValue) + err = ctx.ScopeManager.AssignVariable("last_pos", currentPos+1) if err != nil { return err } - return ctx.ScopeManager.AssignVariable("last_pos", *currentPos.Add(¤tPos, &utils.FeltOne)) + return vm.Memory.WriteToAddress(&addrNextItemIndex, &newNextItemIndexMemoryValue) }, } } @@ -387,3 +352,39 @@ func createUsortVerifyMultiplicityBodyHinter(resolver hintReferenceResolver) (hi return newUsortVerifyMultiplicityBodyHint(nextItemIndex), nil } + +// UsortVerifyMultiplicityAssert hint checks that the `positions` variable in scope +// doesn't contain any value +// +// `newUsortVerifyMultiplicityAssertHint` doesn't take any operander as argument +// +// This hint is used when sorting an array of field elements while removing duplicates +// in `usort` Cairo function +func newUsortVerifyMultiplicityAssertHint() hinter.Hinter { + return &GenericZeroHinter{ + Name: "UsortVerifyMultiplicityAssert", + Op: func(vm *VM.VirtualMachine, ctx *hinter.HintRunnerContext) error { + //> assert len(positions) == 0 + + positionsInterface, err := ctx.ScopeManager.GetVariableValue("positions") + if err != nil { + return err + } + + positions, ok := positionsInterface.([]uint64) + if !ok { + return fmt.Errorf("casting positions into a []uint64 failed") + } + + if len(positions) != 0 { + return fmt.Errorf("assertion `len(positions) == 0` failed") + } + + return nil + }, + } +} + +func createUsortVerifyMultiplicityAssertHinter() (hinter.Hinter, error) { + return newUsortVerifyMultiplicityAssertHint(), nil +} diff --git a/pkg/hintrunner/zero/zerohint_usort_test.go b/pkg/hintrunner/zero/zerohint_usort_test.go index 3faa0e924..c8ebe89aa 100644 --- a/pkg/hintrunner/zero/zerohint_usort_test.go +++ b/pkg/hintrunner/zero/zerohint_usort_test.go @@ -14,145 +14,10 @@ func TestZeroHintUsort(t *testing.T) { runHinterTests(t, map[string][]hintTestCase{ "UsortEnterScope": { { - ctxInit: func(ctx *hinter.HintRunnerContext) { - ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(1), - }) - }, makeHinter: func(ctx *hintTestContext) hinter.Hinter { return newUsortEnterScopeHint() }, - check: varValueInScopeEquals("__usort_max_size", uint64(1)), - }, - }, - "UsortVerifyMultiplicityAssert": { - { - ctxInit: func(ctx *hinter.HintRunnerContext) { - err := ctx.ScopeManager.AssignVariable("positions", []uint64{1}) - if err != nil { - t.Fatal(err) - } - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityAssertHint() - }, - errCheck: errorTextContains("assertion `len(positions) == 0` failed"), - }, - { - ctxInit: func(ctx *hinter.HintRunnerContext) { - err := ctx.ScopeManager.AssignVariable("positions", []uint64{}) - if err != nil { - t.Fatal(err) - } - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityAssertHint() - }, - errCheck: errorIsNil, - }, - }, - "UsortVerify": { - { - ctxInit: func(ctx *hinter.HintRunnerContext) { - err := ctx.ScopeManager.AssignVariable("positions_dict", map[fp.Element][]uint64{ - *feltUint64(0): {1, 2, 3}, - }) - if err != nil { - t.Fatal(err) - } - }, - operanders: []*hintOperander{ - {Name: "value", Kind: fpRelative, Value: feltUint64(0)}, - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyHint(ctx.operanders["value"]) - }, - check: func(t *testing.T, ctx *hintTestContext) { - positions, err := ctx.runnerContext.ScopeManager.GetVariableValue("positions") - require.NoError(t, err) - - require.Equal(t, []uint64{3, 2, 1}, positions) - - lastPos, err := ctx.runnerContext.ScopeManager.GetVariableValue("last_pos") - require.NoError(t, err) - - require.Equal(t, 0, lastPos) - }, - }, - }, - "UsortVerifyMultiplicityBody": { - // Tests when no variables (positions, last_pos) are in the scope. - { - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) - }, - errCheck: func(t *testing.T, ctx *hintTestContext, err error) { - require.NotNil(t, err) - }, - }, - // Tests when we can calculate new memory and variable values. - { - operanders: []*hintOperander{ - {Name: "next_item_index", Kind: uninitialized}, - }, - ctxInit: func(ctx *hinter.HintRunnerContext) { - ctx.ScopeManager.EnterScope(map[string]any{ - "positions": []fp.Element{*feltInt64(8), *feltInt64(6), *feltInt64(4)}, - "last_pos": *feltInt64(2), - }) - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) - }, - check: func(t *testing.T, ctx *hintTestContext) { - allVarValueInScopeEquals(map[string]any{ - "last_pos": *feltInt64(5), "positions": []fp.Element{*feltInt64(8), *feltInt64(6)}, - })(t, ctx) - - varValueEquals("next_item_index", feltInt64(2))(t, ctx) - }, - }, - { - operanders: []*hintOperander{ - {Name: "next_item_index", Kind: uninitialized}, - }, - ctxInit: func(ctx *hinter.HintRunnerContext) { - ctx.ScopeManager.EnterScope(map[string]any{ - "positions": []fp.Element{*feltInt64(90), *feltInt64(80), *feltInt64(70), *feltInt64(60), *feltInt64(50)}, - "last_pos": *feltInt64(0), - }) - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) - }, - check: func(t *testing.T, ctx *hintTestContext) { - allVarValueInScopeEquals(map[string]any{ - "last_pos": *feltInt64(51), "positions": []fp.Element{*feltInt64(90), *feltInt64(80), *feltInt64(70), *feltInt64(60)}, - })(t, ctx) - - varValueEquals("next_item_index", feltInt64(50))(t, ctx) - }, - }, - { - operanders: []*hintOperander{ - {Name: "next_item_index", Kind: uninitialized}, - }, - ctxInit: func(ctx *hinter.HintRunnerContext) { - ctx.ScopeManager.EnterScope(map[string]any{ - "positions": []fp.Element{*feltInt64(87), *feltInt64(51), *feltInt64(43), *feltInt64(37)}, - "last_pos": *feltInt64(29), - }) - }, - makeHinter: func(ctx *hintTestContext) hinter.Hinter { - return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) - }, - check: func(t *testing.T, ctx *hintTestContext) { - allVarValueInScopeEquals(map[string]any{ - "last_pos": *feltInt64(38), "positions": []fp.Element{*feltInt64(87), *feltInt64(51), *feltInt64(43)}, - })(t, ctx) - - varValueEquals("next_item_index", feltInt64(8))(t, ctx) - }, + check: varValueInScopeEquals("__usort_max_size", uint64(1<<20)), }, }, "UsortBody": { @@ -160,7 +25,7 @@ func TestZeroHintUsort(t *testing.T) { // input length greater then allowed size operanders: []*hintOperander{ {Name: "input", Kind: apRelative, Value: addr(7)}, - {Name: "input_length", Kind: apRelative, Value: feltUint64(20)}, + {Name: "input_length", Kind: apRelative, Value: feltUint64(1<<20 + 1)}, {Name: "output", Kind: uninitialized}, {Name: "output_length", Kind: uninitialized}, {Name: "multiplicities", Kind: uninitialized}, @@ -170,10 +35,10 @@ func TestZeroHintUsort(t *testing.T) { }, ctxInit: func(ctx *hinter.HintRunnerContext) { ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(10), + "__usort_max_size": uint64(1 << 20), }) }, - errCheck: errorTextContains(fmt.Sprintf("usort() can only be used with input_len<=%d.\n Got: input_len=%d", 10, 20)), + errCheck: errorTextContains(fmt.Sprintf("usort() can only be used with input_len<=%d.\n Got: input_len=%d", 1048576, 1048577)), }, { // sort items with multiplicity of 1 @@ -192,7 +57,7 @@ func TestZeroHintUsort(t *testing.T) { }, ctxInit: func(ctx *hinter.HintRunnerContext) { ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(100), + "__usort_max_size": uint64(1 << 20), }) }, check: func(t *testing.T, ctx *hintTestContext) { @@ -228,7 +93,7 @@ func TestZeroHintUsort(t *testing.T) { }, ctxInit: func(ctx *hinter.HintRunnerContext) { ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(100), + "__usort_max_size": uint64(1 << 20), }) }, check: func(t *testing.T, ctx *hintTestContext) { @@ -269,7 +134,7 @@ func TestZeroHintUsort(t *testing.T) { }, ctxInit: func(ctx *hinter.HintRunnerContext) { ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(100), + "__usort_max_size": uint64(1 << 20), }) }, check: func(t *testing.T, ctx *hintTestContext) { @@ -306,7 +171,7 @@ func TestZeroHintUsort(t *testing.T) { }, ctxInit: func(ctx *hinter.HintRunnerContext) { ctx.ScopeManager.EnterScope(map[string]any{ - "__usort_max_size": uint64(100), + "__usort_max_size": uint64(1 << 20), }) }, check: func(t *testing.T, ctx *hintTestContext) { @@ -316,5 +181,135 @@ func TestZeroHintUsort(t *testing.T) { }, }, }, + "UsortVerify": { + { + ctxInit: func(ctx *hinter.HintRunnerContext) { + err := ctx.ScopeManager.AssignVariable("positions_dict", map[fp.Element][]uint64{ + *feltUint64(0): {1, 2, 3}, + }) + if err != nil { + t.Fatal(err) + } + }, + operanders: []*hintOperander{ + {Name: "value", Kind: fpRelative, Value: feltUint64(0)}, + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyHint(ctx.operanders["value"]) + }, + check: func(t *testing.T, ctx *hintTestContext) { + positions, err := ctx.runnerContext.ScopeManager.GetVariableValue("positions") + require.NoError(t, err) + + require.Equal(t, []uint64{3, 2, 1}, positions) + + lastPos, err := ctx.runnerContext.ScopeManager.GetVariableValue("last_pos") + require.NoError(t, err) + + require.Equal(t, uint64(0), lastPos) + }, + }, + }, + "UsortVerifyMultiplicityBody": { + // Tests when no variables (positions, last_pos) are in the scope. + { + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) + }, + errCheck: func(t *testing.T, ctx *hintTestContext, err error) { + require.NotNil(t, err) + }, + }, + // Tests when we can calculate new memory and variable values. + { + operanders: []*hintOperander{ + {Name: "next_item_index", Kind: uninitialized}, + }, + ctxInit: func(ctx *hinter.HintRunnerContext) { + ctx.ScopeManager.EnterScope(map[string]any{ + "positions": []uint64{uint64(8), uint64(6), uint64(4)}, + "last_pos": uint64(2), + }) + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) + }, + check: func(t *testing.T, ctx *hintTestContext) { + allVarValueInScopeEquals(map[string]any{ + "last_pos": uint64(5), "positions": []uint64{uint64(8), uint64(6)}, + })(t, ctx) + + varValueEquals("next_item_index", feltInt64(2))(t, ctx) + }, + }, + { + operanders: []*hintOperander{ + {Name: "next_item_index", Kind: uninitialized}, + }, + ctxInit: func(ctx *hinter.HintRunnerContext) { + ctx.ScopeManager.EnterScope(map[string]any{ + "positions": []uint64{uint64(90), uint64(80), uint64(70), uint64(60), uint64(50)}, + "last_pos": uint64(0), + }) + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) + }, + check: func(t *testing.T, ctx *hintTestContext) { + allVarValueInScopeEquals(map[string]any{ + "last_pos": uint64(51), "positions": []uint64{uint64(90), uint64(80), uint64(70), uint64(60)}, + })(t, ctx) + + varValueEquals("next_item_index", feltInt64(50))(t, ctx) + }, + }, + { + operanders: []*hintOperander{ + {Name: "next_item_index", Kind: uninitialized}, + }, + ctxInit: func(ctx *hinter.HintRunnerContext) { + ctx.ScopeManager.EnterScope(map[string]any{ + "positions": []uint64{uint64(87), uint64(51), uint64(43), uint64(37)}, + "last_pos": uint64(29), + }) + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityBodyHint(ctx.operanders["next_item_index"]) + }, + check: func(t *testing.T, ctx *hintTestContext) { + allVarValueInScopeEquals(map[string]any{ + "last_pos": uint64(38), "positions": []uint64{uint64(87), uint64(51), uint64(43)}, + })(t, ctx) + + varValueEquals("next_item_index", feltInt64(8))(t, ctx) + }, + }, + }, + "UsortVerifyMultiplicityAssert": { + { + ctxInit: func(ctx *hinter.HintRunnerContext) { + err := ctx.ScopeManager.AssignVariable("positions", []uint64{1}) + if err != nil { + t.Fatal(err) + } + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityAssertHint() + }, + errCheck: errorTextContains("assertion `len(positions) == 0` failed"), + }, + { + ctxInit: func(ctx *hinter.HintRunnerContext) { + err := ctx.ScopeManager.AssignVariable("positions", []uint64{}) + if err != nil { + t.Fatal(err) + } + }, + makeHinter: func(ctx *hintTestContext) hinter.Hinter { + return newUsortVerifyMultiplicityAssertHint() + }, + errCheck: errorIsNil, + }, + }, }) } diff --git a/pkg/hintrunner/zero/zerohint_utils_test.go b/pkg/hintrunner/zero/zerohint_utils_test.go index a0cb41468..d72420ad6 100644 --- a/pkg/hintrunner/zero/zerohint_utils_test.go +++ b/pkg/hintrunner/zero/zerohint_utils_test.go @@ -207,6 +207,14 @@ func varValueInScopeEquals(varName string, expected any) func(t *testing.T, ctx t.Fatalf("%s scope value mismatch:\nhave: %v\nwant: %v", varName, value, expected) } } + case []uint64: + { + valueArray := value.([]uint64) + expectedArray := expected.([]uint64) + if !reflect.DeepEqual(valueArray, expectedArray) { + t.Fatalf("%s scope value mismatch:\nhave: %v\nwant: %v", varName, value, expected) + } + } case map[fp.Element][]fp.Element: { valueMapping := value.(map[fp.Element][]fp.Element)