Skip to content

Update ERC-7730: added intent mutability specification#1738

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

Update ERC-7730: added intent mutability specification#1738
PatrickAlphaC wants to merge 11 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. 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. Functions whose displayed intent depends on factors that cannot be expressed via these mechanisms MUST be omitted from display.formats; wallets fall back to opaque signing for omitted selectors.

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.

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.

Design summary

Two OPTIONAL, additive precondition blocks live under context.contract:

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

  • type — one of eip1967, eip1822, eip2535. 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 == "eip2535" 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.

Vectors not expressible via these mechanisms. Functions whose displayed intent depends on block.timestamp thresholds, block-environment values, parameter-based hidden branches, dynamic external call resolution, off-chain dependencies (oracle endpoint rotation, off-chain signing services, deployment-time library linkage), or compositional wrapping (multicall, account-abstraction batches, hooks) cannot be witnessed by this version's preconditions. Such functions MUST be omitted from display.formats; wallets fall back to opaque signing for omitted selectors. A descriptor that formats a function with intent-mutability vectors not expressible via proxy or stateRefs is malformed and MUST NOT be attested.

The auditor's filter is intent-depending, not contract-using. The test is not "does this contract use an oracle/timestamp/external call" but "does the rendered intent or any formatted fields for the function depend on a value influenced by that vector?" A function whose displayed intent is wholly parameterized by user inputs (amount, recipient, minOut, deadline) is not intent-mutable in a way the descriptor needs to express, even if the contract internally consults an oracle. A function that renders an oracle-sourced value into its displayed intent is.

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 its 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.

EIP-7702 delegated accounts. When an EOA signs a transaction targeting another contract, its delegation is off the execution path and the targeted-contract descriptor applies normally. When an EOA signs a transaction targeting itself (smart-wallet-style), the wallet resolves the descriptor for the EOA's current delegate contract by reading the delegation indicator. Because EOA owners can re-delegate at any time, no registry can enforce delegate identity globally; wallets surface delegation status to the user.

Full design rationale (why under context rather than a top-level section, why a typed proxy block earns its keep, why diamond bindings are selector-aware, why mask exists, why expectedValue is required, why no descriptor-side off-chain disclosure) is documented inline in the Rationale section.

What changed

ERCS/erc-7730.md

  • New Intent tracking section under Specification with examples: 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, function omission for unexpressible mutability.
  • New Intent tracking Rationale subsection covering the design choices above.
  • New Intent mutability Security Considerations subsection enumerating what V2 catches and what it does not, with the "intent-depending" auditor filter and the normative omission rule.
  • New EIP-7702 delegated accounts Security Considerations subsection.
  • Proxy support updated to route authors at context.contract.proxy with the right type.
  • Simple example uses WETH (mainnet + OP Stack predeploy).

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).

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

Out of scope

  • On-chain attestation schema.
  • Per-function preconditions (different stateRefs per function). Today, per-function mutability is handled by omitting the affected function from display.formats.
  • Predicate / range / OR-set expressions over stateRef.expectedValue.
  • Descriptor-side disclosure of off-chain mutability. Author-supplied prose in a security-critical descriptor is rejected as a phishing surface; the omission rule replaces it.
  • Module enumeration for ERC-6900 / ERC-7579 account abstraction wallets.
  • Stricter if/then/else schema enforcement of the selectors-required-iff-eip2535 rule (currently documented prose-style).

These items are tracked for a future version of the specification.

Reviewer ask

Particularly interested in feedback on:

  • Diamond binding strength. expectedImplementations for type: "eip2535" 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?
  • Strictness of the omission rule. A descriptor that formats a function with unexpressible mutability is malformed and cannot be attested; the only remedy is to omit the function and let wallets fall back to opaque signing. Is this the right pressure on protocol designs and registries, or too restrictive given current DeFi shapes?
  • The intent-depending filter for off-chain factors. Most oracle-using protocols pass the filter (user-bounded intent), but the line between "renders an oracle-sourced value" and "internally uses an oracle" needs to be unambiguous for auditors. Is the spec text on this clear enough?
  • EIP-7702 handling. No schema support; treated entirely as a wallet UX concern with a Security Considerations note. Is that the right scope for this version, or should descriptors be able to declare expected delegates for EOA bindings?

@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
@llbartekll
Copy link
Copy Markdown
Contributor

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

@PatrickAlphaC intentMutability here is part of the descriptor, so let's say auditorA attests via 8176, later auditorB requires to add the description you proposed in order to audit. That would require to update the descriptor and would invalidate auditA(descriptor's hash has changed). What if severity/description is part of the attestation instead of the 7730 descriptor?

In your design we technically require protocol developer to self-audit which is not bad too.

so maybe descriptors declares the fact of mutability and attestation adds interpretation/correctness, what do you think?

@PatrickAlphaC
Copy link
Copy Markdown
Author

Ok, based on our conversations, we are now going with a more strict implementation for V2, and I've created a ticket in the registry to track any potential V3 changes (specifically for intent mutability tracking): ethereum/clear-signing-erc7730-registry#2558

To respond to @llbartekll, yes, the author would have to get a new attestation. So we've removed that "here are some notes" from the spec as well. And yes, the descriptors must include proxy/stateRef intent mutability. If a function includes another way to have their intent mutate, then it should be omitted from the spec (as of V2 of the schema).

@PatrickAlphaC
Copy link
Copy Markdown
Author

This PR is very verbose, happy to trim it down where needed.

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