스크립트로 컨트랙트 호출하기
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 이 즉시 출력됩니다. 두 가지를 확인하세요.
- 값이 바로 돌아온다. 기다림이 없습니다.
- 노드 터미널에 로그는 남지만 블록은 생기지 않는다.
노드 터미널을 보면 이렇게 찍혀 있습니다.
eth_call
Contract call: Counter#get
From: 0xf39f...2266
To: 0x5fbd...0aa3Contract 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_call | eth_sendTransaction |
이 표의 내용은 앞으로 다루는 모든 dApp 의 기본 동작 방식입니다. 화면에 숫자를 보여 주는 일은 왼쪽 열이고, 버튼을 눌러 무언가를 바꾸는 일은 오른쪽 열입니다.