1. 기본적인 가스 절약 방법
- calldata로 memory를 교체
- memory에 상태변수 로딩
- for 루프의 i++ 를 ++i 로 변경
- 배열의 원소 caching
- short circuit (단락, 한 줄로 표현 가능한 경우 줄여서 쓴다)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract GasSaving {
uint public total;
// 가스 최적화를 안한 경우의 일반적인 표현
// function sumIfEvenAndLessThan99(uint[] memory nums) external {
// for (uint i = 0; i < nums.length; i += 1) {
// bool isEven = nums[i] % 2 == 0;
// bool isLessThan99 = nums[i] < 99;
// if (isEven && isLessThan99) {
// total += nums[i];
// }
// }
// }
/** 가스 최적화 */
// start : 50908 gas
// calldata 사용 : 49163 gas
// memory에 상태변수 로딩 : 48952 gas
// 하나의 실행 내에서 상태변수의 값이 여러번 변경될 경우 memory에서 실행하고 최종 결과만 상태변수에 저장한다
// short circuit - 48634 gas
// loop increments - 48244 gas
// cache array length - 48209 gas
// load array elements to memory - 48047 gas
// uncheck i overflow/underflow - 47309 gas
function sumIfEvenAndLessThan99(uint[] calldata nums) external {
uint _total = toal;
uint len = nums.length;
for( uint i = 0 ; i < len ; ){
uint num = nums[i];
if(num % 2 == 0 && num < 99){
_total += num;
}
unchecked {
++i;
}
}
total = _total;
}
}
2. 값이 변하지 않는 변수에 대해 constant 및 immutable 변수 사용
- constant 및 immutable 키워드를 사용하면 컴파일 될 때 storage 슬롯을 사용하지 않기 때문에 가스가 절약된다.
//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);
}
}
3. Cache read variables in memory
- 스토리지에 저장된 변수를 읽을 때, 처음에는 2100 가스가 필요하며, 이후부터는 100 가스가 소비된다.
- 예를 들어 for 반복문을 통해 배열의 원소에 접근하는 경우 배열의 길이를 변수에 별도로 저장하여 사용하는 것이 더 효과적이다.
- 또한 배열이 너무 크지 않다면, 전체 배열을 메모리로 읽고 메모리에서 엑세스하여 연산을 하는 방법도 있다.
//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;
}
}