Gas is used to pay for the computational resources needed to execute a Smart Contract. The more complex and computationally intensive your Smart Contract is, the more gas it uses and, therefore, the more it costs.
Additionally, gas fees are an incentive for Ethereum miners to add transactions to blockchain. When you send a transaction, setting a higher limit for gas usage increases the chances of your transaction being included in the next block.
However, estimating how much gas your Smart Contract will use is often challenging. The price of gas is determined based on many different factors, including supply, demand, and the wavering Ethereum price. So, it becomes particularly important to optimize Smart Contracts properly to reduce their gas consumption in the first place.
Experience an easier and faster way of building smart contracts with Tenderly DevNets. Spin up a managed, zero-setup development environment in seconds. Build on top of production data from 30+ networks with Tenderly debugging tools built in.
Try DevNetsSmart Contract gas estimation vs. optimization
Many Smart Contract engineers use different tools to estimate gas usage to avoid under or overpaying for gas. However, these estimates aren't always accurate since many varying factors affect the price of gas. For example, different block sizes, block times, block propagation times, or comparisons to recent blocks make it difficult to accurately estimate the possible minimum gas price for the following block.
This is why Smart Contract optimization is essential, allowing engineers to write and improve upon their code to reduce its gas usage. Ethereum gas optimization requires you to ensure your code executes as intended while consuming less computational resources to do so. And this becomes easier with Tenderly Gas Profiler.
How to optimize Smart Contract gas usage with Tenderly Gas Profiler
Gas Profiler provides a detailed overview of how much gas a transaction used upon running on-chain or off-chain. The feature uses flame graphs as a visual representation of gas consumption across the entire transaction. It then allows you to break its gas usage down to individual function calls and see which parts of your code used the most gas to execute.
You can access Gas Profiler easily by creating a Tenderly account, opening a transaction, and then clicking the Gas Profiler tab in the navigation bar.
Why use Gas Profiler?
Many gas profilers on the market are actually gas estimators, focusing primarily on displaying average gas consumption in Smart Contracts. This can be helpful, but in most cases, it doesn't provide the level of detail engineers need.
On the other hand, Tenderly Gas Profiler makes it easy for engineers to examine how much gas each called function spent for the execution of a transaction. It’s an essential tool for analyzing Smart Contracts and a great starting point for further code optimization. Gas Profiler allows you to understand gas usage on a high level and then deep dive into internal function executions for an in-depth insight.
How to optimize Smart Contract gas usage in Tenderly
After gathering the information you need in the Gas Profiler section, you can open the function you want to inspect in Tenderly Debugger by clicking the “View in Debugger” button.
Once in Debugger, you can further examine the line of code using Stack and Execution Traces for additional information. With such detailed input, you can then determine which code adjustments you can make to reduce gas consumption.
To assess if the spotted adjustments actually optimize the gas usage, you can use Tenderly Simulator. Before running the Simulation, you can edit the contract source to include these adjustments in the code and Simulate the execution with the changed source.
So, the next step is to click the Re-Simulate button. Once in the next view, click Edit Contract Source that will open a window where you can optimize the computationally demanding line of code.
Once you’ve made the needed edits, hit the Apply Changes button and then Simulate Transaction. You’ll get a new transaction output with updated gas usage. You can head back to Gas Profiler to see how much gas your updated code used and whether your optimization was successful.
What are some coding practices for gas optimization in Solidity?
After analyzing gas consumption using Gas Profiler and opening the Edit Contract Source view, you can optimize your Smart Contract code by implementing more efficient solutions.
Here are some of the best coding practices you can try:
1. Optimize Storage usage
The number of Storage slots and the ways you represent your data in your Smart Contract affects the gas usage heavily. Here are some recommendations:
- Limiting Storage use: Since Storage is the most expensive type of Memory, you need to use it as little as possible, which isn’t always easy. Use Storage only for the essential data, nothing more. Try reducing the usage of blockchain Storage. For instance, transient (non-permanent) data can be stored in Memory. Also, prevent Storage modifications by saving intermediate results in Memory or on a stack. Assign results to Storage variables only after all calculations have been completed.
- Packing variables: The minimum amount of Memory in Ethereum is a 256-bit slot. Even if slots aren’t full, you need to pay for an integer quantity. To avoid this, you can use the variable packing technique. The compiler will pack several consecutive variables whose total length is 256 or less into a single slot. Use this technique when defining structs as well. Also, declare the Storage variables of the same data type consecutively when defining them. The Solidity compiler will pack them automatically. However, note that this doesn’t apply to Memory and Calldata memories as their variables cannot be packed.
2. Optimize Memory usage
The choice of data types is another factor that determines the work the Ethereum Virtual Machine (EVM) does in the background to read, write, and operate on them. This directly contributes to gas usage, so choosing carefully will help optimize gas.
- Uint* vs. Uint256: The EVM performs operations in 256-bit chunks, so converting an uint* (unsigned integers smaller than 256 bits) to uint256 consumes additional gas. When packing more variables into a single slot, use unsigned integers that are less than or equal to 128 bits. If that’s not possible, use uint256 variables.
- Mapping vs. array: A list of data is only represented by two data types in Solidity: arrays and maps. Mappings are more efficient and less expensive, while arrays are iterable and packable. It's a good idea to use mappings to manage lists of data unless there's a need to iterate or it's possible to compress data types. This is beneficial for both Storage and Memory. Mapping may be used to manage an ordered list by providing an integer index as a key.
- Fixed size: Any fixed-size variable in Solidity is less expensive than a variable-size one. Use a fixed-size array instead of a dynamic one when possible.
- Default value: When variables are initialized, it’s a good practice to set their values, but this uses gas. In Solidity, all variables are set to 0 by default. Therefore, if a variable's default value is 0, don't explicitly set it to that value.
- Constants: For unchanging data, use constants over variables.
3. Optimize operations
Not all implementations of a solution are equally efficient in terms of gas usage. The way you decide to implement a function call or a complex logical expression can contribute to the amount of gas used. Here are some instances where a more optimal solution can reduce the gas used:
- Internal function calls: When you call a public function, it takes much longer than calling an internal one since all parameters are copied into the Memory. Instead, use internal function calls, where arguments are passed as references, whenever possible. They're somewhat more expensive to execute, but they save a lot of duplicate bytecode when used multiple times.
- Fewer functions: Try to keep the number of internal and private functions to a minimum to balance function complexity and quantity. In turn, this will help you reduce gas fees upon execution by reducing the number of function calls. However, keep in mind that having too large functions makes testing more difficult and even jeopardizes security. So, be careful not to reduce the number of functions too much.
- Short circuit: When it comes to logical expressions, try simplifying the complex ones as much as possible. Write them in such a way to minimize the chances of unnecessary evaluation of the second expression. Remember that when the first expression is true in a logical disjunction (OR/||), the second one won’t be performed. Also, keep in mind that if the first expression is evaluated to be false in a logical disjunction (AND/&), subsequent expressions won’t be evaluated.
- Limiting modifiers: The code of modifiers is placed within a modified function, which increases its size and gas usage. To avoid this, reduce the number of modifiers.
- Single line swap: In one instruction, you can exchange the values of two variables. Use: (a, b) = (b, a) instead of using an auxiliary variable for swapping.
4. Explore some general recommendations
In addition to implementing some specific solutions, you can also try to avoid some practices that can contribute to higher gas fees:
- Minimize direct access to Storage variables: Try accessing Storage variables through local (Memory and Calldata) variables. Instead of repeatedly reading/writing to a Storage variable, copy it to a local variable and use it instead. Write to the Storage variable only when the final result is calculated.
- Use loops as little as possible: Use loops in your Smart Contract only if absolutely necessary and unavoidable.
- Eliminate unnecessary computation: Try finding the most efficient algorithms to perform computations. Remove computations if your algorithm directly uses their results. In other words, eliminate unused calculations.
- Inefficient data structures: Try finding more efficient and suitable structures to represent your data. For example, favor mappings over arrays for direct indexing operations if you don’t need to iterate over the data in sequence.
5. Use less power-demanding solutions
There are a few other options that can help you optimize Smart Contracts and reduce their gas consumption. Consider using:
- Precompiled contracts where available.
- Optimized libraries.
- Compressible data structures.
- ZK-SNARKs to reduce the amount of data that needs to be stored and computed on-chain.
Smart Contract optimization starts with gas usage information
While there are different practices to optimize Smart Contracts, you first need an in-depth understanding of how your code uses gas. Tenderly Gas Profiler allows you to get into the intricacy of gas consumption by breaking it down granularly to single function calls. Once you’re powered with information and proper tooling, you can play around with Smart Contract optimization and find a solution that’ll help you reduce gas fees.