스마트 컨트랙트 감사: 배포 전 무엇을 검증해야 하는가
정적 분석·퍼징·포멀 검증·수동 리뷰로 구성되는 스마트 컨트랙트 감사 절차와 재진입 취약점 수정 사례, DeFi·NFT·브리지 감사 포인트를 정리한다.
2026-08-12 · 최초 발행 2025-12-06
블록체인 기반 서비스의 핵심 구성 요소인 스마트 컨트랙트는 배포 후 수정이 어렵고, 결함이 곧바로 자산 손실로 이어진다. Smart Contract Audit은 배포 전후 코드와 설계의 취약점, 논리 오류, 거버넌스 리스크를 체계적으로 검증하는 절차이며 DeFi·NFT·DAO·브리지 같은 고가치 프로토콜에서는 필수 품질보증 활동이다.
감사가 다루는 범위와 산출물
감사는 스마트 컨트랙트의 보안·정합성·성능·운영성을 정적·동적 분석, 수동 리뷰, 포멀 검증으로 점검하고 리스크를 식별·정량화·개선하는 보증 활동이다. 범위는 설계 문서, 위협 모델, 소스코드(Solidity/Vyper 등), 의존성(라이브러리/오라클), 배포·업그레이드 파이프라인, 온체인 구성(프록시/멀티시그/타임락), 모니터링 규칙까지 걸친다. 산출물은 취약점 리스크 레지스터(심각도/영향/가능성/증거), 재현 절차, 수정 권고안, 커버리지·테스트 결과, 잔여 리스크 명세다.
스코핑과 위협 모델링
자산(사용자 자금, 관리자 권한, 서드파티 의존성)을 식별하고 공격면(입력 경로, 외부 호출, 업그레이드 경로)을 정의한다. STRIDE, SWC Registry, DASP Top 10 기준으로 위협을 분류하고 리스크 기준을 수립한다.
자동 분석과 퍼징
정적 분석(SAST) 도구로 재진입, 정수 오버/언더플로, 접근제어, tx.origin 사용, uninitialized storage 등을 자동 탐지한다. 퍼징·프로퍼티 기반 테스트는 ABI 기반 자동 입력 생성으로 에지 케이스를 탐색하고 커버리지를 높인다.
수동 리뷰와 포멀 검증
비즈니스 로직 일관성을 검토하고 상태 전이·회계 보전성(invariant)을 확인하며 엣지 조건은 수기로 추론한다. 필요한 경우 포멀 검증으로 잔고 보전, 권한 불변 같은 핵심 안전성 속성을 증명한다.
리포팅에서 재검증까지
심각도 기반으로 티켓화하고 재현 스텝·패치 가이드를 제공한다. 수정 후에는 회귀 테스트와 재감사를 수행하고 변경 영향도를 분석해 잔여 리스크를 명시한다. 이 절차를 거치면 고·중위험 취약점을 70% 이상 사전 제거할 가능성이 있다(조직 성숙도·범위·시간에 따라 변동). 익스플로잇 대응, 포크, 법무 비용에 견주면 선제적으로 감사에 투자하는 편이 비용 효율적이다.
운영 통제와 거버넌스
업그레이드 프록시, 멀티시그, 타임락, 서킷 브레이커(Pausable) 같은 운영 통제를 검토한다. 임계치 초과 전송, 비정상 민팅, 슬리피지 이상치 같은 온체인 모니터링 룰을 설정하고 알림 체계를 구축한다. 모니터링·런북·롤백 계획을 미리 세워두면 사고 대응 평균 복구 시간(MTTR)도 단축된다.
감사 절차 흐름
감사 기법별 특성 비교
| 감사 기법 | 성능(속도) | 확장성(규모) | 일관성(재현) | 안정성(오탐/미탐) | 운영 편의 |
|---|---|---|---|---|---|
| 정적 분석(SAST) | 높음 | 높음 | 높음 | 중간(오탐 존재) | 높음 |
| 퍼지/동적 테스트 | 중간 | 중간 | 중간 | 높음(미탐 감소) | 중간 |
| 포멀 검증 | 낮음 | 낮음 | 매우 높음 | 매우 높음 | 낮음 |
| 수동 코드 리뷰 | 낮음 | 낮음 | 중간 | 높음(맥락 반영) | 중간 |
| 온체인 모니터링 | 높음 | 높음 | 높음 | 중간 | 높음 |
프로토콜 유형별 감사 포인트
DeFi 대출 프로토콜은 담보 비율, 청산 로직, 이자 계산 불변식을 검증하고 오라클 조작, 재진입, 유동성 고갈 시나리오를 테스트한다. 업그레이드 가능한 토큰·금고는 UUPS/Transparent Proxy 권한·타임락을 검증하고 초기화자 보호(initializer)·슬롯 충돌을 점검한다. NFT 민팅·세일즈는 화이트리스트·할당량 검증, 수수료 정산 정합성, 재진입 및 재판매 로직을 점검한다. 크로스체인 브리지는 서명 검증, 재생공격(replay) 방지, 메시지 순서 불변성, 신뢰 최소화 설계를 검토한다.
재진입 취약점과 수정
전제조건은 Solidity ^0.8.20, Foundry(Forge) >= 0.2.0이며, forge init, forge test로 로컬 테스트한다.
취약 코드는 외부 호출을 먼저 수행하고 상태 갱신을 나중에 한다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VaultBad {
mapping(address => uint256) public balance;
function deposit() external payable {
balance[msg.sender] += msg.value;
}
function withdraw() external {
uint256 amt = balance[msg.sender];
require(amt > 0, "no balance");
(bool ok, ) = msg.sender.call{value: amt}(""); // 외부 호출 먼저 수행
require(ok, "transfer failed");
balance[msg.sender] = 0; // 상태 갱신 지연 -> 재진입 가능
}
}
수정 코드는 상태를 먼저 갱신한 뒤 외부 호출한다(Checks-Effects-Interactions).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/security/ReentrancyGuard.sol";
contract VaultGood is ReentrancyGuard {
mapping(address => uint256) public balance;
function deposit() external payable {
balance[msg.sender] += msg.value;
}
function withdraw() external nonReentrant {
uint256 amt = balance[msg.sender];
require(amt > 0, "no balance");
balance[msg.sender] = 0; // Checks-Effects-Interactions
(bool ok, ) = msg.sender.call{value: amt}("");
require(ok, "transfer failed");
}
}
테스트는 Foundry로 인출 후 잔액 변화를 검증한다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "./VaultGood.sol";
contract VaultTest is Test {
VaultGood v;
function setUp() public {
v = new VaultGood();
vm.deal(address(this), 10 ether);
v.deposit{value: 1 ether}();
}
function testWithdraw() public {
uint256 beforeBal = address(this).balance;
v.withdraw();
assertEq(address(this).balance, beforeBal + 1 ether);
}
}
모범사례와 트레이드오프
최소 권한·타임락·멀티시그는 오퍼레이션 리스크를 줄이지만 긴급 대응이 지연되고 거버넌스가 복잡해진다. 업그레이드 가능 아키텍처는 취약점 패치를 쉽게 하지만 중앙화 리스크와 업그레이드 키 관리 부담이 생긴다. 서킷 브레이커(Pausable)·레이트 리미트는 사고 확산을 억제하지만 서비스 중단 리스크와 오남용 우려가 있다. 버그바운티·Responsible Disclosure는 커뮤니티 기반 탐지를 강화하지만 보상·법무 정책 관리가 필요하다.
감사는 배포 전후 전 단계에 걸친다
설계·코드·운영 전 단계에 걸친 체계적 보증 활동이라는 점에서, 자동화 도구와 수동 리뷰, 포멀 방법의 균형적 결합, 거버넌스 통제가 핵심이다. 배포 전 감사와 배포 후 모니터링을 표준 절차로 내재화하는 편이 안전하다.