zdenham / open-facets

Proof of Concept for Open Facets

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Open Facets POC

This is a proof of concept of what a smart contract development library for EIP-2535 diamonds could look like, using ERC721 as an example.

The "contracts/example" directory includes an example implementation using the library. The example shows an ERC721 with an allowlist gated mint, and special functionality that prevents transferring tokens to blocked addresses. This was chosen to showcase both inheritance and composability with the library.

I summarize some of the design choices below, would love any feedback and critiques!

Design Choices / Features / Rationale

Facet Folders

Because we break facets down into various parts (external handlers, logic, storage, modifiers, interfaces), it constitutes having a separate folder for each facet rather than just a single file.

Storage Libraries

Diamond storage is used in this implementation, but storage is also kept in its own library with no other logic. This convention is useful because changes to the storage library can be treated separately and with more care than logic changes, sort of analogous to a migrations directory in a web server codebase.

Logic Libraries

All business logic goes in libraries with internal functions. This is important as diamond codebases grow, because multiple facets can share the same library functions with merely a JUMP operator, more info on sharing functionality here. In my experience, this becomes a huge DRY win over time. Note By keeping all business logic in libraries, you might also future proof your facets in case pure library facets become the norm.

Lightweight External Facet Contracts

With the exception of modifiers, the facet contracts have no logic, they simply make calls to underlying logic libraries.

If they don't do anything meaningful, then why have contract facets at all? Why not just library facets? So glad you asked:

  • Inheritance. If implementers need to override functionality in a standard contract, in many cases inheritance is still the cleanest way to do it. Libraries can't inherit. You can see an example of the inheritance use case in the TransferBlocklistNFT which inherits ERC721 and overrides transfer functionality.
  • ABI. The abi that is generated for libraries is different than contracts. As a result, many popular off chain tools and clients such as typechain, ethers etc... do not behave as you might expect with things like events and function signatures that encode struct arguments.
  • Modifiers (See below)

Portable Modifiers

I like modifiers a lot for concise access control checks, but because solidity libraries don't have inheritance, its not possible to share modifiers between libraries. As such, we declare separate modifier contracts which can be shared amongst the lightweight facets, see "OwnableModifiers.sol"

Virtual External Facet Functions

Public function signatures are all virtual, meaning implementers can choose to inherit facets should they need to. This gives a good amount of choice (perhaps too much?) to the implementer. They can choose to deploy the entire facet & turn off specific functions via diamond cut, or inherit and modify the facets.

Small Logic Functions

I've found breaking the logic facets down into very small, granular functions is useful for code reusability / composability across facets, and tends to be good programming practice in general.

Duplicate Event Declarations

Unfortunately, events (and custom errors) declared / emitted in libraries do not show up in the calling contract's ABI. There are a few issues about this in the solidity language repo.

As a result, to get events to show up properly in the ABI, we redeclare events both in the library and the contract. To make the event declarations shareable between contract facets, it may be worth breaking them into a separate IFacetEvents interfaces, but there is no getting around redeclaring them in the library at this point.

About

Proof of Concept for Open Facets


Languages

Language:Solidity 85.4%Language:JavaScript 14.6%