Skip to content

impl diff: resolve non-positional storage access beyond detection #370

Description

@spalen0

Follow-up to #367 / #368, from the review in #368 (comment).

Where #368 leaves this

#368 makes storage coverage decide the verdict: any proven conflict is INCOMPATIBLE, anything the check can't see is UNKNOWN, and only full coverage is COMPATIBLE. To get there without a compiler it detects non-positional storage access and resolves roots in a few bounded forms:

  • Scope is the inheritance chain plus library functions reachable by name. Receivers aren't resolved, so this over-approximates toward UNKNOWN.
  • Custom value types resolve by a declaration that is unique across the bundle. Ambiguous or missing declarations are gaps.
  • Roots resolve from literals, unique constants, one local or getter hop, and the keccak256, EIP-1967 and ERC-7201 expressions.
  • A namespace is proven unchanged only by an identical elementary struct plus a verified root.
  • Unannotated accessors, raw sload/sstore and delegatecall are reported as gaps, not analysed.

The outcome is correct, but it is conservative. On the 3Jane upgrade, USD3's positional layout is COMPATIBLE while the combined verdict is UNKNOWN. This issue tracks turning the gaps that can be closed into real coverage.

Work

  1. Follow the delegatecall target when it is a known verified contract. Yearn tokenized strategies delegatecall a shared TokenizedStrategy, which writes the proxy's storage at yearn.base.strategy.storage. Resolving that target and adding its code to the scope would turn the most common gap for Yearn strategies into checked coverage.
  2. Resolve the receivers of library calls instead of reaching by name. Today, using any function of SafeERC20 or Address can pull an unrelated functionDelegateCall into scope.
  3. Follow storage references passed into helpers. This covers storage-pointer parameters and using … for on storage types.
  4. Resolve custom value types with lexical scope and import aliases, rather than a bundle-unique name. That covers contract-scoped types (C.Id) and names that are declared twice but used in only one scope.
  5. Compare namespace struct layouts with compiler-derived data, so compatible changes such as an appended member can be accepted. Today only text-identical elementary structs are proven.
  6. Evaluate root expressions more broadly through general constant folding, instead of the fixed forms listed above.
  7. Detect overlapping namespace ranges, i.e. a struct extent crossing another root. Today only exact duplicate roots are detected.
  8. Use compiler ASTs when a matching build supplies one. Sourcify returns no AST for these contracts (0 of 35 sources for the new USD3). The monitoring cron must still never download or run a compiler.

Acceptance

  • Each item comes with a regression fixture that fails before its fix.
  • No compiler download or execution, and no new network dependency in unit tests.
  • Verdict precedence is unchanged. A proven conflict stays INCOMPATIBLE even when another region is unknown, and incomplete coverage never produces an unqualified COMPATIBLE.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pythonPull requests that update python code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions