EIP-5267: Retrieval of EIP-712 domain

Currently, in order to instantiate a web3 Contract object, libraries like Ethers require the user to submit a minimum of the contract address and the contract ABI (in either human-readable string form or JSON). The ABI is output by Solidity. It would be very annoying if the user had to go in and manually edit the ABI file to manually change these parameter names every time a new version of the contract is built and deployed, from the versions that were built (e.g. _name and _version) to the versions that the user wants to use in the JS API (e.g. name and version).

The creator of Ethers is considering pulling the ABI directly from Etherscan, so that to instantiate a Contract object, you would only have to specify the address of an Etherscan-verified contract. At that point, whatever is built by Solidity, and deployed, is what will be automatically exposed in Javascript.

So yes, names exposed in Solidity do in fact matter in Javascript code.

An application or a library that instantiates a Contract object in order to consume its EIP-5267 interface would do so based on a user-provided address and a known ABI embedded in the application or library. The known ABI would have known names, they would not depend on the names that were present in the contract source code.

Take an ERC20 wallet as an example. When the user loads a new token address, the wallet will not fetch the ABI from an explorer, it will interact with it through a known ABI for ERC20 tokens. The same thing should happen in an application or library that interacts with EIP-5267 compliant contracts.

@frangio’s ERC lightning talk at Devcon in Bogota on EIP-5267 ERC Lighting Talks - YouTube

Hi @frangio this is important EIP, thank you for authoring.

Two questions from me:

  1. What is the rationale choosing bytes1 field instead of byte field or uint8 field?
  2. What is the rationale of putting a salt field in the domain? It seems not reasonable to get salt from remote, unless the understanding of salt is different, could you ellaborate?
  1. I think bytes1 and byte are synonyms. As for byte vs integer types, I went with byte because it doesn’t have integer semantics.
  2. The inclusion of salt in the domain is taken directly out of EIP-712. I think it should be seen as a salt to the domain separator, and shouldn’t be confused with a salt for the message being signed, which would be different.

I could not imagine how salt could be retrieved from the function state, are you referring to nonce instead?

In the current snapshot of reference implementation, you gave

pragma solidity 0.8.0;

contract EIP712VerifyingContract {
  function eip712Domain() external view returns (
      bytes1 fields,
      string memory name,
      string memory version,
      uint256 chainId,
      address verifyingContract,
      bytes32 salt,
      uint256[] memory extensions
  ) {
      return (
          hex"0d", // 01101
          "Example",
          "",
          block.chainid,
          address(this),
          bytes32(0),
          new uint256[](0)
      );
  }
}

which shows salt as 0.

Can you give more real example of how salt will be generated from contract function?

This EIP makes no statements about the semantics of salt. All that EIP-712 says is:

a disambiguating salt for the protocol. This can be used as a domain separator of last resort.

I see. Maybe just the name salt is confusing, but I can understand the 712’s using it as a last resort to prevent replay attack if chainId is not sufficient to disambiguate…

QQ: Clarify computation of domainSeparator, or provide an interface for implementors to decide

domainSeparator = hashStruct(eip712Domain)

to make it more convenient for client to directly use.

This is because with the field uint256[] memory extensions, I assume there is two ways. to interpret the potential computation of
domainSeparator:

  • {fields, name, version, chainId, contractAddress,salt} // Original EIP-712
  • {fields, name, version, chainId, contractAddress,salt, extensions} // EIP-712 plus extensions introduced in EIP-5267

If extensions are present they must be included in the domain (but the field would not be an extensions field, it would be other fields defined by the extensions). If this is not clear I can add an example.

Got it. thank you @frangio . I am in strong support of this EIP-5267. Thanks for this proposal.

Is there any plan soon for OZ to expose what the methods are exposed in EIP-5267?

@frangio @wighawag , inspired by your discussion about event vs function, given the lesson we learn in ERC721Metadata, it would be nice for an event could be used to notify when an update occurs to of EIP-712Domain

Can you share some reference to what you mean?

In general, it may not be possible to emit an event when the domain changes. For example, if the chain forks with a new chain id the EIP-712 domain immediately changes and there is no opportunity to trigger that event, unless triggered manually after the fact. That said, it could still be useful to have an optional event.

Can you share some reference to what you mean?

ERC721Metadata is a good interface, a little pity that it didn’t define a way to trigger update, hence EIP-4906 is drafted and serve as a complement to it.

In general, it may not be possible to emit an event when the domain changes. For example, if the chain forks with a new chain id the EIP-712 domain immediately changes and there is no opportunity to trigger that event, unless triggered manually after the fact.

Yes, that’s the case, it won’t emit an event. It will aways need to be executed TX in order to emit the event.

That said, it could still be useful to have an optional event.

Yes, exactly, this is what I mean. An optional event for implementor to have an option to notify callers who care about change of domain.

I’ve added an explanation of the use of the extensions array, and also added an optional event to emit when the domain changes. (EIP-5267: Clarify use of extensions and add event by frangio · Pull Request #6297 · ethereum/EIPs · GitHub)

I did some more research on backwards compatibility and put together a small app to show that it is possible to reconstruct the EIP-712 domain of contracts that don’t impement this EIP by guessing the domain.

https://eip5267.vercel.app

I added a minor comment about this in the EIP (I don’t want to go into detail about it in the spec) and requested the move into Last Call.

Congrats on moving to last call.

Cross posting some editorial questions hoping to get @frangio 's clarification I left on github PR

Discussion: can you share the rationale of extensions being format as uint256 instead of bytes32 ?

Extensions are described by their EIP numbers because EIP-712 states: “Future extensions to this standard can add new fields […] new fields should be proposed through the EIP process.”

Here EIP-712 didn’t specify what format of fields will be, so it’s open to future designer to design, EIP-5267 is one of such future design.

Q1. Could author share your thoughts into why choose the format of extensions an uint256[] vs bytes32[] or generally bytes? This is one of the problem that EIP-5750 is trying to address and thus when EIP-5267 made a design choice that’s different, I’d love to learn the rationale and understand it better. When using uint256[] and described in EIP-5267 as it needs to be a EIP number, does it mean only EIP number will be allowed in the extension as a way to provide extending information? Will EIP number be sufficient? Do you anticipate some of these elements could be re-purposed in the future?

Q2. In the reference implementation,

    return (
          hex"0d", // 01101
          "Example",
          "",
          block.chainid,
          address(this),
          bytes32(0),
          new uint256[](0)
      );

and also in javascript

  if (extensions.length > 0) {
    throw Error("Extensions not implemented");
  }

Is it intentional that in author’s perspective the extension shall at least have one element?

The extensions array has a specific meaning. It’s an array of EIP numbers. There is no reason to encode this as bytes32[] or bytes.

Yes. This follows the EIP-712 specification:

Future extensions to this standard can add new fields with new user-agent behaviour constraints. User-agents are free to use the provided information to inform/warn users or refuse signing. Dapp implementers should not add private fields, new fields should be proposed through the EIP process.

This means that applications that want to use new fields should do so by creating an EIP. The resulting EIP number would go in the extensions array.

Please also see the new code under Reference Implementation showing what supporting an extension EIP would look like.

No, there are no constraints on the number of elements. The reference Solidity implementation returns an empty array, and this is expected to be the most common value.

When EIP-712 says “new fields should be proposed through the EIP process.” this means there will be exceptions. This means, when you choose “EIP numbers” as the sole possible value of extensions

  • it rule out all exceptions that (1) compliant EIP-712 implementations may choose to ignore the “should” wording soft spec of EIP-712 and introduce behavior at their sole discretion without proposing an EIP or (2) the EIP process stops functioning like the YellowPaper or (3) EIP-number stops being a number but some chars like softly proposed by @Pandapip1 and a few others in the debate of how EIP numbers shall be assigned and {revoked, reassigned, corrected}.
  • it also rule out the case when the EIP number is the only granularity. For example, when used with complicated EIP (EIP-4337), it couldn’t specify a sub interface or sub behavior in that EIP or other more complicated EIPs. For example, in a cross-chain validation scenario (EIP-5164 may lead to), EIP or implementer may want to specify a chain-pair in some case and EIP-5267 will be too restrictive (see footnote #1).

If this is indeed the intention of EIP-5267, sure this can be a design choice author makes. But I like to flag this as explicit and suggest we spell out rationales leading to these decisions.


Footnote #1: An counter example is that EIP-1271 doesn’t have an extension field, therefore we end-up needing EIP like EIP-6066 to specify a new method and all developers need to re-establish a consensus to follow or not follow that new function.

function isValidSignature(uint256 tokenId, bytes32 hash, bytes calldata data)

Follow up the debate of EIP-1271 lacks a tokenId or general extraData field in the EIP-5750

I reached out to the author of eip712-codegen @danfinlay to consider adding this uint256 extensions field which it currently don’t have. [Early feedback needed] Propose to adding extension fields by xinbenlv · Pull Request #8 · danfinlay/eip712-codegen · GitHub so that all the compliant contracts of EIP-5267 could be supported by eip-712-codegen.

Note: this is en example why I feel we need EIP-5750 to help people come to a consistent way to represent extension.

Or maybe what really needs to be considered is the represent the extensibility of EIP-712, which you wrote

Additionally, the type of the EIP712Domain struct needs to be extended with the subdomain field. This is left out of scope of this reference implementation.

so that subdomains could be included.

From my comment in eip712-codegen#8:

I would recommend ignoring extensions for now. They are specified in the EIP for future proofing but so far no extensions have been defined.

1 Like