Home

Awesome

EIP-2535 Diamond Pattern for Morons

:warning: WARNING :warning:

Clone & Test

git clone https://github.com/alexbabits/diamond-3-foundry
forge install # Installs OZ dependency, only needed for ERC20 and ERC1155 examples
forge test

Deploy Locally

anvil # Then open 2nd bash terminal.
export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # This is a local private key from anvil.
forge script script/deployDiamond.s.sol --rpc-url http://localhost:8545 --private-key $PRIVATE_KEY --broadcast

Education Start: Existential Questions

  1. Why am I here? Most likely because you started building a contract that exceeded Vitalik's tiny 24kB contract size limit. (Or you a security researcher trying to quickly understand the Diamond Proxy Pattern that a protocol uses).
  2. WTF is the Diamond Proxy Pattern? It just uses delegatecall under the hood like all popular proxy-implementation patterns to allow it's implementations to operate on the central proxy storage. Delegatecall can screw up your original storage if you aren't careful during upgrades.
  3. How do I verify my diamond contract? IDK ¯\_(ツ)_/¯ as I haven't explicitly done this myself. Etherscan cannot view and verify diamonds properly, so Louper was built to examine and verify testnet and mainnet diamond protocols.

The Correct Stack [Diamond-3, AppStorage, Foundry, VSCode]

  1. Starter Template Choices: [Diamond-1, Diamond-2, Diamond-3]. Best: Diamond-3. They are all similar and cover the core implementation of Diamond pattern. This is the latest one, it works fine.
  2. Storage Organization Choices: [AppStorage, DiamondStorage]. Best: AppStorage. Much cleaner/easier, big protocols use it.
  3. Tool Choices [Foundry, Hardhat, ...]. Best: Foundry. Hardhat is better if you really enjoy javascript over solidity.
  4. IDE: [VSCode, Remix, ...]. Best: VSCode. Remix is better for quick mock-ups, but VSCode/IntelliJ/etc. is better actual serious projects imo.

Basic Information

Architecture

This repo can be seen as having 4 "parts". It's recommended to first understand the basic concepts of the diamond, then learn the dummy examples, and then move on to actual real life token examples. This repo is commented in detail so you can learn quickly through looking at the code.

:warning: The last two parts with ERC20Facet and ERC1155Facet are "hand crafted" examples. Assume they have issues and bugs. :warning:

  1. Diamond template files needed to adhere to EIP-2535 that don't need to be touched.
  2. Dummy Examples using AppStorage
  3. ERC20Facet example using AppStorage.
  4. ERC1155Facet example using AppStorage.

Diamond template files

Dummy Examples

ERC20Facet Example

Overview: This example uses OpenZeppelin ERC20. There are many different ways to implement an ERC dependent facet in your diamond. I like a more explicit approach where you first dump everything from an ERC into one file and then decide if you want to inherit something or refactor it into separate interfaces. Regardless of the approach, you need to fully understand the anatomy of the ERC you want to implement. For all state changing functions, you are required to do some manual "surgery" by appending s to state variables, so that the ERC template works as intended with the AppStorage pattern of your Diamond pattern protocol. More clarity: Importing the AppStorage.sol file and referencing it's struct variables through s.Var is what allows the facet to operate on the centralized storage of the Diamond, allowing multiple facets to operate all on that same AppStorage with respect to the Diamond.

Facet Constructor Note: When a facet has a constructor, any state that is set during construction resides in the actual facet itself, like with ERC20Facet. This is not a problem since we purposely have a dummy constructor only to satisfy the OZ requirement. We are always interacting with the ERC20Facet through the single diamond proxy address. If you were to interact directly with the ERC20Facet, you would see the empty strings for name and symbol, I think. "...data stored in a facet’s contract storage is ignored by a diamond... Immutable variables can be set in constructor functions of facets, because these are stored as part of a contract’s bytecode, and are not stored in contract storage. - Nick Mudge".

Inheritance Note: You cannot inherit an abstract contract that uses storage because that messes up the slot 0 placement where AppStorage should be. I fixed this problem with AppStorageRoot.sol. You could just not directly inherit and use any "template" contracts that already have storage.

ERC1155Facet Example

References