블록체인 학습
0 XP

Chapter 5: 함수 기초

스크립트로 컨트랙트 호출하기

스크립트로 컨트랙트 호출하기

Chapter 5 에서 view 함수를 배울 때 "상태를 읽기만 하므로 가스가 들지 않는다" 고 했습니다. 교재의 문장으로만 읽으면 잘 와닿지 않는 이야기입니다. 이번 랩에서 그 차이를 직접 눈으로 확인합니다.

같은 컨트랙트의 함수를 두 종류로 나누어 호출해 보면, 반환값·소요 시간·가스·노드 로그가 전부 다르게 나타납니다.

앞 랩에서 띄운 로컬 노드가 계속 켜져 있어야 합니다. 노드를 끄면 배포된 컨트랙트도 사라지므로, 껐다면 npx hardhat node 를 다시 실행하고 배포부터 새로 해야 합니다.

1단계 — 배포된 컨트랙트에 연결

scripts/interact.js 파일을 만듭니다. 앞 랩에서 메모해 둔 주소를 ADDRESS 에 붙여 넣으세요.

// scripts/interact.js
import { network } from 'hardhat'
 
const { ethers } = await network.getOrCreate()
 
// 앞 랩에서 출력된 컨트랙트 주소로 바꾸세요
const ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'
 
// 이미 배포된 컨트랙트에 연결한다 (새로 배포하는 것이 아니다)
const counter = await ethers.getContractAt('Counter', ADDRESS)
console.log('연결 완료:', await counter.getAddress())

getContractAt 은 배포를 하지 않습니다. "이 주소에 이 ABI 를 가진 컨트랙트가 있다" 고 알려 주어, 자바스크립트 쪽에 함수 호출용 손잡이를 만들어 줄 뿐입니다.

2단계 — view 함수 호출

스크립트 아래에 이어서 작성합니다.

// view 함수 — 결과가 곧바로 반환된다
const before = await counter.get()
console.log('현재 count:', before)

실행합니다.

npx hardhat run scripts/interact.js --network localhost

현재 count: 10n 이 즉시 출력됩니다. 두 가지를 확인하세요.

  1. 값이 바로 돌아온다. 기다림이 없습니다.
  2. 노드 터미널에 로그는 남지만 블록은 생기지 않는다.

노드 터미널을 보면 이렇게 찍혀 있습니다.

eth_call
  Contract call:       Counter#get
  From:                0xf39f...2266
  To:                  0x5fbd...0aa3

Contract call 이라는 줄이 있어서 트랜잭션처럼 보이지만, Block # 도 Gas used 도 없습니다. view 함수 호출은 블록체인에 무언가를 쓰는 행위가 아니라, 노드에 "지금 이 값이 뭐야?" 하고 물어보는 조회이기 때문입니다. 블록에 담기지 않으니 가스도 들지 않고, 서명할 필요도 없습니다. 이 요청을 eth_call 이라 합니다.

3단계 — 상태 변경 함수 호출

이번에는 상태를 바꾸는 increase 를 부릅니다.

// 상태 변경 함수 — 트랜잭션이 만들어진다
const tx = await counter.increase(5n)
console.log('트랜잭션 해시:', tx.hash)
 
// 블록에 담길 때까지 기다린다
const receipt = await tx.wait()
console.log('블록 번호:', receipt.blockNumber)
console.log('사용한 가스:', receipt.gasUsed)

여기서 await counter.increase(5n) 가 반환하는 것은 결과값이 아니라 트랜잭션 객체입니다. 이 시점에는 아직 아무것도 확정되지 않았습니다. 트랜잭션이 네트워크에 전송되었을 뿐입니다.

await tx.wait() 를 빠뜨리면, 트랜잭션이 아직 블록에 담기기 전에 다음 줄이 실행됩니다. 바로 이어서 값을 조회하면 바뀌기 전 값이 나올 수 있습니다. 상태 변경 뒤에 결과를 확인해야 한다면 wait() 는 선택이 아니라 필수입니다.

wait() 가 돌려주는 receipt 에는 실제로 벌어진 일의 기록이 담깁니다. 몇 번 블록에 들어갔는지, 가스를 얼마나 썼는지가 여기에 있습니다.

4단계 — 다시 조회해서 확인

const after = await counter.get()
console.log('바뀐 count:', after)
console.log('증가량:', after - before)

10n 이었던 값이 15n 이 되어 있어야 합니다. bigint 끼리는 - 연산이 그대로 되므로 차이도 바로 계산됩니다.

bigint 는 일반 number 와 섞어서 계산할 수 없습니다. after - 10 은 오류가 나고 after - 10n 은 정상입니다. ethers.js v6 가 반환하는 정수는 모두 bigint 라는 점을 기억하세요.

5단계 — 두 호출의 차이 정리

스크립트를 다시 실행한 뒤 노드 터미널을 확인하면, 두 호출이 서로 다른 모양으로 남아 있습니다.

eth_call
  Contract call:       Counter#get
  From:                0xf39f...2266
  To:                  0x5fbd...0aa3
 
eth_sendTransaction
  Contract call:       Counter#increase
  Transaction:         0x48ab...b3bf
  From:                0xf39f...2266
  Gas used:            26815 of 30000000
  Block #2:            0x9d40...ab17

둘 다 Contract call 로 시작하지만, 갈라지는 지점은 그 아래입니다. increase 에만 Gas used 와 Block # 이 붙어 있습니다. get() 을 두 번이나 불렀는데도 블록은 하나도 생기지 않았습니다. 정리하면 이렇습니다.

구분get() (view)increase() (상태 변경)
반환값함수의 결과값트랜잭션 객체
가스들지 않음소모함
블록생기지 않음새 블록에 담김
기다림즉시wait() 로 대기
노드 요청eth_calleth_sendTransaction

이 표의 내용은 앞으로 다루는 모든 dApp 의 기본 동작 방식입니다. 화면에 숫자를 보여 주는 일은 왼쪽 열이고, 버튼을 눌러 무언가를 바꾸는 일은 오른쪽 열입니다.

시작 전 준비물

  • 앞 랩에서 배포한 Counter 컨트랙트 주소
  • `npx hardhat node` 가 계속 실행 중인 터미널
  • ethers.js v6 기본 문법(async/await)

진행 확인

0 / 5

각 단계를 직접 수행하고, 아래 ‘확인 기준’이 실제로 보이면 체크하세요. 모두 체크하면 완료할 수 있습니다.