How I Reduced My Solidity Smart Contract Size — Practical Guide
In the ever-evolving world of blockchain technology, Ethereum has emerged as a dominant platform, enabling developers to create decentralized applications and execute smart contracts. These self-executing contracts are at the core of Ethereum's capabilities, facilitating secure and transparent interactions between parties without intermediaries. However, there are some significant challenges that developers face and contract size optimization and transaction cost optimization are some to name.
To deploy a contract on Ethereum, the Max allowable size is 24KB which was introduced in EIP-170. This limitation is also observed in the Layer 2 solutions like Polygon, Arbitrium, etc. When optimizing a contract, developers face a trade-off: reducing the contract size may lead to increased transaction costs while on the other hand, optimizing transaction costs might elevate the deployment cost as the contract size increases.
Without further wasting any time let's start our practical guide :)
Following is the code I am considering which is less than the 24KB limit due to space constraints here.
1) Replace the Require() with Custom Error:
To decrease contract size, you can replace the require() with Custom Errors. We will put these errors in the if condition and invert the previous condition written in require().
As you can see here, we have decreased the contract size. Since this is an example contract, that’s why I have used fewer modifiers but the real-world code might contain a lot of modifiers which would result in large contract size saving.
2) Remove Any Strings in Structs
If you have any string field in the struct, remove it, as it takes up a lot of space when used in the struct.
3) Move the extra calls to a Proxy Contract:
When we fetch data from the blockchain, we make calls to get data. We can move these calls to another proxy contract to reduce the size of the main contract.
To separate calls into the proxy contract, I have created a proxy contract, an interface, and an abstract contract.
The interface IMainContract contains the types for the main contract which are also required by both abstract and proxy contracts so separating these structs in an interface.
The abstract contract contains the function signatures of the main contract that would be called by the proxy contract.
Finally, we have our Proxy Contract that contains all our calls:
Now we are left with our Main Contract which is very small in size i.e. ~ 8 KiB!
4) Use a Library for Custom Errors:
To further reduce our contract size we can move all of our Custom Errors to a library and create a single Custom Error with an indicator that will show what error has occurred.
In my case, I have very less Custom Errors so it did not do too much saving but when you have a lot of errors it will become handy.
5) Optimizer:
Up until now, we have not turned on the optimization. Now we will see the effects of optimization with different numbers of the “runs” on the contract size.
Following is the comparison of contract sizes when compiled with different numbers of runs.
Conclusion:
In real-world scenarios, the effectiveness of these optimizations will depend on the complexity of the smart contract and its particular use case. I would also like to remind you again that there is a tradeoff between the contract size and transaction cost. Developers must find the right balance between contract size and transaction costs to ensure optimal performance and cost-effectiveness for their specific smart contract deployment.
If you liked my story you can follow me for more interesting tips and tricks at Farasat Ali — Medium.
You can Find Me on LinkedIn and GitHub.
Also, Visit My Portfolio at:
You can also check out the following articles related to Web3 and Blockchain: