Gas Optimization Guide for Smart Contracts
Effectively managing gas consumption is a critical aspect of smart contract development. This guide explores techniques and practical strategies for optimizing gas consumption, helping developers minimize costs and improve efficiency on the TRON blockchain.
1. Optimization on Variables and Storage Arrangement
1.1 Reduce State Variable Usage
State variables are stored on-chain and therefore require Energy for every modification.
Define variables that are required only for function execution as memory variables or temporary variables on the stack to reduce Energy consumption caused by read/write (R/W) operations on state variables.
1.2 Arrange Storage based on Variable Types and Sizes
In theory, Solidity variables like unit8
and unit16
can consume less storage when packed. However, when used as individual variables, they occupy almost the same storage slot size as a unit256
variable.
Therefore, packing multiple smaller variable types into a single slot can effectively save storage space. For example, you can pack multiple unit128
and uint64
variables into the same slot following the “Place variables of the same type together in a wide-to-narrow sequence” principle.
1.3 Constant and Immutable Variables
Constant Variable: This type of variables is determined during compilation, embedded within the bytecode, and remains immutable post-deployment. Reading constants does not consume additional energy associated with storage retrieval.
uint256 constant FEE_RATE = 10;
Immutable Variable: This type of variables can be configured in construction functions and stay immutable once the contract is deployed. Immutable variables occupy less storage space than regular state variables, and reading immutable variables does not consume additional energy associated with storage retrieval.
uint256 immutable START_TIME;
constructor(uint256 _start) {
START_TIME = _start;
}
Set variables that should remain unchanged to constant
or immutable
to minimize reading consumption.
1.4 Reduce Redundant Reads of Variables
In Solidity, each read operation on state variables consumes Energy. To optimize, assign the value to a local variable (stored on the stack or in memory) and reuse this local variable in your logic to minimize redundant state reads.
uint256 value = stateVar;
// For later operations, keep using `value` instead of `stateVar`
2. Optimization on Logic and Function Calls
2.1 Short-Circuit
In the case of conditional statements, such as if
or require
, arrange boolean expressions so that conditions that are most likely false/true are placed first to shorten the execution.
// Assume that conditionA is more likely to be false
require(conditionA && conditionB, "Failed");
// When A is false, the fail result is returned. The computation on B is bypassed.
2.2 Minimize Redundant Operations in Loops
In loops (e.g., for
/while
), if complex logics or R/W operations on state variables are involved, Energy consumption can soar with each iteration.
Where feasible, limit the number of loop iterations to an acceptable range or reduce the cost per iteration through batched operations and Events
.
When accessing state variables within a loop, it is advisable to retrieve them once before the loop, store and modify them in memory variables during the loop, and then write the final values back to state storage after the loop completes.
2.3 Minimize Function Call Depth
Function calls themselves incur invocation gas costs. Inlining simple logic can reduce Energy consumption to some extent, but readability and maintainability must also be carefully considered.
Internal vs. Public/External Functions: Internal function calls typically incur lower costs than external function calls. Therefore, declare functions that are used exclusively within a contract as internal
.
2.4 Use calldata instead of memory
Declare external function parameters that are only read within the function body as calldata
to minimize copy operations and therefore reduce Energy costs for memory allocation.
function processData(uint256[] calldata data) external {
// Here, `data` is stored as calldata instead of memory
}
3. Optimization on Data Structures and Access Methods
3.1 Choose Suitable Data Structures
Mapping vs. Array: If the data can be queried by key instead of traversal, mappings are generally more efficient than arrays. However, if extensive data traversal is required, arrays allow sequential access, whereas mappings cannot be directly traversed.
Static vs. Dynamic Array: Static arrays consume less storage and Energy resources. However, the choice of array types should be based on your business requirements.
3.2 Minimize Storage Operations
If massive R/W operations to storage are needed within a contract, load the data in memory or the stack before the logic starts, and write the results to storage once processing is complete.
Implicit R/W operations can occur in the function body if reference types (e.g., dynamic arrays or structs) are conveyed as function parameters. In this case, determine whether to declare the reference types as memory
or storage
.
3.3 Use Events for Some Storage Scenarios
Data that only needs to be recorded or tracked can be covered in events for logging instead of being stored on-chain, especially for data not required by the contract logic in the following procedure. This method can dramatically cut storage costs.
For example, for a batch of user activity results, as long as these results are not required for later determinations in the smart contract, using events can reduce massive Energy consumption.
4. Advanced Optimization Methods
4.1 Inline Assembly
In scenarios where extremely high performance and massive Energy resources are required, assembly can be used to optimize logics such as batch processing, mathematical operations, or direct operations on storage slots.
However, readability, maintainability, and security must be prioritized, and developers should have a comprehensive understanding of the underlying logic of the Ethereum Virtual Machine (EVM).
4.2 Use Libraries instead of Inheritance or Repetition
This applies to features or modules that are generally and frequently called. Using libraries can reduce function duplication and minimize bytecode size.
However, costs of deploying and calling the libraries should be evaluated according to your actual needs.
4.3 Manage Contract Sizes
For contract deployment, the larger the bytecode size, the higher the Energy consumption. If a contract contains excessively redundant logic or resources, you can split the contract or use libraries to keep the contract in a smaller size.
For an enormously large contract, you can use upgradeable or proxy patterns to partition the logic into multiple segments for deployment, thereby reducing the size of individual contracts.
4.4 Moderate Use of Proxy to Reduce Energy Consumption of Application Iterations
If your application requirements change frequently, adopting an upgradable proxy pattern allows you to retain data and avoid redeploying massive logic code, thereby minimizing the cost of redeployment.
In this case, the storage layout must be carefully designed to prevent conflicts or disorders during upgrades.