You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The standard is at a crossroads because I found a significant change that can be made in the architecture that improves things significantly.
Deployment and upgrade of diamonds has had a couple problems:
High gas costs.
Function selector management complexity
Today, deploying or upgrading a diamond requires assembling function selectors off-chain. Existing tools like Hardhat and Foundry don’t support this natively, so developers must rely on custom scripts or third-party libraries to handle diamond “plumbing.”
The change I propose below reduces gas costs and eliminates selector management complexity.
Diamonds become cheaper to deploy
Function selectors no longer need to be gathered off-chain
Standard deployment tools can be used without special support
But it also solves another problem:
ERC-2535 has the introspection functions facetAddresses(), which returns the facet addresses used by a diamond, and facets() which returns the facet addresses and function selectors used by a diamond. The problem with these functions is their implementations have been complex, feeding the idea that diamonds are complex to implement or understand. Earlier we solved this problem in ERC-8109 by removing these functions and adding the functionFacetPairs() function which is far simpler to implement.
Well, the change I propose, just by accident, happens to significantly simplify the implementations of facetAddresses()facets() so we can use them again in ERC-8109. With the change, efficient implementations of these functions becomes simple and easy to understand. We can change ERC-8109 to use the original introspection functions defined by ERC-2535 and functionFacetPairs is no longer needed so we can remove it from ERC-8109.
Having ERC-8109 use the same introspection functions as ERC-2535 will probably mean that Etherscan, Blockscout and Louper will support ERC-8109 diamonds automatically.
Architecture Change
Each facet MUST implement a pure introspection function:
function functionSelectors() externalpurereturns (bytes4 memoryselectors)
This allows a diamond to discover all selectors directly from the facet at deployment or upgrade time.
When deploying or upgrading a diamond, an array of facet addresses is required. The diamond queries each facet’s functionSelectors() function on-chain to determine which selectors to add, replace, or remove.
Selector gathering is no longer an off-chain responsibility.
This also means that ERC-8109 diamonds are facet based, not function based.
Example
Here is an example implementation of this function in DiamondInspectFacet.sol
With the new model, only one selector per facet needs to be stored.
8 facets → 8 selectors → 1 slot → ~20,000 gas
~10× reduction in selector storage cost
In addition to this, since ERC-8109 is now facet-based, it can use simpler, more gas efficient facet-based events to record the addition/replacement/removal of facets.
Reducing Complexity of Introspection Functions
As mentioned above, with the change in architecture, only the first selector of a facet needs to be stored in the selectors array. This is because the introspection functions can use the first selector of a facet to get a facet's address and then, if needed, can call functionSelectors() on the facet to get the rest of the function selectors provided by the facet.
Here is how the facetAddresses() function is implemented:
/** * @notice Gets the facet addresses used by the diamond. * @dev If no facets are registered return empty array. * @return allFacets The facet addresses. */function facetAddresses() externalviewreturns (address[] memoryallFacets) {
DiamondStorage storage s =getStorage();
bytes4[] memory selectors = s.selectors;
uint256 facetCount = selectors.length;
allFacets =newaddress[](facetCount);
for (uint256 selectorIndex; selectorIndex < facetCount; selectorIndex++) {
bytes4 selector = selectors[selectorIndex];
address facet = s.facetAndPosition[selector].facet;
allFacets[selectorIndex] = facet;
}
}
Here is how the facets() function is implemented:
struct Facet {
address facet;
bytes4[] functionSelectors;
}
/** * @notice Returns the facet address and function selectors of all facets * in the diamond. * @return facetsAndSelectors An array of Facet structs containing each * facet address and its function selectors. */function facets() externalviewreturns (Facet[] memoryfacetsAndSelectors) {
DiamondStorage storage s =getStorage();
bytes4[] memory selectors = s.selectors;
uint256 facetCount = selectors.length;
facetsAndSelectors =newFacet[](facetCount);
for (uint256 selectorIndex; selectorIndex < facetCount; selectorIndex++) {
bytes4 selector = selectors[selectorIndex];
address facet = s.facetAndPosition[selector].facet;
bytes4[] memory facetSelectors =IFacet(facet).functionSelectors();
facetsAndSelectors[selectorIndex].facet = facet;
facetsAndSelectors[selectorIndex].functionSelectors = facetSelectors;
}
}
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
The standard is at a crossroads because I found a significant change that can be made in the architecture that improves things significantly.
Deployment and upgrade of diamonds has had a couple problems:
Today, deploying or upgrading a diamond requires assembling function selectors off-chain. Existing tools like Hardhat and Foundry don’t support this natively, so developers must rely on custom scripts or third-party libraries to handle diamond “plumbing.”
The change I propose below reduces gas costs and eliminates selector management complexity.
But it also solves another problem:
ERC-2535 has the introspection functions
facetAddresses(), which returns the facet addresses used by a diamond, andfacets()which returns the facet addresses and function selectors used by a diamond. The problem with these functions is their implementations have been complex, feeding the idea that diamonds are complex to implement or understand. Earlier we solved this problem in ERC-8109 by removing these functions and adding thefunctionFacetPairs()function which is far simpler to implement.Well, the change I propose, just by accident, happens to significantly simplify the implementations of
facetAddresses()facets()so we can use them again in ERC-8109. With the change, efficient implementations of these functions becomes simple and easy to understand. We can change ERC-8109 to use the original introspection functions defined by ERC-2535 andfunctionFacetPairsis no longer needed so we can remove it from ERC-8109.Having ERC-8109 use the same introspection functions as ERC-2535 will probably mean that Etherscan, Blockscout and Louper will support ERC-8109 diamonds automatically.
Architecture Change
Each facet MUST implement a pure introspection function:
This allows a diamond to discover all selectors directly from the facet at deployment or upgrade time.
When deploying or upgrading a diamond, an array of facet addresses is required. The diamond queries each facet’s
functionSelectors()function on-chain to determine which selectors to add, replace, or remove.Selector gathering is no longer an off-chain responsibility.
This also means that ERC-8109 diamonds are facet based, not function based.
Example
Here is an example implementation of this function in
DiamondInspectFacet.solReduced Deployment Gas
Current diamond implementations store every selector in on-chain storage in a
selectorsarray for introspection.Example:
8 facets × 10 selectors = 80 selectors
→ 10 storage slots
→ ~200,000 gas
With the new model, only one selector per facet needs to be stored.
8 facets → 8 selectors → 1 slot → ~20,000 gas
~10× reduction in selector storage cost
In addition to this, since ERC-8109 is now facet-based, it can use simpler, more gas efficient facet-based events to record the addition/replacement/removal of facets.
Reducing Complexity of Introspection Functions
As mentioned above, with the change in architecture, only the first selector of a facet needs to be stored in the
selectorsarray. This is because the introspection functions can use the first selector of a facet to get a facet's address and then, if needed, can callfunctionSelectors()on the facet to get the rest of the function selectors provided by the facet.Here is how the
facetAddresses()function is implemented:Here is how the
facets()function is implemented:The full implementation of
DiamondInspectFacet.solcan be seen here: https://github.com/Perfect-Abstractions/Compose/blob/main/src/diamond/DiamondInspectFacet.solThe implementation of the
upgradeDiamondfunction be seen here: https://github.com/Perfect-Abstractions/Compose/blob/main/src/diamond/DiamondUpgradeFacet.solI am planning to update ERC-8109 with this new change. I am interested in your ideas, questions and feedback.
Beta Was this translation helpful? Give feedback.
All reactions