Cointime

Download App
iOS & Android

Diamond Proxy Contracts: Best Practices

Validated Project

Proxy contracts are important tools for smart contract developers. Various proxy patterns have emerged to standardize the usage and implementation of proxy contracts. We have previously outlined upgradeable proxy contract security best practices. In this article, we introduce another proxy pattern that has gained traction in the developer community, the Diamond proxy pattern.

What is a Diamond Proxy?

A diamond proxy contract, also known as a "diamond," is a design pattern for Ethereum smart contracts that is introduced by Ethereum Improvement Proposal (EIP) 2535. The diamond pattern allows a contract to have an unlimited number of functions by separating the contract's functions into smaller contracts called "facets." The diamond acts as a proxy that routes function calls to the appropriate facets.

The diamond pattern is designed to address the issue of the maximum contract size limit imposed by the Ethereum network. By breaking down a large contract into smaller facets, the diamond pattern allows developers to build more complex and feature-rich smart contracts that would otherwise exceed the size limit.

Diamond proxies offer significant flexibility compared to traditional upgradeable contracts. They allow for partial upgrades, or the ability to add, replace, or remove selected functionalities while leaving other parts untouched. They also circumvent the bytecode size limit of a single contract by delegating calls to different implementation contracts, thus allowing for more complex functionalities in one contract address. This article provides an overview of EIP-2535, including a comparison with the widely used Transparent Proxy Pattern and the UUPS Proxy Pattern, and its security considerations for the developer community.

Architecture

In the context of EIP-2535, a “diamond” is a proxy contract with function implementations provided by different logic contracts called “facets.” Compared to traditional proxy patterns, the “diamond” is equivalent to the proxy contract, and different “facets” correspond to the implementation contracts. Different facets of one diamond proxy can share internal functions, libraries and state variables. The key components of a diamond are as follows:

  • Diamond: The central contract that acts as a proxy and routes function calls to the appropriate facets. It contains a mapping of function selectors to facet addresses.
  • Facets: Individual contracts that implement specific functionality. Each facet contains a set of functions that can be called by the diamond.
  • Diamond Loupe: A set of standard functions defined in EIP-2535 that provide information about the facets and function selectors used in the diamond. The diamond loupe allows developers and users to inspect and understand the structure of the diamond.
  • DiamondCut: A function used to add, replace, or remove facets and their corresponding function selectors in the diamond. Only an authorized address (e.g., the diamond's owner or a multi-signature contract) can perform a diamond cut.

Similar to traditional proxies, when there is a function call on a diamond proxy, the proxy’s fallback function is triggered. The main difference with diamond proxies is that in the fallback function there is a selectorToFacet mapping that stores and determines which logic contract address has the implementation of the function that is being called. Then, it executes that function using delegatecall just like a traditional proxy.

Fallback Function Implementation

All proxies use the fallback() function to delegate function calls to external addresses. Below is shown the implementation of the diamond proxy and the implementation of a traditional proxy. Note that the assembly code blocks are very similar, with the only difference being the facet address in the delegatecall of the diamond proxy, and the impl address in the delegatecall of the traditional proxy. The main difference is that in the diamond proxy, the facet address is determined by a hashmap from the caller’s msg.sig (function selector) to the facet address, whereas in traditional proxy, the impl address is not dependent on the caller input.

Diamond proxy fallback function
Traditional proxy fallback function

Adding, Replacing, and Removing Facets

The selectorToFacet mapping determines which contract contains the implementation of each function selector. The project team oftentime needs to be able to add, replace, or remove such function selector to implementation contract mapping. EIP-2535 specifies that a diamondCut() function is mandatory for this purpose. A sample interface is shown below.

Each FacetCut struct contains a facet address and array of 4-bytes function selectors to be updated in a diamond proxy contract. The FaceCutAction allows one to AddReplace, and Remove function selectors. The implementation of the diamondCut() function should include adequate access control, prevent storage slot collision, revert on failure, and emit events appropriately.

Querying Facets

In order to query which functions a diamond proxy has and which facets are used, the “diamond loupe” is used. A “diamond loupe’ is a special facet that implements the following interface defined in EIP-2535:

The facets() function should return all facet addresses and their four byte function selectors. The facetFunctionSelectors() function should return all the function selectors supported by a specific facet. The facetAddresses() function should return all the facet addresses used by a diamond. The facetAddress() function should return the facet that supports the given selector, or address(0) if not found. Note that there should not be more than one facet address that has the same function selector.

Storage Slot Management

Given that the diamond proxy delegates different function calls to different implementation contracts, the proper management of storage slots to prevent collision is critically important. EIP-2535 referenced several possible storage slot management approaches.

Diamond Storage

A facet can declare state variables in structs. Any number of structs, each with a different storage position, can be used by a facet. Each struct is given a specific position in contract storage. Facets can declare their own state variables that do not conflict with the storage locations of state variables declared in other facets. A sample library and diamond storage contract is provided in EIP-2535, as shown below:

AppStorage

AppStorage is a specialized version of diamond storage. This pattern is used to more conveniently and easily share state variables between facets. An AppStorage struct is defined to contain an arbitrary number and type of state variables needed for an application. A facet always declares the AppStorage struct as the first and only state variable, in storage slot position 0. Different facets can then access the variables from the struct.

Other

There can be other storage slot management strategies, including a mixture of diamond storage and AppStorage, such that some structs are shared amongst different facets, and some are unique to specific facets. In all cases, it is critically important to prevent accidental storage slot collisions.

Comparison with Transparent Proxies and UUPS Proxies

The two main proxy patterns currently used by the Web3 developer community are the transparent proxy pattern and the UUPS proxy pattern. In this section, we briefly compare the diamond proxy pattern with the transparent proxy and the UUPS proxy patterns.

  1. EIP-2535
  2. EIP-1967
  3. Diamond proxy reference implementation
  4. OpenZeppelin implementation

Diamond Proxy Contract Security Best Practices

Proxies and upgradeable solutions are complex systems, and OpenZeppelin provides library code and comprehensive documentation for UUPS/Transparent/Beacon upgradeable proxies. However, for the diamond proxy pattern, while OpenZeppelin has affirmed its benefits, they have decided not to include the implementation of the EIP-2535 Diamonds in their libraries. Developers using existing third-party libraries or implementing the solution on their own must exercise extra caution in their implementation. Therefore, we have compiled a list of security best practices for the developer community's reference.

1. Break up the contract logic into separate facets to enable modular design and upgrades

By breaking up the contract logic into smaller, more manageable modules, developers can more easily test and audit their code. Additionally, this approach allows developers to focus on building and maintaining specific facets of a contract, rather than managing a complex, monolithic codebase. The end result is a more flexible and modular codebase that can be easily updated and modified without affecting other parts of the contract.

Source: Aavegotchi Github

2. The proxy contract must be initialized with a valid DiamondCut facet contract address during deployment

When the diamond proxy contract is deployed, it must add the address of the DiamondCutFacet contract to the Diamond proxy contract, with the diamondCut() function implemented. The diamondCut() function is used to add, remove or replace facets and functions, and the Diamond proxy cannot work properly without DiamondCutFacet and diamondCut().

Source: Mugen’s Diamond-3-Hardhat

3. To add new state variables to a storage struct, add them to the end of the struct

When adding new state variables to a storage struct in a smart contract, it is important to add them to the end of the struct. Adding new state variables to the beginning or middle of a struct can cause the new state variable to overwrite existing state variable data, and any state variables after the new state variable will likely reference the wrong storage location.

4. When using the AppStorage pattern, do not declare and use state variables outside the struct

The AppStorage pattern requires that one and only one struct is declared for a diamond proxy, and the struct is shared by all facets. If multiple structs are desired or needed, the DiamondStorage pattern should be used instead.

5. Do not put structs directly in another struct

Do not put structs directly in another struct unless you don’t plan on ever adding more state variables to the inner structs. You won't be able to add new state variables to inner structs in upgrades without overwriting the storage slot of variables declared after the struct.

The solution is to add new state variables to the structs that are stored in mappings, rather than putting structs directly in structs, as the storage slot for variables in mappings is calculated differently and is not continuous in storage.

6. Do not add new state variables to structs that are used in arrays

When a new state variable is added to a struct, it changes the size and layout of the struct. This can cause problems if the struct is used as an element in an array, as the size of the array will be affected by the size of the struct. If the size and layout of the struct is changed, then the size and layout of the array will also change, which can lead to issues with indexing or other operations that rely on the size and layout of the struct being consistent.

7. Do not use the same storage slot for different structs

Similar to other proxy patterns, each variable should have a unique storage slot. Otherwise, two different structs at the same location will overwrite each other.

8. Do not leave the initialize() function unprotected

The initialize() function is commonly used to set important variables such as the address of privileged roles. If left uninitialized when the contract is deployed, a bad actor could call it and take control of the contract.

The recommendation is to include proper access control on the initialize / setter functions, or ensure that the function is called when the contract is deployed and cannot be called again.

9. Do NOT allow any facet to be able to call selfdestruct()

If any facet in a contract is able to call the selfdestruct() function, it can potentially destroy the entire contract and cause a loss of funds or data. This is particularly dangerous in the diamond proxy pattern, where multiple facets may have access to the proxy contract's storage and data.

Summary

We have seen an increasing number of projects adopting the Diamond proxy pattern in their smart contracts, due to its flexibility and other advantages compared to traditional proxies. However, the additional flexibility could also mean a broader attack surface for malicious actors. We hope this article is useful to the developer community in understanding the mechanics of the diamond proxy pattern and its security considerations. Project teams should conduct rigorous testing and third party audits to reduce the risk of exploits related to implementation of diamond proxy contracts.

Read more: https://www.certik.com/resources/blog/7laIe0oZGK6IoYDwn0g2Jp-diamond-proxy-contracts-best-practices

Comments

All Comments

Recommended for you

  • Web3 AI platform ChainML completes $6.2 million seed round of financing

    Web3 AI platform ChainML has announced the completion of a $6.2 million seed round of expansion financing, led by Hack VC, with participation from Inception Capital, HTX Ventures, Figment Capital, Hypersphere Ventures, and Alumni Ventures. The platform also announced the launch of its agent-based foundation layer, Theoriq.

  • Metaverse project Baby Shark Universe completes seed round financing

    Baby Shark Universe project, a metaverse project, has completed a seed round of financing with a valuation of $34 million. Participating investors include Animoca Brands, CREDIT SCEND, Sui Foundation, Comma3 Ventures, Creditcoin, GM Ventures, Neuler, Notch Ventures, X+, and Planetarium. The specific amount has not been disclosed, and the new funds will be used for development and global marketing. According to reports, Baby Shark Universe is an open-world role-playing game where players can create their own game content (items, maps), enjoy content created by other players, and expand the game's narrative based on their choices and actions.

  • Hong Kong Stock Exchange Confirms Crypto ETFs Unavailable to Mainland Chinese Investors

    According to Coindesk, the Hong Kong Stock Exchange has confirmed that cryptocurrency ETFs are not available to mainland Chinese investors. Hong Kong's cryptocurrency ETFs will provide a means to bypass capital controls in mainland China due to their unique physical redemption model.

  • Web3 social infrastructure UXLINK completes $5 million in financing

    Web3 social infrastructure UXLINK announced the completion of a new round of $5 million financing, led by SevenX Ventures, INCE Capital, and HashKey Capital. It is reported that UXLINK's total financing has now exceeded $15 million.

  • Chinese police bust underground bank using cryptocurrency for illegal currency conversion

    Chinese police have arrested six people for running an illegal currency conversion operation that used cryptocurrency to handle around $296 million. The operation was discovered by the Public Security Bureau of Panshi City, Jilin, and involved an "underground bank" that exploited the anonymity and ease of cross-border transfers offered by crypto. The operation used domestic accounts to receive and transfer funds, and exchanged between the yuan and South Korean won. The service was used by Korean purchasing agents, e-commerce firms, and import/export companies, among others.

  • Hong Kong Securities Regulatory Commission warns the public to beware of a suspicious asset investment product called "LENA Network"

    Hong Kong Securities and Futures Commission warned the public to be wary of a suspicious virtual asset investment product called "LENA Network". The product involves pledging and lending arrangements related to virtual assets, and claims to provide high returns to investors. This investment product has not been approved by the Securities and Futures Commission for sale to the Hong Kong public. The Securities and Futures Commission notes that the Hong Kong public can access information about the product and contact the product through the Internet. The Securities and Futures Commission advises against trusting those "too good to be true" investment opportunities and remaining vigilant when making investment decisions.

  • Hong Kong Securities and Futures Commission: The Anti-Money Laundering Ordinance applies to the virtual asset industry

    The "virtual currency to ETF" mechanism in Hong Kong has raised concerns about money laundering. The industry believes that the review difficulty, such as KYT (Know Your Token), is high. Some individuals with mainland backgrounds are trying to conduct small-scale "virtual currency to ETF" transactions, taking the opportunity to "whiten" their own holdings of ether and bitcoin through forms such as personal accounts. They have also deployed some virtual currencies to Hong Kong's virtual currency exchanges and will decide whether to increase capital in the future depending on the situation. When responding to relevant questions, the Hong Kong Securities and Futures Commission emphasized that in the operation of ETF products, every link in the entire virtual asset ecosystem, including fund companies, custodians, asset trading platforms, participating brokers, etc., must be licensed or recognized institutions and strictly comply with requirements such as asset custody, liquidity, valuation, information disclosure, and investor education. The "Anti-Money Laundering Ordinance" of the Securities and Futures Commission also stipulates that financial institutions and designated non-financial enterprises and industry personnel must comply with customer due diligence and record-keeping requirements, and relevant regulations apply to the virtual asset industry.

  • TON community member: Some TON wallets received virtual account NFTs starting with "888", which is a phishing project

    On May 13th, according to a member of the TON official community, a new NFT with a virtual number starting with "888" has been added to the TON wallet. However, the transaction fee for each transfer is as high as 1 TON, which is caused by the fishing project changing the Gas.

  • Swiss Crypto Bank Amina: Listing Ethereum as a Security Could Cause Many Crypto Teams to Exit the Space

    Swiss encrypted bank Amina stated in the latest "Cryptocurrency Market Monitoring" report that classifying Ethereum as a security could not only bring risks to the entire cryptocurrency market, but also lead to many cryptocurrency teams exiting the field. This determination could hinder the development of the cryptocurrency market and potentially reverse progress made over the years. In addition, the US SEC is likely to delay its decision on the status of Ethereum, putting the cryptocurrency asset in a "gray area".

  • Ethereum has about $48.05 million in on-chain loan liquidation quota around $2,778

    According to Defi Llama data, there is approximately $48.05 million in on-chain liquidation volume for Ethereum around $2,778.