How to optimise gas fees in Solidity Smart Contract
This guide provides a practical, step-by-step walkthrough on how to optimize gas costs when writing smart contracts in Solidity.
Why Does Gas Optimization Matter?
Gas optimization is a critical part of smart contract development. It helps ensure that smart contracts remain efficient and cost-effective, even under high network congestion. By reducing the computational overhead of contract execution, developers can lower transaction fees, speed up confirmation times, and improve the overall scalability of their dApps.
For developers, gas optimization is about writing clean, secure, and predictable code that minimizes unnecessary computations. For users, it's about ensuring they can interact with your contract without paying excessive fees.
Why It’s Especially Important on Kaia
With over 83 Mini dApps launched and counting, the Kaia blockchain has emerged as the leading EVM-compatible chain in transaction volume driven largely by the explosive growth of these onchain applications.
Each Mini dApp relies on smart contracts to perform onchain actions. Whether minting items, placing bets, or managing in-game assets, every contract interaction consumes gas. Without optimization, these dApps could quickly become too expensive for users to interact with — especially at scale.
That’s why gas efficiency isn’t just a nice-to-have. It’s a necessity. Developers building on Kaia must ensure that each function call is optimized to minimize cost while preserving functionality and security.
Gas Optimization Techniques
Storage Packing
Storing and retrieving data on the blockchain is one of the most gas-expensive operations, especially when the data must persist across transactions and blocks. In Solidity, this data is stored in contract storage, which is permanent and incurs gas costs. To reduce these costs, developers must carefully optimize how storage is used, especially when declaring state variables.
The Kaia Virtual Machine (KVM) stores contract data in units called storage slots. Each storage slot can hold exactly 256 bits (32 bytes) of data. Solidity data types come in various sizes — for example, a bool is 1 byte, and an address is 20 bytes.
Through a technique known as storage packing, we can tightly arrange smaller variables to fit within a single 32-byte storage slot. This helps reduce gas usage because reading from or writing to one storage slot is significantly cheaper than accessing multiple.
Let’s consider the following example:
Breakdown:
In the unoptimized version (SlotUnOptimized), Solidity stores the struct like this:
- address to -> takes 20 bytes -> stored in slot 0
- uint256 numConfirmations -> takes 32 bytes -> stored in slot 1
- uint80 value (10 bytes) and bool executed (1 byte) -> stored in slot 2
Despite value and executed variable being small, Solidity places them in their own storage slot due to alignment padding unless explicitly reordered. As a result, this struct uses 3 storage slots, which means 3x the gas cost for storage operations. However, the total size of address (20 bytes) + uint80 (10 bytes) + bool (1 byte)
is 31 bytes and falls within the 32-byte limit of a single slot. By simply reordering the declarations so that smaller variables are grouped together, Solidity can pack them into the same slot. This is the essence of storage packing.
In the optimized version (SlotOptimized) as seen above, all the smaller variables are placed adjacent to each other, allowing the compiler to store them in fewer slots — reducing deployment and runtime gas costs.
Cache Storage
Beyond how variables are laid out in storage slots, it’s also important to understand the gas cost associated with accessing and modifying storage.
Each storage slot on the Kaia Virtual Machine costs: 20,000 gas to initialize (first write) 5,000 gas to update (subsequent writes) Because of this, it’s critical to minimize the number of direct storage reads and writes, especially in functions that get called frequently. One effective pattern is to cache storage variables into memory when you need to access them multiple times within a function.
Let’s consider the following example: