@hiddentao Thanks for looking at the new standard!
I have thought of using batch events like that. I like it. That would be more gas efficient. But it would make selectors less visible and accessible and a little more difficult for indexers to processes.
With the per-selector events, the selector is indexed, which makes it possible to easily gather statistics about or search for specific functions on a blockchain. To my knowledge, indexers can process the per-selector events more easily (but not sure how much that really matters).
I think it would be much harder to get all the facet addresses for the selectors if only an array of selectors was returned. Off-chain code would have to query facetAddress(selector) in a diamond for every selector.
I do not see in the motivation why an on-chain contract needs to inspect a Diamond contract. That seems like an implementation detail.
I DO understand why an off-chain participant would want to know about a contract changing its implementation. Because that implementation detail results in you losing all your assets or not.
I like that you remove facetFunctionSelectors and facetAddresses. They were redundant.
I wonder if functionFacetPairs can be optional. The events like DiamondFunctionAdded may provide sufficient documentation for an explorer or a wallet. I don’t think a list is important to have on-chain, and poses an out-of-gas risk like all methods returning an unbounded array. I think such functionality is only useful off-chain. Removing it would reduce the cost of configuration considerably.
The standard function upgradeDiamond does too much. Post-7702, batching should be done by the caller. So, I would prefer a design where functions do one thing only, instead of everything. For example:
function configure(bytes4[] selectors, address facet)
If they want to remove, they pass address(0) as facet; this simplifies adding, replacing, and removing into a single action. If they want a _functionCall, they can (1) install one, (2) call into it, and (3) remove it. If they want to tag a release, they can have install a facet to emit _tag and _metadata.
I have also wondered if functionFacetPairs() could be optional. It is a question of reliability. Will the events approach always be available and feasible and practical in every senario, on every EVM, throughout time?
Well I suppose it can make sense to let people use their own judgement if their particular use case warrants using functionFacetPairs() or not. So perhaps it should be optional.
functionFacetPairs requires an array of bytes4be stored in a diamond. Eight function selectors can be stored in a single storage slot. So that is the cost.
I like the flexibility of configure and the idea of using EIP-7702 for batch transactions.
I think that some people/tools would prefer using upgradeDiamond and some would prefer using configure.
There can be more than one standard for diamond upgrade functions. Maybe I should take upgradeDiamond out of ERC-8109 and put it in a new standard, since it is optional anyway. If I did that, would you be interested at all in creating a new ERC standard for configure ?
I agree with you. The functionFacetPairs() function is meant for off-chain calls. It is meant to be used by block explorers, and other off-chain tools to show what is in a diamond.
@mudgen Single function facets and their function dispatcher - neither discussed in ERC 8109, nor in 2535.
I assume one can omit solidity’s dispatcher for single function facets - and just to use the rest of the calldata.
E.g. this facet bytecode - 0x60125f5260205ff3 - it just returns 18 and can be routed through 0x313ce567 , which means decimals() . The facet itself does not check the function selector as it is single function facet only. One can assume the function dispatcher is on the diamond’s proxy level.
Or are there any expectations about “misconfiguration” - e.g. in the case the facet is routed via 0x01020304 selector in the diamond’s entrypoint ? And that such misconfigurations shall be handled by facets returning some error?
Can you please confirm what you are saying and its reasoning?
Are you saying the case where a facet only has a fallback() function? So that when decimals() is called on a diamond it calls into the facet with the fallback() which executes?
If so, why would that be done? What is the reasoning to do that?
I do not understand the following part. Can you please explain?
I am exploring the idea that facets do not need to be written in Solidity, nor do they need to be full ‘contracts’ in the traditional sense. They can be raw highly specialised EVM bytecode snippets designed for a single purpose.
note: yes, fallback() in the context of Solidity
In a Diamond, the Proxy already does the heavy lifting of routing selectors. If I deploy a facet specifically for decimals(), I can use raw bytecode like 0x60125f5260205ff3 (which just returns 18). This saves gas because it eliminates the Solidity ‘Function Dispatcher’ (the msg.sig check) inside the facet.
For a high-frequency function like transfer(address,uint256), a dispatcherless Yul facet would immediately grab calldataload(4) and calldataload(36). It completely ignores the first 4 bytes of the call because it assumes the Proxy wouldn’t have routed the call there unless the selector was correct.
My question about ‘misconfiguration’ is whether you think the Diamond Standard should enforce that facets remain ‘self-aware’ of their selectors. If I use this optimized version, and the Diamond owner accidentally points the approve(address,uint) selector to transfer facet, the facet will execute a transfer instead of approval.
Is this ‘blind trust’ in the Diamond’s mapping acceptable in your view of the architecture?"
Or does ERC-2535 or ERC-8109 require that facets ‘defend’ themselves against being called with the wrong selector? Or is it valid to assume the Proxy’s mapping is the final authority, allowing us to strip the dispatcher out of the facets for better gas efficiency?
About ‘misconfiguration’: The standard does not specify that facets must remain “self-aware” of their selectors.
Or does ERC-2535 or ERC-8109 require that facets ‘defend’ themselves against being called with the wrong selector?
The standard does not require this. The main purpose of the standard is to specify the loupe functions and events for tooling interoperability. How diamonds are implemented is left to implementers. The specific implementation requirements are in the Implementation Requirements section.
Is this ‘blind trust’ in the Diamond’s mapping acceptable in your view of the architecture?"
Personally, in my opinion, it can be acceptable. I have in my mind an implementation for something that does use a facet with a fallback function.
In a Diamond, the Proxy already does the heavy lifting of routing selectors. If I deploy a facet specifically for decimals(), I can use raw bytecode like 0x60125f5260205ff3 (which just returns 18). This saves gas because it eliminates the Solidity ‘Function Dispatcher’ (the msg.sig check) inside the facet.
Have you done any gas testing on this to see how much gas you could save, or have any real idea? My understanding is that the runtime facet dispatch for a single function facet would require a small amount of gas, like 50 gas. But I haven’t specifically tested it.
Your motivation does not say that block explorers and other off-chain tools need to show what is in a diamond. Therefore, (as current) there is no justified motivating reason to do so.
If there is a specified motivation then the next question is: is this the best way to serve such clients?
This is the reason I am raising this topic. The implementation focuses on Diamond proxy; there are no requirements on facets.
With that decimals example: 148 gas (common approach) vs 31 gas (fallback with Yul assembly)
My point is that facet implementations will naturally iterate towards highly optimised shapes in the long term. Which is beneficial for the whole chain(s).
I am not saying the requirements on the facets must belong here into this particular ERC. But it should be raised somewhere if there are any expectations on the facet implementations.
@fulldecent Great points. Yes, the motivation should say that, I plan to add that. Yes the standard is the best way that I know of to serve such clients, but I am trying to get more feedback from such people to confirm that or get more data.
Hi, thanks for the proposal, overall it looks great. In particular, the separation of events for different types of facet changes is great, and it’s something we could potentially leverage in Blockscout for more accurate facet change detection.
I’d like to share some feedback from Blockscout’s perspective, specifically regarding how this proposal impacts detection and presentation of Diamond proxy contracts in the explorer.
Currently, Blockscout relies on the facetAddresses getter defined in EIP-2535. This getter returns a list of facet addresses, which aligns well with how our UI is structured. On the proxy contract details page, we don’t depend on selector-level information; instead, we primarily need the set of facet addresses to:
Display the list of facets belonging to a given Diamond proxy.
Allow users, in the “Read / Write Proxy” tab, to switch between facets and see the available methods based on the ABI of each verified facet.
In the current version of ERC-8109, the facetAddresses getter is removed in favour of functionFacetPairs, which returns (function selector, facet address) pairs. From Blockscout’s standpoint, this is a breaking change. While we can technically derive the list of facet addresses from `functionFacetPairs`, doing so would require additional processing and would force us to maintain separate detection and handling logic for EIP-2535 and EIP-8109 Diamonds.
This isn’t a blocker for us, but it does add complexity to Diamond detection and standard differentiation within the explorer.
Given that, I wanted to ask: is the removal of the facetAddresses getter a fundamental requirement for ERC-8109, or would it be acceptable to keep it alongside functionFacetPairs? From the discussion so far, functionFacetPairs clearly serves other use cases well, but retaining facetAddresses would help preserve backward compatibility and simplify integration for tools like Blockscout.
Thanks again for the proposal and for considering this feedback.
@vbaranov This is exactly the kind of feedback that is needed from people building or maintaining tooling. Thank you so much!
What you say here makes complete sense to me. I see that the facetAddresses() function is ideal for your application, as it probably is for others as well.
The reason the facetAddresses() function was removed is because its implementation in facets is too complicated. It makes it too hard for people to understand diamond implementations easily.
So unfortunately, yes, the facetAddresses() won’t be required in the ERC-8109 standard. That being said, ERC-8109 diamonds can still have the facetAddresses() function, but it can’t be relied on by tools to exist.
Also, in case it is helpful in some way: both ERC-2535 and ERC-8109 require the same facetAddress(selector) function.
Thank you for supporting ERC-2535 diamonds, and I really hope that you can support ERC-8109 diamonds despite this breaking change.
@fulldecent Thanks again for pointing out that the motivation section lacked motivation for showing what is in a diamond.
I updated the Motivation section of the standard giving motivation for showing what is in a diamond using the introspection functions and events. ERC-8109: Diamonds, Simplified