
- Select a language for the TTS:
- UK English Female
- UK English Male
- US English Female
- US English Male
- Australian Female
- Australian Male
- Language selected: (auto detect) - EN
Play all audios:
Gas costs play a significant role in the long run for each Ethereum-based Dapp. We share several hands-on techniques, by example, to help your Dapps cut the associated gas costs. We would
love to hear your feedback if any. TERMS we use in the article: * SC: Smart Contract * GC: Gas Cost * SOLC: Solidity Compiler * EVM: Ethereum Virtual Machine OVERALL APPROACH:
_Feedback-based solution._ We tried to optimize the design and implementation of our SC the way it is expected from SOLC and EVM. We verified the ideas by performing GC regression tests and
apply the feedback back to the design and implementation. TECHNIQUES we used to cut gas costs (_note that we use SOLC version 0.4.21 for discussion purpose)_: * _BIT-COMPACTION_
Bit-compaction format for external function parameters helps cut gas costs because it minimizes the amount of input data sent to the Ethereum blockchain. Please note that it introduces
certain small extra gas costs to unpack the data. However, the savings usually weigh out the extra costs. Take the following code snippet for example: _pragma solidity ^0.4.21;__contract
bitCompaction { function oldExam(uint64 a, uint64 b, uint64 c, uint64 d) public { } function newExam(uint256 packed) public { } }_ Its corresponding assembly code shows that the
oldExam function incurs 4 `_CALLDATALOAD`_ operations once it is called, and each `CALLDATALOAD` operation triggers one memory allocation operation in Ethereum, while the newExam function
incurs only 1 instead. Note that the assembly code is too large for us to present here, but one can easily generate it by performing (for example) `solc — asm — optimize — optimize-runs 200
bitCompaction.sol`. In the experiment, we have the following call data for each function: oldExam call data: ([]uint8) (len=132 cap=132) { 00000000 3e f2 62 fd 00 00 00 00 00 00 00 00 00
00 00 00 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000020 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000040 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000060 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00000070 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00000080 00 00 00 01 }newExam call data: ([]uint8) (len=36 cap=36) { 00000000 83 ba 6e 5a 00 00 00 00 00 00 00 01 00 00 00 00 00000010 00 00
00 01 00 00 00 00 00 00 00 01 00 00 00 00 00000020 00 00 00 01 } As we can see, those uint64 parameters in the oldExam function are first converted to uint256 ones once being called. Gas
costs for the above two are 22235 and 21816 respectively, meaning the newExam function saves 419 gas costs. The more scattered the parameters are, the more savings you cut by adopting the
bit compaction method. * _BATCHING_ Batching cuts gas cost because it reduces common data processing. Take the following code snippet for example: _Old: func once(uint256 header, uint256
val...) x N New: func batch(uint256 header, uint256[] val... x N)_ Executing the old function N times processes the common `header` field N times, as well as calling the function N times.
However, by batching, the function is called only one time and the common `header` field is processed only one time. Therefore, it saves gas costs by reducing `CALLDATALOAD`, memory
allocation, and function call operations. The larger the N, the larger the savings by adopting the batching mechanism. * _SEPARATING WRITES TO STORAGE STRUCT_ Separating writes to struct
object that is defined in storage, cuts gas cost in many cases. Take the following code snippet for example: _pragma solidity ^0.4.21;__contract structWrite { struct Object { uint64
v1; uint64 v2; uint64 v3; uint64 v4; }__ Object obj;__ function StructWrite() public { obj.v1 = 1; obj.v2 = 1; obj.v3 = 1; obj.v4 = 1; }__ function
oldExam(uint64 a, uint64 b) public { uint a0; uint a1; uint a2; uint a3; uint a4; uint a5; uint a6; uint b0; uint b1; uint b2; uint b3; uint b4; uint b5;__ obj.v1 = a + b;
obj.v2 = a - b; obj.v3 = a * b; obj.v4 = a / (b + 1); }__ function setObject(uint64 v1, uint64 v2, uint64 v3, uint64 v4) private { obj.v1 = v1; obj.v2 = v2; obj.v3 =
v3; obj.v4 = v4; }__ function newExam(uint64 a, uint64 b) public { uint a0; uint a1; uint a2; uint a3; uint a4; uint b0; uint b1; uint b2; uint b3; setObject(a + b, a - b,
a * b, a / (b + 1)); } }_ Once the optimize option is turned on, for the above example, SOLC compiles storage struct writes in such a way that the oldExam function would incur 1 EVM
`SSTORE` operation (`SSTORE` is the most gas costly operation) for each of the struct field in Object since the current implementation of SOLC could not optimize the `SSTORE` operations when
there is not enough stack space (there is stack space for only 16 local variables). However, with the new approach, it has ample stack space such that the current compiler can optimize the
struct accesses by performing, in total, only 1 `SSTORE` operation after merging writes to the struct. This can be easily verified against by looking at the corresponding assembly code. This
is restricted to the current implementation of SOLC, and is subject to change in the future. For the above example, the gas costs are 58140 and 27318 respectively, indicating a gas cost
saving of 30822 for the newExam function. When the stack space is not enough, the more fields the struct has, the more gas cut you achieve if the new approach is leveraged. * _UINT256 AND
DIRECT MEMORY ACCESS_ Calculation unit in SOLC is uint256. Hence other types (e.g., uint8) require type conversions before calculations are applied. This incurs extra gas costs. Moreover,
direct memory access is more GC-efficient than direct storage access, and more GC-efficient than struct pointer based access. These smaller tricks can be represented as: _uint8 data;
__=> __ uint256 data;__uint256 val = storageData; => uint256 memoryData = storageData; (N Times) uint256 val = memoryData;__uint64 val =
obj.v1; __=> __ uint64 val = val1;_ * _ASSEMBLY OPTIMIZATION_ When compiling your SOLC code, make sure to perform GC experiments with the SOLC `optimize — runs` option to figure
out the best GC-efficient assembly code to run in EVM. CITATION @article{li2018gassaving, title = "Techniques to Cut Gas Costs for Your Dapps"", author = "Li,
Zhichao", journal = "medium.com", year = "2018", month = "June", url =
"https://medium.com/coinmonks/techniques-to-cut-gas-costs-for-your-dapps-7e8628c56fc9" } Or: Li, Zhichao. (Jun 2018). "Techniques to Cut Gas Costs for Your Dapps".
Medium.com. https://medium.com/coinmonks/techniques-to-cut-gas-costs-for-your-dapps-7e8628c56fc9. REFERENCE * Gas Costs from Yellow Paper. _LINK_.