Skip to content

Commit 0140b8b

Browse files
ivokubCopilotyelhousni
authored
feat: allow replacing hints in test engine (#1737)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Youssef El Housni <youssef.elhousni@consensys.net>
1 parent 6fe2483 commit 0140b8b

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

test/engine.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type engine struct {
4545
blueprints []constraint.Blueprint
4646
internalVariables []*big.Int
4747
noSmallFieldCompatibility bool
48+
hintMapping map[solver.HintID]solver.Hint
4849
}
4950

5051
// TestEngineOption defines an option for the test engine.
@@ -75,6 +76,20 @@ func WithNoSmallFieldCompatibility() TestEngineOption {
7576
}
7677
}
7778

79+
// WithReplacementHint allows to replace a hint function in the test engine with
80+
// a custom one, for a given hint ID. This is useful for testing edge cases,
81+
// such as when the hint function returns incorrect values.
82+
func WithReplacementHint(id solver.HintID, f solver.Hint) TestEngineOption {
83+
return func(e *engine) error {
84+
if e.hintMapping == nil {
85+
e.hintMapping = make(map[solver.HintID]solver.Hint)
86+
}
87+
// Later calls override earlier ones for the same hint ID, matching solver.OverrideHint behavior.
88+
e.hintMapping[id] = f
89+
return nil
90+
}
91+
}
92+
7893
// IsSolved returns an error if the test execution engine failed to execute the given circuit
7994
// with provided witness as input.
8095
//
@@ -527,6 +542,12 @@ func (e *engine) NewHint(f solver.Hint, nbOutputs int, inputs ...frontend.Variab
527542
if nbOutputs <= 0 {
528543
return nil, fmt.Errorf("hint function must return at least one output")
529544
}
545+
hintFn := f
546+
if e.hintMapping != nil {
547+
if mappedFn, exists := e.hintMapping[solver.GetHintID(f)]; exists {
548+
hintFn = mappedFn
549+
}
550+
}
530551

531552
in := make([]*big.Int, len(inputs))
532553

@@ -538,7 +559,7 @@ func (e *engine) NewHint(f solver.Hint, nbOutputs int, inputs ...frontend.Variab
538559
res[i] = new(big.Int)
539560
}
540561

541-
err := f(e.Field(), in, res)
562+
err := hintFn(e.Field(), in, res)
542563

543564
if err != nil {
544565
panic("NewHint: " + err.Error())

test/engine_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,59 @@ const (
136136
divUncheckedConstConst
137137
)
138138

139+
// squareHint returns x^2 for the first input.
140+
func squareHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error {
141+
outputs[0].Mul(inputs[0], inputs[0])
142+
return nil
143+
}
144+
145+
// zeroHint always returns 0.
146+
func zeroHint(_ *big.Int, inputs []*big.Int, outputs []*big.Int) error {
147+
outputs[0].SetUint64(0)
148+
return nil
149+
}
150+
151+
type squareHintCircuit struct {
152+
X frontend.Variable
153+
XSquare frontend.Variable
154+
}
155+
156+
func (c *squareHintCircuit) Define(api frontend.API) error {
157+
res, err := api.Compiler().NewHint(squareHint, 1, c.X)
158+
if err != nil {
159+
return err
160+
}
161+
api.AssertIsEqual(res[0], c.XSquare)
162+
return nil
163+
}
164+
165+
func TestHintReplacement(t *testing.T) {
166+
field := ecc.BN254.ScalarField()
167+
168+
t.Run("without replacement", func(t *testing.T) {
169+
err := IsSolved(
170+
&squareHintCircuit{},
171+
&squareHintCircuit{X: 3, XSquare: 9},
172+
field,
173+
)
174+
if err != nil {
175+
t.Fatal(err)
176+
}
177+
})
178+
179+
t.Run("with replacement", func(t *testing.T) {
180+
err := IsSolved(
181+
&squareHintCircuit{},
182+
&squareHintCircuit{X: 3, XSquare: 0},
183+
field,
184+
WithReplacementHint(solver.GetHintID(squareHint), zeroHint),
185+
)
186+
if err != nil {
187+
t.Fatal(err)
188+
}
189+
})
190+
}
191+
139192
func TestDivUncheckedZeroPanicsInEngine(t *testing.T) {
140193
tests := []struct {
141194
name string

0 commit comments

Comments
 (0)