Improves/Fixes judge prompt; testing for templates#100
Conversation
|
|
||
| **Ground B — the prover cannot discharge it.** | ||
|
|
||
| The claim is that CVL expresses the property fine, but the *prover* cannot prove it in practice: timeouts, non-linear arithmetic, path explosion, non-termination, unknown error in the prover. |
There was a problem hiding this comment.
this gives an easy way out for the author agent out of Ground A type skips: it may say that it cannot modify a summary (e.g. NONDET) because otherwise the run would time out.
I am not saying to change the prompts more but it points to further work we'll need to do
|
|
||
| #### The unbounded-array limitation (a specific Ground A) | ||
|
|
||
| The prover cannot universally quantify over dynamic-length arrays, so a property that must reason about *all* elements of an arbitrary-length array with no feasible size bound is a valid Ground-A skip. Two scoping caveats: |
There was a problem hiding this comment.
something something grounding? which types of dynamic-length arrays? memory or storage?
worth clarifying.
I suppose it's important enough that you put it in the main prompt
| @@ -0,0 +1,92 @@ | |||
| { | |||
There was a problem hiding this comment.
I appreciate the indexing of all prompts actually
There was a problem hiding this comment.
It's not all prompts, just those that use our typed interface but yeah. Ideally we'd have this used for every template
ericeil
left a comment
There was a problem hiding this comment.
This is very, very cool! I would love to see more documentation of how the tests work (which I guess is the core of this change), and a CI test to verify that the manifest is up to date.
Also, as mentioned in Slack, we should consider whether we could re-use the template engine I'm planning to use for the Rust code, which might obviate the need for this (though of course it would come with its own problems).
| # -------------------------------------------------------------------------- | ||
| # STUBS -- fill these in for your repo | ||
| # -------------------------------------------------------------------------- |
There was a problem hiding this comment.
Haha this definitely isn't copy pasted from a stub I asked Claude to write bad_poker_face.gif
There was a problem hiding this comment.
So I guess the idea is to run this any time we make a change that would affect the manifest? Can we have a CI workflow that checks that the manifest is up-to-date?
There was a problem hiding this comment.
It exists brother. test_manifest or something
| ) | ||
|
|
||
| @dataclass | ||
| class PartialTemplate[T: Mapping[str, Any], U: Mapping[str, Any]]: |
There was a problem hiding this comment.
Please add some comments about the difference between TypedTemplate and PartialTemplate, when to use each, etc.
There was a problem hiding this comment.
These changes should probably be in a separate PR (and maybe reviewed by folks more familiar with the problem this addresses).
There was a problem hiding this comment.
This is hard for me to follow. Can you add some documentation describing how this works (and what a "cursed patcher" is? 😄 )
| del sys.modules[name] | ||
| sys.modules.update(saved) | ||
|
|
||
| def test_template_params_recoverable(): |
There was a problem hiding this comment.
This really needs some descriptive comments also
There were three problems with the judge template.
This is the second time that woopsies in our prompts has bitten us. I was tired of it. So I went a little crazy.
The other part of the PR
The Problem
We have a typing mechanism for the templates (
TypedTemplate) and its cousinInjectedTemplate.InjectedTemplatewas used to split the responsibility of who injected what template params, but it sacrificed type safety. We have yeeted it in favor of the stronger typedPartialTemplate.However, we still have a problem; nothing actually ensures the parameters we pass into the template actually correspond to the values used to render the template, nor their shape. Turning on strict mode helps, but it doesn't provide that big assurance.
The Solution: Part 1
We now have a script
composer/scripts/template_manifest.pywhich crawls our source to find all uses ofTypedTemplateandPartialTemplate. This is done through a very shallow syntax check, but we have programmed in assurance around that.This manifest is a static artifact in the repo; we have a test now that verifies recomputing from source matches that file. Further, we have another test that actually walks the whole module root and imports every single module (with some exceptions). During this walk we instrument the
TypedTemplateandPartialTemplateconstructors to record every instance they create; if we find that the templates named in the manifest don't cover all of the instances created during import, we fail the test. [2]Finally, we have another sanity test that all of the templates named in the manifest, once resolved, can have their template parameters extracted via reflection.
Why do we need all this? Well...
The Solution: Part 2
We can use this manifest and the runtime recoverable template parameter types to fuzz the templates. This is found in
test_fuzzed_templates.pyThe test loads the manifest and then, for each template, fuzzes the template. We resolve the type parameters, and use "hypothesis" to construct correctly shaped
dictinstances for the declared template type. We then render the template under strict rendering, which throws an exception if we reference an undefined name/field/attribute/key/whatever.However, there were several things we had to work around which makes the resulting test file look like hell on earth.
One is that "hypothesis" does not understand pydantic's
Field(pattern=....)so we got bogged down in it generating tons of solidity identifiers that did not match the regex. That is worked around by hooking in (via monkey patching, sigh) to the internal_from_typefunction used by hypothesis. There is no better way (that I could find) to intercept all value generation calls that didn't require us cluttering up the actual main code.A similar issue occurred with
Field(default_factory=list); something about this confused "hypothesis" which made it pass some pydantic internal sentinel value something or other to the model validator, which complained about not receiving a list. Accordingly, we simply strip all metadata from pydantic fields when generating those types.Another "fun" issue was in the constraint that every
Application,SourceApplicationetc. is expected to have at least oneExplicitContract-typed element in components. hypothesis kept generating examples where this list was empty, or the elements were allExternalActorsubtypes. There was no way to express (AFAICT) a generic rule like this, so we fallback on some pretty gnarly introspection to try to figure out what types belong incomponentsand that there is at least one (correct) subtype ofExplicitContract. We do a similar trick for ContractComponent.One final bit of grossness: we would run into issues where
sortand the "shape" ofcontextwhen calling theapplication_contexttemplates wouldn't be in sync. It's an implicit assumption that whensort == update, the ContractInstances all contain subtypes ofFromSourceContract. But these (common) template parameters live alongside other, template specific parameters. The solution is to add a@component_contextdecorator which is applied to any template param type which expects to useapplication_context_new. When loading a type with this decorator, we introspect the type of thesortfield to find out its possible values, and then vary the types we generate depending on the result.This all composes nicely and recursively, but we have to reach pretty deep into the hypothesis internals to get there.
The good news is that this trouble did in fact catch some sloppiness in our templates, including where we were conditioning on
sortinapplication_context_new.j2when that wasn't even bound. We've also now changed loosey-goosey "truthy" checks to explicitis defined.[1] Examples of BS reasons: "CVL can't support hooks on struct fields packed within the same slot" (obviously false), or "You can't do this when you use a NONDET summary for this function" (when the author claiming the skip CHOSE to write nondet summary; i.e., they tied their own hands and then claimed "can't do it, my hands are tied")
[2] This obviously doesn't catch cases where
TypedTemplateinstances are created dynamically within some function call, but there are limits to even my powers.