Diamond Loupe Functions
Nick Mudge
Posted on October 8, 2020
A loupe is a magnifying glass that is used to look at diamonds.
In EIP-2535 Diamond Standard a loupe is four standard read-only functions that tell you what functions and facets are provided by a diamond.
A facet is a smart contract that supplies diamonds with external functions.
If you look at the source code of a diamond it will show a fallback function. It doesn't show what external functions it has. To get that information you need to call one or more of the four standard, read-only loupe functions.
Here are the four standard read-only loupe functions:
-
facetAddresses()
Returns all the facet addresses used by a diamond. -
facetAddress(bytes4 _functionSelector)
Returns the facet address that implements a functions. -
facetFunctionSelectors(address _facet)
Returns all the functions used by a diamond that come from a particular facet. -
facets()
Returns an array consisting of all the facet addresses and functions used by a diamond.
The loupe functions can be used for many useful things including:
- Use the facet addresses to query services like Etherscan to retrieve and show all the source code used by a diamond.
- Retrieve ABI info for a diamond.
- Tools and programming libraries use them to deploy diamonds.
- Tools and programming libraries use them to upgrade diamonds.
- User interfaces use them to show information about diamonds.
- User interfaces use them to enable users to show and call functions on diamonds.
- Tests use them to verify that correct functions and facets are being used correctly.
The loupe functions make diamonds transparent. This is important for understanding, showing and using exactly the functionality provided by diamonds.
They are important for security to verify that diamonds are used correctly and not mistakenly or maliciously.
They are important for testing to ensure that diamonds contain the correct functions and facets and to test code that uses diamonds.
Another Way. Transparency With Events
The loupe functions are not the only way to find out what functions and facets a diamond has.
Anytime a function/facet is added/replaced/removed on a diamond the DiamondCut
event is emitted that records exactly what changed in a diamond.
All the DiamondCut
events for a diamond can be queried and an algorithm executed to determine what facets and functions currently exists for a diamond.
You might wonder why we have two ways to do the same thing.
First, the DiamondCut
event provides something that the loupe functions don't: A historical record of all upgrades done on a diamond. The historical record can be used to verify the state, correctness and security of a diamond over time.
So you might wonder why have loupe functions when events can be used to get the current functions and facets used by a diamond.
Why Have Loupe Functions When You can Use Events?
To be clear, this isn't a debate of events vs external functions. The DiamondCut
events are a given and there is no restriction on their use. The debate is this: Why have events and loupe functions?
Let's look at the tradeoffs of having/not having loupe functions.
Pros
More choice People that want to use event systems, such as the The Graph, or event database systems, to determine the contents of diamonds can do so. People who want to call the loupe external functions on diamonds to get the same data, can do so. Having events and the loupe functions make diamonds easier to use for more people because they can choose how to get the data.
Easier adoption by services and tools Services such as Etherscan, blockchain explorers, web applications, programming libraries and other software may prefer or require the use of events or external functions. Having both options increases the capability to integrate with tools and services.
More transparent, reliable and available If an event system is not available or up-to-date or is slow or has any other problem, then the loupe functions can be used. If external functions can't be called in a particular context then an event system or database can be used. Having both methods available increases the reliability, availability and transparency of diamonds.
Simple and easy The loupe functions are simple and easy to use. They are regular external contract functions that are called with regular client contract libraries like web3.js or ethers.js. No special custom software or third party software or library or dependency is needed to call the loupe functions, since they are regular contract functions.
Keeping data, code and logic within the diamond loupe functions simplifies client software that needs the data.
Software that uses events has to rely on custom software or a third-party library to query all theDiamondCut
events and execute logic to determine the current functions and facets.Secure The code for the
diamondCut
function and the diamond loupe functions is carefully looked at by Solidity developers and security experts. These functions are simple enough for a competent Solidity developer to read and understand.In theory the loupe functions can be executed on-chain. In theory the loup functions in the diamond-3 implementation are gas-efficient enough to be called and used in transactions.
Cons
Loupe functions add code to a diamond.
However a DiamondLoupeFacet only needs to be deployed once and can be reused by many diamonds.
The diamond-1 implementation does the following to implement loupe functions:
- Stores an array of function selectors. When a new function is added its selector is added to the function selectors array. When a function is removed its selector is removed from the function selectors array.
- The loupe functions are implemented like this:
-
facetAddress(bytes4 _functionSelector)
The facet address that is associated with the_functionSelector
is found and returned. -
facetAddresses
The function selectors are looped through to find all the facet addresses used by the diamond. An array of facet addresses is returned. -
facetFunctionSelectors(address _facet)
The function selectors are looped through to find all the selectors that belong to the facet specified by_facet
. An array of selectors is returned. -
facets()
The function selectors are looped through to find all the facet addresses and function selectors used by the diamond. These are returned.
-
The diamond-2 implementation is implemented the same way as diamond-1 except that its code is more complicated because it is gas-optimized.
The diamond-3 implementation does the following to implement loupe functions:
-
Stores an array of facet addresses. For each facet address an array of function selectors is stored. When a new facet is added it is added to the array of facet addresses. When a facet is removed it is removed from the array of facet addresses. When a function is added its selector is added to an array of function selectors. When a function is removed its selector is removed from an array of function selectors.
- The loupe functions are very simple and implemented like this:
-
facetAddress(bytes4 _functionSelector)
The facet address that is associated with the_functionSelector
is found and returned. -
facetAddresses
The array of facet addresses is returned. -
facetFunctionSelectors(address _facet)
The array of function selectors for the facet is returned. -
facets()
The array of facet addresses is looped through to get each facet address and its function selectors. These are returned.
-
- The loupe functions are very simple and implemented like this:
Note that the loupe functions are read-only and are isolated in their own facet. They do not affect other facets of a diamond. Because of this the loupe functions do not increase complexity of the main application code or other facets.
Loupe functions require more gas for adding/replacing/removing functions Loupe functions require that some data be stored in the diamond so that function selectors and facet addresses can be retrieved. The diamond-2 implementation is the most efficient for adding/replacing/removing functions. It stores 8 function selectors in a single 32-byte contract storage slot and avoids storage reads and writes.
I don't have gas metrics for diamond implementations that don't have loupe functions. But I do have some gas metrics for diamond implementations that do have loupe functions.
- diamond-1 implementation To add 20 functions from a single facet costs 617,348 gas.
- diamond-2 implementation To add 20 functions from a single facet costs 536,246 gas.
- diamond-3 implementation To add 20 functions from a single facet costs 683,000 gas.
Overall
I think having events and loupe functions is worth it. I've been working on EIP-2535 Diamonds and diamond implementations to be useful for myself and you and others. So I am interested in your input about this.
Posted on October 8, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.