Any comments, contributions, or feedback? Ping me!
Follow @adlrocha Tweet to @adlrocha
Reducing the size of Solidity contracts
I am currently working on a project that we call IPC (InterPlanetary Consensus). We are now implementing the core logic of the protocol in a set of Solidity contracts. Unfortunately, the implementation of one of the contracts of the protocol, the Gateway
, was too large to be deployed (over the 24KB
limit), so we had to figure out ways to reduce its size. This post, is a great introduction of the different approaches to fight the contract size limit. In this quick write-up I will share chronologically how we leveraged each of these approaches to try and make our contract “deployable”.
-
Optimize the code: It may seem like an obvious one, but optimizing the code it can sometimes have a huge impact in the size of the contract. This is the first thing we tried. We removed functions, duplicate code, enabled the compiler’s optimizer (yep, we compile with the
--via-ir
flag), declared custom errors, etc. We definitely reduced the size a lot, but the gateway contract was still to large to be deployed. -
Separate the contracts: The second approach we followed was to separate as much of the contract logic as we could into different libraries. But once again, there was a limit in the level of “downsizing” that we achieved if we wanted a clean separation between libraries and the core logic.
EIP-2535: Diamond contract
At this point we decided to embark ourselves into more complex contract separation architectures. While reading through different solutions and EIPs, we came across this Introduction to EIP-2553 diamonds that perfectly illustrated what we were after, and I quote:
“I wanted one storage space for all state variables and one Ethereum address from which I could design and implement all functions without bytecode size limitation. And I wanted all functions to read and write to state variables directly, easily and in the same way. It would also be nice to have optional, seemless upgrade functionality: to be able to replace functions, remove them and add new functionality without needing to redeploy everything. To be able to extend the smart contract system in a consistent, systematic way after it is deployed.”
It seemed like the Diamond pattern was a perfect fit for us. A diamond appears to be a single smart contract with a single Ethereum address. But internally and hidden from the outside it utilizes a set of contracts called facets for its external functions. The following two images depicts the implementation of a diamond under the hood:
Cool, right? And now instead of paraphrasing a lot of the resources we went through for the implementation to explain how they work, let me collect all of these resources and let you navigate them at will.
- ERC-2535: Diamonds, Multi-Facet Proxy
- Sample implementation of diamond contracts (1, 2, 3)
I will also leave you here PR where we move from a raw contract to a diamond pattern implementation for reference:
Important references
Finally, here I leave a bunch of interesting resources about Solidity fundamentals that may come handy if you decide to dive deep into the implementation of a diamond contract:
- Understand how storage and state is laid out in contracts
- Understand how delegated calls work
- Layout of State in Storage variables
And this one is an interesting consequence you realize after tinkering with the diamond pattern, and is that libraries can actually have state, despite what the Solidity docs say :).
Any comments, contributions, or feedback? Ping me!