블록체인

[ethereum] gas와 관련된 에러

멍개. 2022. 8. 28. 08:01

안녕하세요 멍개입니다. 이번글에서는 이더리움에서 트랜잭션 실행할 때 발생할 수 있는 gas 에러를 살펴보겠습니다.

▶ out of gas

▶ exceeds block gas limit

▶ gasLimit is too low

▶ base fee exceeds gas limit

· 네트워크 실행

$ ganache-cli

· out of gas

트랜잭션을 실행하여 상태를 변경할 때 실행된 코드에 따라 가스를 소모합니다. 이때 트랜잭션엔 어느정도 코드 실행을 할지 결정하는 gas를 설정할 수 있는데 설정된 값보다 더 많은 코드를 실행하려고 할 때 발생하는 에러입니다.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
 
contract Mung {
 string public text;
 constructor(string memory _text) {
   text = _text;
 }
  function setText(string memory _text) public{
   text = _text;
 }
  function say() public view returns(string memory){
   return text;
 }
 
 function errorOccur(uint a) public pure returns (uint) {
   require(a == 0, "hello world error");
   return a;
 }
}

해당 컨트랙트 코드를 배포하는 트랜잭션을 만들어보겠습니다.

const Web3 = require('web3');
let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

async function main() {
 const from = '0x42aBb1C1b24fC723634547AcC6fa2478fD0EBe44'; // ganache-cli에서 생성한 주소
 var mungContract = new web3.eth.Contract([
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "_text",
				"type": "string"
			}
		],
		"stateMutability": "nonpayable",
		"type": "constructor"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "a",
				"type": "uint256"
			}
		],
		"name": "errorOccur",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "pure",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "say",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "_text",
				"type": "string"
			}
		],
		"name": "setText",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "text",
		"outputs": [
			{
				"internalType": "string",
				"name": "",
				"type": "string"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
]); // remix에서 ABI 복사한 값을 인자로 전달
 var mung = mungContract.deploy({
     data: '',  // remix에서 bytecode.object 복사한 값
     arguments: ['test'] // 생성자에 전달할 값
 }).send({
     from: from,
     gas: '100000' 
   }, function (err, contract){
     console.log(err, contract);
 })
}

main();

 

여기서 data엔 컴파일 결과인 바이트 코드를 넣어줍니다. gas가 100,000으로 설정되어 있습니다. 해당 코드를 실행하면 다음과 같은 에러 메시지를 출력합니다.

Error: Returned error: VM Exception while processing transaction: out of gas

실행될 코드가 가스를 초과하서 발생한 에러입니다.

해당 컨트랙트 코드는 4,700,000 정도면 충분히 배포 가능합니다.

· Exceeds block gas limit

실행할 트랜잭션은 블록에 포함되어집니다. 이때 블록이 포함하고 있는 트랜잭션의 모든 가스합을 가지고 있는데 이 값이 특정값을 넘어가면 발생하는 에러가 Exceeds block gas limit 입니다. 앞의 코드에서 gas를 매우 높여서 실행하면 다음과 같은 에러가 발생합니다.

Error: Returned error: Exceeds block gas limit

 

· base fee exceeds gas limit

해당 에러는 스마트 컨트랙트를 배포하거나 실행하는 트랜잭션을 실행할 떄 최소 기준 가스보다 gas의 한도치를 너무 낮게 설정할 경우 발생하는 에러입니다.(이더리움을 전송하는 트랜잭션은 해당 에러가 발생하지 않습니다.)

Returned error: base fee exceeds gas limit

· gasLimit is too low

이더리움 전송 트랜잭션은 gasLimit이 21,000으로 고정되어 있습니다. 만약 해당 수치보다 낮은 수치로 트랜잭션을 발생하면 gasLimit is too low 에러가 발생합니다. 기준치는 초과하지만 21000보다 작을 때 해당 에러가 발생합니다.

const Web3 = require('web3');
let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

async function main() {
  const from = '0x42aBb1C1b24fC723634547AcC6fa2478fD0EBe44';
  const pk = '0xa960a13e39802345fb5c0e4bce81aed3e94ca4fccf3f17e148fd68beece8fe08' // from에 대응하는 privateKey를 넣어준다
  const to = '0x6c6117196b7c4D4986a1DA1234C003DaE54F44C9' 
  const tx = {
	from,
	to,
	gas: 21000, // gasLimit
	gasPrice: '21000000000', // 해당 값은 그대로 입력 합니다.
	value: '1000000000000000' // 원하는 이더전송 수량을 입력합니다.
  }
  const account = web3.eth.accounts.privateKeyToAccount(pk) // 개인키를 account 객체로 복구
  const signedTx = await account.signTransaction(tx) // 개인키로 트랜잭션 서명
  const sentTx = await web3.eth.sendSignedTransaction(
	signedTx.raw || signedTx.rawTransaction
  ); // 서명된 트랜잭션 이더리움 노드로 전송
  console.log(sentTx);
}
main();

from, pk, to는 적절히 넣어줍니다. 해당 코드를 실행하면 정상적으로 동작합니다. gas를 다음과 같이 수정한 후 실행하면 에러가 발생합니다.

 
  const tx = {
    from,
    to,
    gas: 2100, // 해당 값은 그대로 입력 합니다.
    gasPrice: '21000000000', // 해당 값은 그대로 입력 합니다.
    value: '1000000000000000' // 원하는 이더전송 수량을 입력합니다.
  }​
Error: Signer Error: Signer Error:  gasLimit is too low. given 2100, need at least 21000.

두 번째로 스마트 컨트랙트를 호출 할 때 21,000보다 낮게 설정해도 해당 에러가 발생합니다.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;
 
contract Mung {
 string public text;
 constructor(string memory _text) {
   text = _text;
 }
  function setText(string memory _text) public{
   text = _text;
 }
  function say() public view returns(string memory){
   return text;
 }
 
 function errorOccur(uint a) public pure returns (uint) {
   require(a == 0, "hello world error");
   return a;
 }
}
const Web3 = require('web3');
let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
async function main() {
  const CA = "0xaF3DE8794C26363c04A313Bc0cf8A01915017B21" // 배포한 스마트 컨트랙트 주소
  const from = '0x42aBb1C1b24fC723634547AcC6fa2478fD0EBe44';
  const pk = '0xa960a13e39802345fb5c0e4bce81aed3e94ca4fccf3f17e148fd68beece8fe08' // from에 대응하는 privateKey를 넣어준다어준다
  const ABI = [
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_text",
          "type": "string"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "a",
          "type": "uint256"
        }
      ],
      "name": "errorOccur",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "pure",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "say",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "string",
          "name": "_text",
          "type": "string"
        }
      ],
      "name": "setText",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [],
      "name": "text",
      "outputs": [
        {
          "internalType": "string",
          "name": "",
          "type": "string"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    }
  ]; // remix 또는 트러플 컴파일 결과에서 ABI 복사하여 넣어준다.
 
  // 스마트 컨트랙트 객체 생성
  let Contract = new web3.eth.Contract(ABI, CA); 
 
  // 스마트 컨트랙트에 정의한 함수 실행
  let bytedata = await Contract.methods.setText("test").encodeABI(); 
  console.log(bytedata);
 
  const tx = {
     from,
     to: CA,
     gas: 1000, // 해당 수치는 그대로 입력합니다.
     gasPrice: '21000000000', // 해당 수치는 그대로 입력합니다.
     data: bytedata
  }
 
  const account = web3.eth.accounts.privateKeyToAccount(pk)
  const signedTx = await account.signTransaction(tx)
  const sentTx = await web3.eth.sendSignedTransaction(signedTx.raw || signedTx.rawTransaction);
  console.log(sentTx);
}
 
main();

gas를 1000으로 매우 낮게 주었습니다.

Error: Signer Error: Signer Error:  gasLimit is too low. given 1000, need at least 21520.

이더리움 전송할때와 유사한 에러를 출력합니다. 여시거 21520이 필요하다고 하는데 제가 찾은 최소값은 26788입니다.