The Dark Forest game implements the Diamond pattern, EIP-2535. This pattern provides a single point-of-entry into all game functions, and the ability to upgrade or enhance live game instances.
Besides the EIP specification, which is a highly recommended read, some additional materials about the Diamond pattern include:
- Introduction to EIP-2535 Diamonds by Nick Mudge
- The Diamond Standard: A new paradigm for upgradeability by Ainsley Sutherland
- Understanding Diamonds on Ethereum by Nick Mudge
- Ethereum's Maximum Contract Size Limit is Solved with the Diamond Standard by Nick Mudge
This document assumes some understanding of Solidity smart contract development. Additionally, the Diamond pattern introduces its own terminology:
- The
diamondis the entrypoint smart contract that can proxy to different facets. - A
facetis a smart contract that provides functionality to the diamond. Individual functions of afacetare cut into a diamond via their selectors. - The
cut(ordiamondCut) function call is used to add, remove, or update facets with the diamond. Thecutfunction call can also execute initialization logic after the diamond is updated. - A
selectoris the first four bytes of the call data for a function call. There is a very small chance of a collision between any twoselectorsbut cutting multiple of the same selector is disallowed. - Each diamond will provide a
loupefacet that provides introspection functions for the diamond itself. This can be used to lookup facets and functions already registered with the diamond.
Note: The implementations of diamondCut and loupe functions can vary based on complexity and gas cost choices. Dark Forest has chosen the "diamond-2" implementation because it allows cheaper gas cost of the diamondCut function, which is useful for Lobbies. This results in a higher cost loupe function call.
The various smart contracts are organized into a few different places.
- Facets are stored in the
facets/directory and contain the game logic. - Libraries are stored in the
libraries/directory and contain functionality that can't fit in a facet or must be shared by multiple facets. Additionally,libraries/LibStorage.solcontains Dark Forest's Diamond Storage. - Vendored contracts are stored in the
vendor/directory, which has a mirrored structure ofvendor/facets/,vendor/interfaces/, andvendor/libraries/. The vendor README provides more information about the vendoring process. - The
DFInitialize.solcontract is provided at the root because it is only used by thediamondCutprocess. - The
DFTypes.solfile is also provided at the root because it only provides enum and struct types for the rest of the contracts—it is not a contract itself.
Contract state is shared with a pattern called Diamond Storage and is documented in detail in libraries/LibStorage.sol.
The article How to Share Functions Between Facets of a Diamond by Nick Mudge describes a bunch of possible ways to share functionality between facet smart contracts.
In order of preference, Dark Forest contracts share functionality using:
-
Storing shared state inside an existing storage struct or create a new one following the Diamond Storage pattern.
-
Organizing and refactoring the facets so they don't need cross-facet calls.
-
Writing libraries with
internalfunction calls; however, this might cause contract size issues. As documented on ethereum.org:Don't declare the library functions as internal as those will be added to the contract directly during compilation
-
Calling another another facet through the diamond, using the type safe calling convention
OtherFacet(address(this)).someFunction(arg1, arg2).Be aware: Care must be taken about the
msg.senderand permissions on certain functionality. -
Writing libraries with
publicorexternalfunction calls. These aren't inlined and must be deployed and linked into the facets. This is the least preferred solution.
Much of the Dark Forest Diamond code was based on Nick Mudge's diamond-2-hardhat repository at the 7feb995 commit.
Other inspiration for the implementation came from Aavegotchi's ghst-staking and aavegotchi-contracts.
For a previous version of the the Diamond pattern spec, Trail of Bits released an article titled Good idea, bad design: How the Diamond standard falls short; however, Nick Mudge addressed these concerns in a follow-up article that outlined how the EIP-2535 had been updated to address the concerns.
Nick Mudge maintains a list of security audits against the three different reference implementations of the Diamond pattern.