Foundry & Hardhat
What’s Foundry?
Foundry는 Paradigm이 개발한 Rust 기반 이더리움 개발 툴킷이다. Solidity 코드를 Solidity로 테스트한다는 철학 아래, 빠른 컴파일과 테스트 실행 속도를 핵심 가치로 삼는다.
Foundry는 단일 바이너리가 아니라 4개의 독립적인 CLI 도구로 구성된다.
flowchart LR
F["Foundry"] --> Forge["**Forge**<br/>빌드,테스트"]
F --> Cast["**Cast**<br/>체인 상호작용"]
F --> Anvil["**Anvil**<br/>로컬 노드"]
F --> Chisel["**Chisel**<br/>Solidity REPL"]
| 도구 | 역할 | 핵심 기능 |
|---|---|---|
| Forge | 빌드, 테스트, 배포 | 컴파일, 단위 테스트, fuzz 테스트, 불변성 테스트, Gas 리포트 |
| Cast | 체인 상호작용 CLI | 트랜잭션 전송, 컨트랙트 호출, ABI 인코딩/디코딩, ENS 조회 |
| Anvil | 로컬 이더리움 노드 | 메인넷 포크, 블록 타임 조절, 계정 잔액 조작, 스냅샷 |
| Chisel | Solidity REPL | 코드 조각 즉시 실행, 빠른 프로토타이핑 |
Forge의 테스트 실행 속도는 Hardhat 대비 약 2~5배 빠르다. 이는 Rust로 구현된 EVM(revm) 위에서 Solidity 바이트코드를 직접 실행하기 때문이다.
Cheatcode
Foundry 테스트의 차별점은 cheatcode 이다.
vm 객체를 통해 EVM 상태를 자유롭게 조작할 수 있다.
// msg.sender를 alice로 위조
vm.prank(alice);
// 특정 주소에 ETH 부여
vm.deal(alice, 10 ether);
// 블록 타임스탬프 변경
vm.warp(block.timestamp + 1 days);
// 특정 revert 기대
vm.expectRevert("Unauthorized");
이를 활용하면 접근 제어, 시간 기반 로직, 잔액 조건 등 다양한 시나리오를 테스트할 수 있다.
Fuzz Testing & Invariant Testing
Foundry는 fuzz testing 와 불변성 테스트(invariant testing) 를 기본 내장한다.
fuzz test는 함수 파라미터에 랜덤 값을 자동 주입하여, 개발자가 미처 생각하지 못한 엣지 케이스를 탐색한다.
// amount에 수백 가지 랜덤 값이 자동으로 주입된다
function testFuzzDeposit(uint256 amount) public {
amount = bound(amount, 1, 100 ether);
vault.deposit{value: amount}();
assertEq(vault.balanceOf(address(this)), amount);
}
불변성 테스트는 한 단계 더 나아간다. 컨트랙트의 함수를 랜덤 순서로 호출하면서, “어떤 상황에서도 깨지면 안 되는 속성”이 유지되는지 검증한다.
// 어떤 순서로 deposit/withdraw를 호출해도
// 컨트랙트 잔액은 항상 모든 사용자 잔액의 합과 같아야 한다
function invariant_solvency() public {
assertGe(
address(vault).balance,
vault.totalDeposits()
);
}
What’s Hardhat?
Hardhat은 Nomic Foundation이 관리하는 Node.js 기반 이더리움 개발 환경이다. JavaScript/TypeScript 생태계의 풍부한 도구와 플러그인을 활용할 수 있으며, 배포와 검증 워크플로우에서 강점을 보인다.
flowchart LR
H["Hardhat"] --> HN["**Hardhat Network**<br/>로컬 EVM"]
H --> P["**Plugin 생태계**<br/>ethers.js,viem"]
H --> I["**Ignition**<br/>선언적 배포"]
H --> T["**Task 시스템**<br/>자동화"]
| 구성 요소 | 역할 | 핵심 기능 |
|---|---|---|
| Hardhat Network | 내장 로컬 EVM | console.log 디버깅, 스택 트레이스, 메인넷 포크 |
| Plugin 생태계 | 확장 기능 | ethers.js/viem 통합, 컨트랙트 검증, 가스 리포트 |
| Ignition | 선언적 배포 시스템 | 배포 모듈, 상태 관리, 멱등성 배포 |
| Task 시스템 | 워크플로우 자동화 | 커스텀 태스크, CI/CD 통합 |
Hardhat Ignition
Hardhat Ignition은 선언적(declarative) 배포 시스템이다. “무엇을 배포할 것인가”를 정의하면, “어떤 순서로 배포할 것인가”는 Ignition이 알아서 결정한다.
정말 편하다!
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const TokenModule = buildModule("TokenModule", (m) => {
const token = m.contract("MyToken", ["MyToken", "MTK"]);
const vault = m.contract("Vault", [token]);
// Ignition이 token → vault 순서를 자동 결정
return { token, vault };
});
여기에 더해 배포 상태 관리가 자동으로 이루어진다. 배포가 중간에 실패해도 이미 배포된 컨트랙트를 다시 배포하지 않고, 실패 지점부터 재개한다. 프로덕션 배포에서 이 안정성은 매우 중요하다.
언제 무엇을 쓸까?
단순히 “어느 것이 더 좋은가”가 아니라, 어떤 작업에 어떤 도구가 적합한가를 이해하는 것이 핵심이다.
Foundry가 유리한 영역
- 테스트 속도
- Fuzz 테스트,불변성 테스트
- 체인 상호작용
Hardhat이 유리한 영역
- 배포
- 통합 테스트
- 디버깅
컨트랙트 검증이나 로컬 노드 지원은 양쪽이 비슷하니 뭘 사용해도 무리가 없다.
왜 하이브리드인가
실무에서는 이 둘중 하나만 쓰지는 않는다.
물론 하나만 쓰는 경우도 있지만, 다이나믹하게 스마트 컨트랙트를 다루는 경우에는 반드시 둘 다 쓰는 편이다.
핵심은 각 도구의 강점만 취하는 것이다.
- Foundry로 테스트한다 — Solidity로 작성한 단위 테스트를 빠르게 반복 실행한다. Fuzz 테스트와 불변성 테스트로 엣지 케이스를 자동 탐색한다
- Hardhat으로 테스트한다 - Typescript 로 통합 테스트를 작성한다.
- Hardhat으로 배포한다 — Ignition의 선언적 배포 모듈로 안전하게 배포한다. TypeScript로 복잡한 배포 시나리오를 처리한다
- Cast로 상호작용한다 — CLI에서 컨트랙트를 즉시 호출하고, 트랜잭션 상태를 확인한다
- Anvil로 시뮬레이션한다 — 메인넷을 포크하여 실제 환경과 동일한 조건에서 테스트한다