Solidity의 가스 절약 기술
Gas
은 Solidity의 계산 측정 단위입니다. 스마트 계약과 상호 작용하고 거래하기 위해 지불하는 금액입니다. gas
은 다음과 같은 간단한 공식을 통해 계산됩니다.Gas = Gas Price * Gas used
.gas
가격은 일정하지 않으며 네트워크 정체에 따라 달라집니다. 네트워크를 통해 거래를 추진하는 사용자가 많을수록 gas
가격이 높아집니다. 사용된 gas
의 양은 스마트 컨트랙트에 의해 수행되는 연산에 의해 결정됩니다.이 블로그 게시물에서는 트랜잭션을 스마트 계약으로 보낼 때 소비되는
gas
을 줄이기 위해 사용할 수 있는 몇 가지 기술에 대해 설명합니다. 우리는 스마트 계약의 gas
풋 프린트를 줄이기 위해 다음 나열된 기술을 고려할 것입니다.Use constant and immutable variables for variable that don't change
Cache read variables in memory
Incrementing and decrementing by 1
calldata and memory
Calling view function
Integer overflow/underflow
Use revert instead of require
Avoid loading too many data in memory
Other tips
변경되지 않는 변수에 대해 상수 및 불변 변수 사용
Using the constant
and the immutable
keywords for variables that do not change helps to save on gas
used. The reason been that constant
and immutable
variables do not occupy a storage slot when compiled. They are saved inside the contract byte code. Lets see an example.
//SPDX-License-Identifier:MIT
pragma solidity ^0.8.3;
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
//rest of the interface code
}
//Gas used: 187302
contract Expensive {
IERC20 public token;
uint256 public timeStamp = 566777;
constructor(address token_address) {
token = IERC20();
}
}
//Gas used: 142890
contract GasSaver {
IERC20 public immutable i_token;
uint256 public constant TIMESTAMP = 566777;
constructor(address token_address) {
i_token = IERC20(token_address);
}
}
The first contract Expensive
has a public variable of type IERC20
and a uint256
also defined as public. When we deployed this contract, the contract deployment used up a total of 187302 gas
when deployed to Remix. The second contract named GasSaver
also have the same variable structure as the Expensive
contract but we made use of constant
keyword for the TIMESTAMP
because we will not be changing the value. For the interface
we used the immutable
keyword which means we can no longer modify the value of the i_token
variable after setting it inside the constructor
. This action alone reduced the gas consumption from 187302 to 142890.
메모리의 캐시 읽기 변수
Reading from a variable in storage cost gas
. When we access a variable for the first time it cost 2100 gas
and subsequent access will cost 100 gas
. If we have an array and want to perform some computation on the array elements. It could be more gas effective to read the length
of the array and store it in a variable. If the array is not too large we could also read the whole array
into memory and access it from memory instead of continuously reading from storage.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract Expensive {
uint256[] public numbers;
constructor(uint256[] memory _numbers) {
numbers = _numbers;
}
//Gas used: 40146
function sum() public view returns (uint256 ){
uint256 total = 0;
for (uint256 i=0; i < numbers.length; i++) {
total += numbers[i];
}
return total;
}
}
contract GasSaver {
uint256[] public numbers;
constructor(uint256[] memory _numbers) {
numbers = _numbers;
}
//39434
function sum() public view returns (uint256 ){
uint256 total = 0;
uint256 arrLength = numbers.length;
uint256[] memory _numbersInMemory = numbers;
for (uint256 i=0; i < arrLength; i++) {
total += _numbersInMemory[i];
}
return total;
}
}
The contract GasSaver
above saved the length of the array in a variable arrLength
and also loaded the array into memory. This helps to reduce the gas
spent inside the function.
1씩 증가 및 감소
There are four ways to increment and decrement a variable by 1 in Solidity. Let us see an example.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract One{
uint256 public number;
//Gas used : 43800
function incrementByOne() public returns (uint256){
number += 1;
return number;
}
}
contract Two{
uint256 public number;
//Gas used : 43787
function incrementByOne() public returns (uint256){
number = number + 1;
return number;
}
}
contract Three{
uint256 public number;
//Gas used : 43634
function incrementByOne() public returns (uint256){
return number++;
}
}
contract Four{
uint256 public number;
//Gas used : 43628
function incrementByOne() public returns (uint256){
return ++number;
}
}
The example above list four contracts which shows the way we could increment a number variable by a value of 1. The contract Four
is more gas effective out of the lot when ran. To save more on gas
when incrementing a variable use the method of increment in contract Four
( ++number
) which is more gas
efficient.
호출 데이터 및 메모리
When running a function we could pass the function parameters as calldata
or memory
for variables such as strings
, structs
, arrays
etc. If we are not modifying the passed parameter we should pass it as calldata
because calldata
is more gas
efficient than memory
. Let's see an example:
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract GasSaver {
//Gas used: 22471
function passParameterAsCallData(string calldata _name) public returns (string memory){}
//Gas used: 22913
function passParameterAsMemory(string memory _name) public returns (string memory){}
}
Calling these two functions in Remix and passing the same parameter to the functions, you will noticed that setting a function _name
parameter as calldata
reduced the gas
used.
You can only used
calldata
when you are not going to modify the value of the variable passed ascalldata
inside the function.
뷰 함수 호출
A view
function does not use gas
when called, but if we decide to call a view function inside of another function which is a transaction, it then uses gas
.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract GasSaver {
uint256[] private numbers = [2,3,5,67,34];
function getNumberAt( uint256 _index ) public view returns (uint256){
return numbers[_index];
}
//Gas used when getNumber was called: 44778
//Gas used without calling getNumber() 44450
function sumAndMultiply() public {
uint256[] memory _numbers = numbers;
uint256 arrlength = _numbers.length;
for(uint256 i=0; i < arrlength; ++i){
numbers[i] = _numbers[i] * i;
}
//getNumberAt(2);
}
}
The above is a simple contract to illustrate the point about calling view
functions inside of a transaction. The function getNumberAt
is a view
function that returns the value of the array at the passed index. Calling this function uses no gas
because it is a view function, but if we decide to call this function inside of the sumAndMultiply
function, we can see that the gas
usage of sumAndMultiply
has increased because the view
function is called within it.
Gas
used when sumAndMutiply
functions is called: 44778
.
Gas
used when getNumberAt
view
function is also called inside the sumAndMultiply
function: 44450
.
So, we can see that calling a view
function inside a transaction increased the amount of gas
that should have been spent, if the view
function was not included in the call.
정수 오버플로우/언더플로우
Prior to Solidity 0.8.0
version, interger overflow and underflow checks were performed by using the SafeMath
library. From Solidity 0.8.0
upward, the compiler does that check for us.
This extra check cost gas
. If we know that the mathematical operations we will perform inside the contract will not overflow or underflow, we can tell the compiler not to check for overflow or underflow in the operation.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract OverFlow {
uint256 public numberOne;
uint256 public numberTwo;
//Gas used: 43440
function setNumberOne() public {
++numberOne;
}
//Gas used: 43339
function setNumberTwo() public {
unchecked {
++numberTwo;
}
}
}
The above contract OverFlow
has two functions used to set two variables inside the contract. The compiler in setNumberOne
checks and prevents the number from overflow or underflow. In the second function setNumberTwo
we are telling the compiler to mind its business and not check for overflow or underflow. Using unchecked
means that the code block will not be checked for overflow or underflow.
If we know that our mathematics will be safe we could safe a little gas by using unchecked
.
요구 대신 되돌리기 사용
We use require
to check if a statement is true
. If the statement evaluates to false the transaction is reverted and the remaining gas returned to the user. Since the advert of custom error in Solidity we could use the revert
statement to throw a custom error. Using revert
instead of require
is more gas efficient. You can run the code below to see the the amount of gas
saved when revert
is used instead of require
.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
error NotEnough();
contract GasSaver {
uint256 public number;
//Gas Used: 21898
function setNumber(uint256 _number) public {
require(_number > 10, "number too small please");
number = _number;
}
//Gas Used: 21669
function setNumberAndRevert(uint256 _number) public {
if ( _number < 10 ){
revert NotEnough();
}
number = _number;
}
}
메모리에 너무 많은 데이터를 로드하지 마십시오.
Loading data in memory in a single transaction has the effect of increasing gas
used. The gas
used increase in a quadratic factor when more than 32kb
of memory is used in a single transaction. Let's see an example.
//SPDX-License-Identifier:MIT;
pragma solidity ^0.8.3;
contract One {
//Gas used: 29261
function setArrayInMemory() public pure {
uint256[1000] memory _array;
}
}
contract Two {
//Gas used: 276761
function setArrayInMemory() public pure {
uint256[10000] memory _array;
}
}
//Gas used: 922855
contract Three {
function setArrayInMemory() public pure {
uint256[20000] memory _array;
}
}
contract Four {
//Gas used: 3386918
function setArrayInMemory() public pure {
uint256[40000] memory _array;
}
}
The above example defined four contracts and each contract has a function named setArrayInMemory
where memory space is reserved for an array of uint256
.
Contract One
loads reserves space in memory for 1000 uint256
numbers and when the function is executed it uses up 29261 gas
. Contract Two
creates memory space for 10000 uint256
numbers and we can see that the gas
used is 276761 which is roughly about 10 times of the gas
used by Contract One
.
Things gets interesting in Contract Three
which reserves space for 20000 uint256
numbers. From the gas
used which is 922855, we can see that once the memory used in a single transaction exceeds 32 kilobyte, the quadratic factor of the memory expansion cost kicks in thereby increasing the gas
used in a quadratic way.
Do not populate arrays with enormous amounts of items in a single transaction to prevent
gas
used increase in a quadratic way.
기타 팁
Always put your
require
statement on top in the function before making any state change so that if therequire
statement fails, the remaininggas
is refunded to the user on revert.When comparing operation using
&&
or||
with arequire
statement; you should put the cheaper part of the operation first so that if it fails, the compiler will not compare the second part thereby saving ongas
used.
In conclusion We can use a combination of these techniques highlighted here to improve the gas
consumption of our smart contracts thereby saving our users from spending too much on gas
when interacting with our smart contracts.
Thanks for reading.
Reference
이 문제에 관하여(Solidity의 가스 절약 기술), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jamiescript/gas-saving-techniques-in-solidity-324c텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)