Thanks for the clarification.
In my opinion this is a good direction for ERC-8109.
While there are trade-offs, I think it improves consistency and transparency without adding much overhead, which should make diamonds safer and easier to work with — especially for newcomers.
Thanks @Vagabond for your feedback. Glad to know. It is helpful.
My thought now is to make the facet introspection function optional in the standard to make the standard flexible for multiple ways of organizing and managing facets and selectors.
This is my plans with ERC-8109 at this point:
What I will change:
- I am going to remove the upgrade function.
What I am NOT changing:
- I am NOT going to add a facet introspection function.
- I am not going to change the introspection functions back to the ERC-2535 introspection functions. Going to keep
functionFacetPairs().
My reasoning
ERC-8109 will serve as a base ERC standard for other ERC standards that can build on top of it.
ERC-8109 simplifies terminology and standardizes simplified introspection functions and better events. That’s it. I think a lot of tooling, such as block explorers and indexers can depend and use just these things, and don’t need anything else.
I decided to keep the new functionFacetPairs() introspection function because it can simplify some approaches to implementing diamonds.
New ERC standards can be built on top of ERC-8109 that standardize things related to deployment, upgrades, selector and facet management, and who knows what else.
I am planning to make a new ERC standard that builds on top of ERC-8109, called something like “Facet-Based Diamonds”, that deploys and upgrades diamonds using an introspection function defined in facets.
Another ERC standard (as suggested by @radek ) can be created, that builds on top of ERC-8109, that uses single-function facets and which uses some kind of on-chain package manager or templates or blueprints to deploy, upgrade and manage facets and selectors.
Other ERC standards, building on ERC-8109, can be created with other approaches and ideas.
ERC-8109 update:
- The
upgradeDiamondfunction has been removed from the standard. - The
error FunctionNotFound(bytes4 _selector);error has been added and is required when no facet is found for a selector and there is no fallback function or other handling mechanism for a call.
Hey, it’s a great proposition!
The big thing here, that i’m not sure, is the shift from selector based to facet based. The other things make sense, if this shift go foward.
My concern with this is the lost of flexibility that diamond provided. Reusing facets is now a bit less optimal or require all reusable facets to be really self contained without potential customization to do.
In the case of a facet with 4 selectors and i want to modify 1, i need to redeploy the original facet without the selector that need modifications and deploy a new single-selector facets. Before with selector based diamond , i would just need to deploy the single-selector facets and use the pre-deployed facets for the other 3 selectors.
@maxnorm, thanks for your feedback.
If you have a facet with 4 functions and you want to modify one, this is what you can do:
- Modify the source code of the original facet.
- Deploy the modified source code to create a new facet.
- Replace the old facet with the new facet.
I am working on an implementation that makes this very easy.
Fair point! Only deploying one contract with all the selectors with the one that modified make sense but the deployment with still cost more than only deploying a facet with the selector that need changes.
Imo, this approach still seem to break the on-chain reusability and the part to avoid redeploying already deployed logics blocks.
I like the idea. I agree that it’s simplifying a lot, but with a major downside from the original diamond proposition.
The lost in flexibility is my major concern with this approach. I’ll like to more of the implementation to have a better view on this.
Indeed, you are correct.
Yes, you are right.
Yes, you are right. There are upsides and downsides to this approach.
There are additional upsides to the facet-based approach:
-
It allows you to control the granularity of logic blocks. With the selector-based approach the granularity is functions. With the facet-based approach the granularity can be small facets, or large facets, or a mix between them, or it can be functions, by deploying each function as a separate facet.
-
In my past experience of people working on diamond projects, they tended to think and work at the facet level, and not at the function level. So I think working at the facet level is already what many people would tend to do.
-
It will be easier for tooling to work with facet-based diamonds because tooling won’t have to figure out (as much) which functions in facets are actually used in diamonds, since they all are (mostly). For example, currently, some block explorers may show all functions from all facets in a diamond, when displaying a diamond, even if the diamond doesn’t actually have some of the functions. This is of course wrong, but would be right if the diamond was facet-based.
-
Facet-based diamonds will match the verified source code of the facets used in the diamond. For example if 10 facets are added to the diamond and only some of the functions of each facet are added to the diamond, then the source code for each facets doesn’t match the functionality in the diamond since the diamond only has some of the functions. If the source of all the facets matches the functionality in the diamond, it is easier to read and understand the source code for the diamond.
By the way, ERC-8109 is not facet based, and I am no longer planning to make it facet based. ERC-8109 is a base ERC that standardizes terminology, events and introspection for tooling.
But I am planning to make a new standard, built on ERC-8109, that is facet based, adds an introspection function to facets and an optional upgradeDiamonds function.
The lost in flexibility is my major concern with this approach. I’ll like to more of the implementation to have a better view on this.
Great, I am currently working on a facet-based implementation.
Here is the information I have so far:
Pros for SetDiamondFacet(selector, facet)
- It is simpler because it is one event instead of three.
- Perhaps it is easier to process by tools because it is one event, and not three.
- It is more gas efficient than the
DiamondFunctionReplaced(selector, oldFacet, newFacet)event. - Diamonds can be implemented with it in a way that an
SLOADis not done for each selector to see if it already exists before adding/replacing/removing it. This can significantly reduce gas costs for adding/replacing/removing selectors.
In general I like the simplicity of SetDiamondFacet.
Cons:
- After an upgrade, the
SetDiamondFacetevents in the upgrade transaction do not say specifically if selectors were added or replaced. To understand that from events, tooling has to retrieve and process the initial deployment and the entire history of upgrades. Or tooling can examine transactions. The three events are more explicit and provide more information – if a selector is added or replaced, and what the old facet is.
SetDiamondFacet(selector, facet) does not save an extra SLOAD if current integrity and safety checks are performed. Current diamond implementations verify intent. If a person specifies to add functions – but if one or more of those functions already exist, then the diamond will revert. Similarly a diamond won’t let a person replace a function that doesn’t exist, and won’t let a person remove a function that doesn’t exist. This functionality also prevents selector clashes from being added to a diamond – two different functions with the same function selector but different function signatures. Though that is highly unlikely.
The SetDiamondFacet(selector, facet) event does not save an extra SLOAD if introspection functions are used. The introspection could be made optional, but I feel that would weaken tooling. Blockscout relies on an introspection function to be present and uses it to retrieve facet addresses. And I think etherscan does the same. I think it could be much harder for them to implement and reliably support diamonds using events, and they may not want to. @vbaranov what do you think?
On the other hand it is up to implementers to decide how their diamonds work.
I do see the advantage of implementing a diamond without an SLOAD for each selector to check if it exists already before adding/replacing/removing it to the diamond. That saves significant gas.
Do you expect frequent changes in the diamond configurations? I do not. Hence I would prioritize easy auditability over SLOAD gas savings of emitted events.
DiamondFunctionAdded(selector, facet)DiamondFunctionReplaced(selector, oldFacet, newFacet)DiamondFunctionRemoved(selector, oldFacet)
There can be one event indeed:
DiamondFunctionSet(selector, oldFacet, newFacet)
oldFacet to be 0x0 when added only
newFacet to be 0x0 when removed only
It is similar to what users are used with mint/burn and corresponding Transfer events.
edit: replaced wrongly stated “function” with “event”
Good points @radek, thank you.
I just published a proposal for a new ERC standard for Facet-Based diamond contracts.
It builds on ERC-8109 Diamonds, Simplified.
Facet-based diamonds remove off-chain function selector management from deployment and upgrades, and reduce gas costs to deploy diamonds.
The proposal is here
New ERC: Facet-Based Diamonds
We could move further away from the diamond metaphor by renaming:
FacettoDelegate,facetAddresstodelegateDiamondtoDispatcherorDispatchProxy
Coming from C, I like switch but that word is overloaded.
I’d be okay with this. I don’t think it improves global auditability since oldFacet is redundant if you have the previous logs, but it would improve the auditability of an individual transaction in isolation.
No need to maintain storage for the function facet pairs. Instead you can update functionFacetPairs when you you add or remove functions. I wrote a script that generates a functionFacetPairs implementation from a solidity interface parameter using forge inspect. The script can be run automatically if you are building with make to keep the function up to date.
@wjmelements That is a very interesting approach. I like it a lot! ![]()
ERC-8109 has been withdrawn in favor of ERC-8153 Facet-Based Diamonds.
This is disappointing because I was building on it. I remain strongly opposed to facet-level inspection and monolithic facets. In particular it should be possible to:
- uninstall a method from a facet without deploying a new contract
- install a subset of methods in a facet
- utilize facets implementing overlapping selectors
It seems I must propose my own DispatchProxy standard. The design goal will be minimalism to promote extensibility while supporting wallets and block explorers with ABI. Perhaps erc-8153 and erc-2535 will be compatible extensions but that will not be the main priority.
Plan:
- abandon all diamond jargon
- singular configuration event (
SetDelegate)
DM me if you want to collaborate.
I am sorry ERC-8109 was withdrawn, and I understand.