Skip to content

Update ERC-7730: added intent mutability specification#1738

Open
PatrickAlphaC wants to merge 7 commits into
ethereum:masterfrom
PatrickAlphaC:add-intent-mutability
Open

Update ERC-7730: added intent mutability specification#1738
PatrickAlphaC wants to merge 7 commits into
ethereum:masterfrom
PatrickAlphaC:add-intent-mutability

Conversation

@PatrickAlphaC
Copy link
Copy Markdown

@PatrickAlphaC PatrickAlphaC commented May 11, 2026

Add intent-tracking preconditions for upgrade and state-mutability detection

Summary

Adds two optional precondition blocks under context.contractproxy and stateRefs — that bind an ERC-7730 descriptor to the on-chain state it was authored against, plus an optional metadata.intentMutability free-form string for vectors that can't be witnessed on-chain. Wallets verify each declared precondition against live state before applying the descriptor's formatting; on mismatch they downgrade to opaque signing or warn the user.

Discussion thread: https://ethereum-magicians.org/t/eip-7730-proposal-for-a-clear-signing-standard-format-for-wallets/20403

Motivation

A descriptor's intent and fields describe a function as it exists at authoring time. The executed behavior can diverge from the displayed intent in ways the descriptor's formatting alone cannot detect:

  • Implementation upgrade — a proxy's implementation slot is replaced; selector and parameters unchanged, executed bytecode entirely different.
  • State-dependent branch — a function reads a mutable storage variable and chooses between code paths. Flipping the variable changes which path runs without any bytecode change.
  • Admin-controlled parameter — a fee, pause, allowlist, or routing address is changed by the owner or governance, altering what the function does for a given input.
  • External dependency — the function calls a contract whose address is stored in mutable state, or relies on an oracle whose value or endpoint can be rotated.

None of these is detectable from the descriptor today. Wallets that clear-sign against a stale descriptor display an intent that the contract no longer honors. The intent-tracking preconditions make the on-chain vectors explicit and verifiable, and surface the off-chain vectors to auditors.

Design summary

The mechanism lives in three places, all OPTIONAL and additive:

context.contract.proxy — typed block for standardised upgradeable proxies:

  • type — one of eip-1967, eip-1822, eip-2535. Wallet dispatches on this to choose the verification primitive (fixed-slot read vs. facets() loupe call).
  • expectedImplementations — array of { chainId, address, selectors? } entries. Non-semantic upgrades can be accommodated by appending the new implementation address rather than republishing.
  • selectors is REQUIRED when type == "eip-2535" and MUST be absent otherwise. For diamonds, the wallet enumerates the live selector → facet map via the diamond loupe and verifies every entry matches a declared (address, selectors) pair — catching both facet additions and selector rerouting between audited facets.

context.contract.stateRefs — array of generic slot preconditions for admin parameters, state-dependent branches, custom (non-standard) proxy implementation slots, or any mutable on-chain state that bounds the displayed intent. Each StateRef has slot, expectedValue, description, optional mask (for packed slots), and optional chainId/address for cross-contract refs. The masked comparison is (liveValue & mask) == (expectedValue & mask); bits outside the mask are ignored on both sides. expectedValue is required, making the descriptor self-verifying.

metadata.intentMutability — free-form string documenting intent-mutability vectors that cannot be reduced to a slot read or a fixed interface call: time-based branches, block-environment dependencies, parameter-based hidden branches, dynamic external call resolution, off-chain oracles, deployment-time library linkage, composition. Wallets cannot verify these claims; the trust anchor for them is auditor attestation.

Wallet behavior. Wallets distinguish two failure modes: a structural context mismatch (wrong chain, wrong address, wrong factory) means the descriptor does not apply at all; a precondition mismatch (proxy or stateRefs) means the descriptor is the right one for this contract but the contract's state has drifted from the audited baseline — wallets downgrade to opaque signing or warn the user. Hardware wallets satisfy the verification requirement through their companion software.

Auditor obligation. An auditor reviewing a descriptor MUST verify (a) the proxy and stateRefs preconditions are exhaustive for vectors that can be witnessed on-chain, and (b) every vector that cannot be witnessed on-chain is disclosed in metadata.intentMutability. An attestation MUST NOT be issued while a known intent-mutability vector is undeclared.

Full design rationale (why under context rather than a top-level section, why a typed proxy block earns its keep over generic stateRefs, why diamond bindings are selector-aware, why mask exists, why expectedValue is required, why off-chain disclosure lives in metadata) is documented inline in the EIP's Rationale section.

What changed

ERCS/erc-7730.md

  • New Intent tracking section under Specification with examples for: immutable contract, EIP-1967 proxy with multiple audited implementations, EIP-2535 diamond with selector-to-facet routing, admin pause flag, mapping slot (pre-computed), packed slot using mask, proxy combined with admin state, off-chain disclosure in metadata.
  • New Intent tracking subsection under Rationale covering the design choices above.
  • New Intent mutability subsection under Security Considerations enumerating vectors the on-chain preconditions catch and the broader set they cannot (time, block environment, hidden parameters, dynamic external calls, cross-facet routing within an allow-list, off-chain dependencies, composition), with normative auditor obligations.
  • Proxy support updated to route authors at context.contract.proxy with the right type.
  • Simple example uses WETH (mainnet + OP Stack predeploy) with no proxy or stateRefs declared, demonstrating an immutable-asserting descriptor.

assets/erc-7730/erc7730-v2.schema.json

  • New optional context.contract.proxy block (type enum + expectedImplementations array with optional selectors).
  • New optional context.contract.stateRefs array (StateRef with slot, expectedValue, description, optional mask, optional cross-contract chainId/address).
  • New optional metadata.intentMutability free-form string.

The change is additive. Existing descriptors continue to validate. Descriptors without any precondition declarations are asserting that no intent-mutability vectors apply to the bound contract; auditors verify that assertion.

Out of scope

  • The on-chain attestation schema.
  • Predicate/range expressions over stateRef.expectedValue (e.g., "value within bound", "monotonic counter"). Could be added later as a non-breaking extension; deferred until concrete demand surfaces.
  • Stricter if/then/else schema enforcement of the selectors-required-iff-eip-2535 rule (currently documented prose-style and left to wallet/auditor enforcement).

Reviewer ask

Particularly interested in feedback on:

  • Diamond binding strength. expectedImplementations for type: "eip-2535" is selector-aware so cross-facet rerouting within the allow-list is caught. Is the selector grain correct, or should it be facet-set-only with rerouting flagged as an auditor responsibility?
  • OR-set semantics on stateRefs. proxy.expectedImplementations allows multiple acceptable values (for non-semantic upgrades). Should stateRefs.expectedValue similarly accept an array of acceptable values (for parameters with a known-safe band, e.g., a fee toggling between two audited rates)? Or keep it strictly point-valued and require republication?
  • metadata.intentMutability as a documentation field. It lives in metadata (not context) precisely because wallets cannot verify it — only auditors can. Is the auditor-obligation framing in Security Considerations strong enough, or should we move further toward requiring it to carry a structured attestation reference?
  • Schema-side enforcement of the diamond selector rule. Currently selectors is optional in the schema and the "required for eip-2535, forbidden otherwise" rule is prose. Worth a conditional if/then/else schema clause, or fine as prose?

@eip-review-bot
Copy link
Copy Markdown
Collaborator

eip-review-bot commented May 11, 2026

File ERCS/erc-7730.md

Requires 1 more reviewers from @arein, @arikg, @fredrik0x, @kuzdogan, @lcastillo-ledger, @llbartekll, @paoun-ledger

@PatrickAlphaC PatrickAlphaC changed the title feat: added intent mutability specification feat: added intent mutability specification for 7730 May 11, 2026
@eip-review-bot eip-review-bot changed the title feat: added intent mutability specification for 7730 Update ERC-7730: added intent mutability specification May 11, 2026
@github-actions github-actions Bot added the w-ci label May 11, 2026
Copy link
Copy Markdown
Member

@kuzdogan kuzdogan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very much needed and a very good catch, thank you. Just not approving yet to get some more eyes on and avoid auto-merge.

Copy link
Copy Markdown
Contributor

@llbartekll llbartekll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for flagging that Patrick. One comment:

StateRef is a full 32-byte slot comparison, but contracts pack multiple vars into one slot, no?

what about optional mask on StateRef? wallet checks (slot_value & mask) == (expectedValue & mask).

{
  "slot": "0x0",
  "mask":          "0x0000000000000000000000ff0000000000000000000000000000000000000000",
  "expectedValue": "0x0",
  "description": "varX must be false (byte 20 of slot 0)"
}

@PatrickAlphaC
Copy link
Copy Markdown
Author

@llbartekll oo. This is a great call!! Yes, I'll add this! Please keep feedback coming.

@PatrickAlphaC
Copy link
Copy Markdown
Author

Ok... I spoke with Laurent from Ledger, and he brought up some good points. He liked the idea of allowing 7730 files to be attached to proxy addresses (not just immutable implementations), with wallets checking proxy slots for changes, and note this would also cleanly support diamond proxies.
But disliked the name intentMutability and proposed a simpler structure under context.contract.proxy with a type field (UUPS, diamond, or custom) and a slots array used only for the custom case, since the EIP standards already define how to locate implementation addresses for known proxy types.

  • A separate preconditions or intentMutability field is redundant — the context section already expresses what conditions the contract must meet for the 7730 file to apply.
  • expectedImplementations should become an array of {address, chainId} entries (mirroring the deployments subkey shape), so upgrades that don't change the intent's shape can be handled by simply appending the new implementation address.
  • The spec has deliberately kept non-normative fields out of the JSON in favor of simple identifiers, and this proposal stays consistent with that.

I think I like some of these comments... Going to update accordingly. I think we still need a generic stateRef, but it also makes me think the threat vector of changing intents is bigger than I thought. You could hide logic in:

  • A timestamp (function does X until a timestamp, then does Y)
  • A function selector
  • Some parameter in the input
  • etc

So there is a lot of emphasis on the auditor looking for ways intent can be changed.

…ty to a metadata object, and setup intent tracking in the contract.context area
@github-actions github-actions Bot added the w-ci label May 12, 2026
@PatrickAlphaC
Copy link
Copy Markdown
Author

PatrickAlphaC commented May 12, 2026

Ok, my most important question (I think?) for this group now.

If an auditor finds a way for the intent to be changed that is not covered in this spec (time-bound, block-bound, oracle-bound, etc) what should a wallet do/how should we handle?

Should a wallet:

  1. Just not display signing intent of that protocol
  2. Display it with a warning that says "there is some fishy shit here"
  3. ???

I think we should have the intentMutability metadata be expanded to include what wallets should do.

  "metadata": {
    "intentMutability": [
      {
        "severity": "warn",
        "description": "Swap rate fetched from off-chain oracle at https://oracle.example.com. Owner 
  can rotate the endpoint without any on-chain state change; the displayed `value` field may differ 
  from execution."
      }
    ]
  }

Then, all "clear signing" should come with a warning? But maybe this should be in the display section?

@github-actions
Copy link
Copy Markdown

The commit 4ac1d2b (as a parent of 76cc3fe) contains errors.
Please inspect the Run Summary for details.

@github-actions github-actions Bot removed the w-ci label May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants