Should Diamond Loupe Functions Return Tightly Packed Data?

mudgen

Nick Mudge

Posted on August 31, 2020

Should Diamond Loupe Functions Return Tightly Packed Data?

Quick Background Material

Diamond: A diamond is a contract with functionality from multiple contracts that can share internal functions, libraries and state variables. Diamonds implement EIP-2535 Diamond Standard.

Facet: The word facet comes from the diamond industry. It is a side, or flat surface of a diamond. A diamond can have many facets. In EIP-2535 Diamond Standard a facet is a contract that supplies a diamond with external functions. A diamond gets its external functions from facets.

Loupe: A loupe is a magnifying glass that is used to look at diamonds. In EIP-2535 Diamond Standard the loupe is four standard functions that return what functions and facets a diamond currently has.

Tightly Packed Encoding vs ABI Encoding

Currently, three of the diamond loupe functions return a tightly packed bytes array.

The great thing about a tightly packed bytes array is that it doesn't waste bytes. A tightly packed bytes array doesn't contain any useless bytes containing the value 0, unlike ABI encoded arrays.

The alternative to a tightly packed bytes array is an ABI encoded array.

ABI encoded arrays are the default data encoding used by Solidity contracts. They are more standard than tightly packed bytes arrays and are easy to use. You or something has to encode/decode tightly packed bytes arrays. Ethereum contracts and tools handle all the encoding/decoding of ABI encoded arrays for us.

But ABI encoded arrays are very inefficient, wasting bytes. ABI encoding uses 32 bytes for many values when that many bytes are not needed.

Let's look at some loupe functions:

facetAddresses loupe function

The facetAddresses loupe function returns a bytes array of all facet addresses currently used by a diamond. The bytes array is tightly packed so every byte in the array is used.

Instead of returning a bytes array of addresses the facetAddresses function could return an ABI encoded array of addresses.

Let's compare the two approaches: Let's say that a diamond has 10 facet addresses. An address is 20 bytes in size.

  1. Using a tightly packed bytes array the size is 200 bytes. Because 10 * 20 = 200.
  2. Using an ABI encoded array of addresses the size is 320 bytes. Because 32 * 10 = 320. Each value in the array has 32 bytes and only 20 bytes of each value is used. For many values ABI encoding uses 32 bytes whether they need them or not.

facetFunctionSelectors loupe function

The loupe function facetFunctionSelectors returns a tightly packed bytes array of all function selectors currently used by a diamond.

Instead of using a tightly packed bytes array it could return an ABI encoded array of bytes4 values.

Let's compare the two approaches: Let's say that a diamond has 100 function selectors. Each function selector is 4 bytes in size.

  1. Using a tightly packed bytes array the size is 400 bytes. Because 100 * 4 = 400.
  2. Using an ABI encoded array of bytes4 the size is 3200 bytes. Because 100 * 32 = 3200. Each value in the array has 32 bytes even though only 4 of the bytes are used in each value.

facets() loupe function

This function returns an array of tightly packed bytes arrays. Each bytes array holds a facet address and the function selectors associated with the facet address. I'll let you or someone else figure out or make a comparison of tightly packed encoding and ABI encoding for this one.

So What Should Diamonds Use?

Tightly packed encoding is efficient.

ABI encoding is inefficient.

But ABI encoding is very standard and used by all Ethereum tools and software. ABI encoding is easier to use and write software with. It is nice. Maybe that matters more than the efficiency of data encoding.

I originally chose tightly packed bytes array return values for EIP-2535 Diamond Standard because I envisioned that there would be thousands of diamonds in the world and off-chain client software continuously calling the loupe functions to provide information about diamonds. Tightly packed bytes arrays would reduce bandwidth costs and perhaps offer other scaling advantages. But in a world where computing resources are already cheap and getting cheaper, maybe it doesn't matter. Maybe easier software now matters more.

Easier to write software can have less bugs.

So what do you know and think? Should EIP-2535 Diamond Standard be changed to use ABI encoded array return values for the loupe functions? Or should it continue to use tightly packed bytes arrays?

UPDATE 6 September 2020

Thanks to all the developers who provided their feedback and input about whether the loupe function return values should be ABI encoded or tightly packed.

It was interesting to discover that developers were as mixed about it as myself.

I removed the tightly packed encoding of loupe function return values from EIP-2535 Diamond Standard. Loupe function return values now use ABI encoding.

I made this change because at this time the data efficiency of tightly packed encoding isn't needed. In the future if it is needed or more useful then a new standard for efficient loupe functions can be written and used. In addition, at any time developers can write their own diamond related information retrieval functions with whatever data encoding efficiency they desire and use them while at the same time providing the four standard loupe functions.

ABI encoded return values are a bit easier to work with, a bit easier to learn and understand. In addition, something unexpected was discovered.

Recent research has shown that it is possible to make all the diamond loupe functions gas-efficient enough to be executed in on-chain transactions. ABI encoding simplifies the code to do this and may save gas.

Currently the diamond reference implementation is gas-optimized to use the least gas for adding/replacing/removing any number of functions. The loupe functions in the diamond reference implementation are great for off-chain software to use to show what functions and facets a diamonds use. But it costs too much gas to call the loupe functions in on-chain transactions. But it doesn't have to be this way.

It is possible to instead make the loupe functions gas-optimized for execution in on-chain transactions.

It is also possible to make the loupe functions gas-optimized for adding/replacing/removing functions and for execution in on-chain transactions. It would require a lot of code and be complicated but I think it is worth doing.

I think it would be interesting and awesome to have gas-efficient diamonds where you can query and process its functions in on-chain transactions.

Find a job developing smart contracts using Solidity at delegatecall.careers

💖 💪 🙅 🚩
mudgen
Nick Mudge

Posted on August 31, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related