Exaud Blog
Blog
How To Reduce Gas Fee On Blockchains
Software Developer Arthur Britto is ready to make anyone comfortable and pumped to learn about Blockchain Development. Posted onby ExaudSoftware Developer Arthur Britto is ready to make anyone comfortable and pumped to learn about Blockchain Development. If you’ve been curious about it but too scared to dive straight into it, you can start off by learning how to reduce gas fee on blockchains! Good luck, buddy!
(Hey! If you are familiar with solidity concepts, I suggest that you jump to the wrap-up on the last page).
Reduction of costs is an essential part of every process, to gain some advantages in the market. Therefore, if you’re developing Dapps (decentralized applications) on an EVM-compatible (Ethereum Virtual Machine) chain, you should always go in the direction of the Solidity gas reduction. This way, we could perform cheaper transactions, mitigating the overall cost.
What are Solidity Gas Costs?
If you’re familiar with blockchain development, you know that EVM is a global processor that a network of miners powers. The miners execute the work for us and create new blocks appending them to the blockchain. Essentially, the EVM network supplies computational power to execute smart contracts and on-chain transactions.
However, utilizing the computational power and capabilities of EVM doesn’t come free. Moreover, gas is also the system’s unit of measurement to keep track of how much computational power is used to execute a contract or function. Every blockchain transaction has a gas fee, which is the sum of almost every process inside of the smart contract. Saying that it’s possible to optimize gas fees by reducing the number and complexity of all blockchain interactions.
ou can see the miners as an Uber driver, if you have a distant trip to do with many stops and more complex requests, this will increase the price of the trip, increase the gas fee.
Determining structures that can be moved off-chain
Store on-chain only critical data for the Smart Contract and keep all possible data off-chain. When a Smart Contract holds a significant amount of data that must be updated, nevertheless all its data must be copied to the newly deployed Smart Contract, consuming a lot of gas. Keep the data in a separate Smart Contract, accessed by one or more Smart Contracts, using the data and holding the processing logic. If this logic must be updated, the data remains in the Data Contract. This pattern usually is included also in the implementations of the Proxy pattern.
Delete unnecessary variables or code
Limit the modifiers. Internal functions are not inlined but called separate functions. They are slightly more expensive at run time, but save a lot of redundant bytecode in deployment, if used more than once.
Solidity allows swapping the values of two variables in one instruction. So, instead of the classical swap using an auxiliary variable, use: (a, b) = (b, a).
Use global variable constants where applicable
If a global variable does not need to be changed throughout the life of the contract, think about using a constant instead. A constant variable does not increase gas costs, as it is not stored in the storage. It’s stored in the bytecode, which is a much smaller number. In terms of gas amounts, storage costs would be around 20000; whereas storage in the bytecode would be around 200.
Optimization at the cost of readability
Optimization is often at the expense of readability. In other words, you pay less for execution, but your code becomes slightly less readable.
keccak256 over any other hash function
The hash function that you decide to use doesmake a difference in the gas costs. Our hash function uses keccak256, which is the cheapest you can get.
Amount of Gas
– keccak256 (also known as sha3*, in Appendix G)
– 30 gas + 6 gas/word
– sha256 (see Appendix E, line 209)
– 60 gas + 12 gas/word
– ripemd (see Appendix E, line 212)
– 600 gas + 120 gas/word
Bytes32 over string/bytes
Keep constant strings short. Be sure that constant strings fit 32 bytes. For example, it is possible to clarify an error using a string; these messages, however, are included in the bytecode, so they must be kept short to avoid wasting memory.
Use solidity libraries for code reuse
If a Smart Contract tends to perform all its tasks by its own code, it will grow and be very expensive.
Use libraries. The bytecode of external libraries is not part of your Smart Contract, saving gas. However, calling them is costly and has security issues. Use libraries in a balanced way, for complex tasks.
Calls to libraries are made through delegate calls which means the libraries have access to the same data that the contract has and also the same permissions. This means that it’s not worth doing for simple tasks. Another thing to remember is that Solc (solidity compiler) inlines the internal functions of the library. Inlining has advantages of its own but it takes bytecode space.
Mappings are cheaper than Arrays, mostly.
Due to the way that EVM works, An array is not stored sequentially in memory but as a mapping. You can pack Arrays but not Mappings. So, it’s cheaper to use arrays if you are using smaller elements like uint8 which can be packed together. You can’t get the length of a mapping or parse through all its elements, so depending on your use case, you might be forced to use an Array even though it might cost you more gas.
Pack your variables when possible.
In Ethereum, the minimum unit of memory is a slot of 256 bits. You pay for
an integer number of slots even if they are not full.
Pack the variables. When declaring storage variables, the packable ones,
with the same data type, should be declared consecutively. In this way, the
packing is done automatically by the Solidity compiler. (Note that this
pattern does not work for Memory and Call data memories, whose variables
cannot be packed.)
Use unsigned integers smaller or equal to 128 bits when packing more variables in one slot (see Variables Packing pattern). If not, it is better to use uint256 variables.
uint8 uses more gas than uint256(solidity default), usually.
Boolean variables are stored as uint8 (unsigned integer of 8 bits). However, only 1 bit would be enough to store them. If you need up to 32 Booleans together, you can just follow the Packing Variables pattern. If you need more, you will use more slots than actually needed.
Pack Booleans in a single uint256 variable. For this purpose, create functions that pack and unpack the Booleans into and from a single variable. The cost of running these functions is cheaper than the cost of extra Storage.
Make fewer external calls or public calls, internal ones are the cheapest.
Limit external calls. In Solidity, differently than other programming languages, it is better to call a single, multi-purpose function with many parameters and get back the requested results, rather than making different calls for each data.
Calling public functions is more expensive than calling internal functions because in the former case all the parameters are copied into Memory. Whenever possible, prefer internal function calls, where the parameters are passed as references.
Refunds
Freeing up many storage slots in one transaction can quickly result in the refund counter surpassing half of the full transaction cost. In such a case or in case of self-destructs which can lead to a high refund counter as well we should evaluate how much of the refund can actually be used, as the refund can be at most half of the transaction cost. Thus freeing up storage slots or deleting contracts can make more sense in combination with other operations if possible.
Use proxy patterns for mass deployment.
Use Proxy delegate pattern. Proxy patterns are a set of Smart Contracts working together to facilitate the upgrading of Smart Contracts, despite their intrinsic immutability. A Proxy holds the addresses of referred Smart Contracts, in its state variables, which can be changed. In this way, only the references to the new Smart Contract must be updated.
Avoid repetitive checks.
Avoid redundant operations. For instance, avoid double checks; the use of the SafeMath library prevents underflow and overflow, so there is no need to check for them.
Avoid updating the variable value in each iteration of the function loop.
Storage is by far the most expensive kind of memory, so its usage should be minimized. Limit data stored in the blockchain, and always use memory for non-permanent data. Also, limit changes in storage: when executing functions, save the intermediate results in memory or stack and update the storage only at the end of all computations.
Write values instead of computing them. If you already know the value of some data at compile time, write directly these values. Do not use Solidity functions to derive the value of the data during their initialization. Doing so might lead to a less clear code, but it saves gas.
Correct Operator and Control Flow Choice
When using the logical operators, order the expressions to reduce the probability of evaluating the second expression. Remember that in the logical disjunction (OR, ||), if the first expression resolves to true, the second one will not be executed; or that in the logical disjunction (AND, &&), if the first expression is evaluated as false, the next one will not be evaluated.
Don’t initialize variables if they’ll have the default value (0, false).
It is good software engineering practice to initialize all variables when they are created. However, this costs gas in Ethereum, in Solidity, all variables are set to zeros by default. So, do not explicitly initialize a variable with its default value if it is zero.
Fixed-size variables are preferable.
In Solidity, any fixed-size variable is cheaper than variable size. Whenever it is possible to set an upper bound on the size of an array, use a fixed size array instead of a dynamic one.
Wrap-up reduction of gas price:
- Analyze your code and try to move data off-chain.
- Delete unnecessary variables or code.
- Use global variable constants where applicable.
- If some optimization makes your code unreadable, think twice.
- keccak256 over any other hash function.
- bytes32 over string/bytes.
- uint8 uses more gas than uint256(solidity default), usually.
- Use solidity libraries for code reuse.
- Mappings are cheaper than Arrays, mostly.
- Pack your variables when possible.
- Make fewer external calls or public calls, internal ones are the cheapest.
- Refunds.
- Use proxy patterns for mass deployment.
- Avoid repetitive checks.
- Avoid updating the variable value in each iteration of the function loop.
- Correct Operator and Control Flow Choice
- Don’t initialize variables if they’ll have the default value (0, false).
- Fixed-size variables are preferable.
Resources:
You can download the yellow paper here and the beige paper here (it’s a simplified version of the yellow paper, without the technical language)!
If you have any questions, send me an email: arthur.britto@exaud.com
Working at Exaud is more than just a job. Want to come along for the ride? We’re always looking for great people to join us. Check our current openings on our Careers Page.