SettleLab
전체 코스
LESSON 01Security Testing

Solidity 보안 기초와 학습맵

입문35분근거 3

학습 결과

  • Solidity 보안의 기본 위험 범주를 설명한다.
  • 스테이블코인 코드 리뷰의 첫 번째 pass를 수행한다.
  • asset, authority, state, interaction, time, upgrade, operations 관점으로 리뷰 메모를 작성한다.

선행 조건

  • Stablecoin Systems

완료 기준

  • 기본 취약점 범주를 설명했다.
  • 리뷰 대상 함수 목록을 작성했다.
  • 첫 불변식 후보를 3개 적었다.

Solidity 보안 기초와 학습맵

도입

Solidity 보안은 취약점 이름을 외우는 일이 아니다. 돈의 상태가 여러 actor와 여러 함수 호출 순서에서도 깨지지 않는지 검증하는 일이다. 스테이블코인과 checkout contract는 잔고, allowance, mint/burn 권한, freeze/pause, 서명 nonce, refund/settlement 상태를 동시에 다루기 때문에 작은 누락이 곧 정산 사고로 이어질 수 있다.

이 강의는 보안 트랙의 시작점이다. 목표는 완벽한 auditor가 되는 것이 아니라, 코드를 처음 열었을 때 어디부터 표시해야 하는지 배우는 것이다. 어떤 자산이 위험한지, 누가 움직일 수 있는지, 어떤 상태가 깨지면 안 되는지, 외부 호출과 시간이 개입하는지부터 본다.

학습 목표

  • Solidity 보안의 기본 위험 범주를 설명한다.
  • 스테이블코인 코드 리뷰의 첫 번째 pass를 수행한다.
  • asset, authority, state, interaction, time, upgrade, operations 관점으로 리뷰 메모를 작성한다.

개념 설명

위험 매트릭스가로 스크롤 · 크게 보기 지원
Solidity 보안 기초와 학습맵 위험 매트릭스이 시각화는 보안 리뷰와 테스트 캠페인에서 가능성, 영향, 감지 신호, 대응 소유자를 어떻게 묶을지를 보여주며, 'Solidity 보안 기초와 학습맵'에서 남겨야 할 설계 증거를 좁힌다.
Impact highLikelihood medium

핵심 가정 오류

권한 경계가 테스트되는가

Impact mediumLikelihood medium

운영 상태 누락

불변조건이 실패 상태를 잡는가

Impact mediumLikelihood low

학습 산출물 미흡

Security first-pass 리뷰표 만들기

크게 보기
Impact highLikelihood medium

핵심 가정 오류

권한 경계가 테스트되는가

Impact mediumLikelihood medium

운영 상태 누락

불변조건이 실패 상태를 잡는가

Impact mediumLikelihood low

학습 산출물 미흡

Security first-pass 리뷰표 만들기

1. 보안 트랙은 리뷰 관점에서 읽는다

Security Testing 코스는 아래 순서로 이어진다. 각 강의는 하나의 질문에 답한다.

표 자료가로 스크롤 · 크게 보기 지원
순서강의핵심 질문
1Solidity 보안 기초이 코드에서 무엇이 위험한가?
2AccessControl/Pausable누가 mint, burn, freeze, pause, upgrade할 수 있는가?
3Foundry fuzz/invariant어떤 호출 순서에서도 깨지면 안 되는 성질은 무엇인가?
4Slither/Echidna workflow도구 결과를 어떻게 triage할 것인가?
5보안 리뷰 체크리스트리뷰 메모를 release gate로 어떻게 바꿀 것인가?
6ECDSA/EIP-712서명이 다른 chain, contract, nonce에서 재사용되지 않는가?
7Upgradeable proxycode와 storage가 바뀌어도 돈의 상태가 유지되는가?
8Monitoring runbook배포 후 mint/freeze/upgrade 이상을 어떻게 감지하는가?
크게 보기
순서강의핵심 질문
1Solidity 보안 기초이 코드에서 무엇이 위험한가?
2AccessControl/Pausable누가 mint, burn, freeze, pause, upgrade할 수 있는가?
3Foundry fuzz/invariant어떤 호출 순서에서도 깨지면 안 되는 성질은 무엇인가?
4Slither/Echidna workflow도구 결과를 어떻게 triage할 것인가?
5보안 리뷰 체크리스트리뷰 메모를 release gate로 어떻게 바꿀 것인가?
6ECDSA/EIP-712서명이 다른 chain, contract, nonce에서 재사용되지 않는가?
7Upgradeable proxycode와 storage가 바뀌어도 돈의 상태가 유지되는가?
8Monitoring runbook배포 후 mint/freeze/upgrade 이상을 어떻게 감지하는가?

2. 먼저 잡을 mental model

코드를 읽기 전에 아래 일곱 가지 관점을 표로 만든다.

표 자료가로 스크롤 · 크게 보기 지원
관점질문스테이블코인/checkout 예시
Asset무엇이 위험한가?user balance, merchant settlement, escrowed funds
Authority누가 움직일 수 있는가?minter, burner, freezer, pauser, upgrader, relayer
State어떤 상태가 깨지면 안 되는가?totalSupply, payment status, refund idempotency
Interaction외부 호출과 callback이 있는가?token transfer, receiver hook, oracle, bridge
Timedeadline, finality, challenge window가 있는가?permit deadline, ERC-3009 validBefore, CCTP finality
Upgradecode나 storage가 바뀔 수 있는가?proxy implementation, storage layout, admin key
Operations배포 후 누가 감시하고 멈출 수 있는가?monitoring, pause, manual review, timelock
크게 보기
관점질문스테이블코인/checkout 예시
Asset무엇이 위험한가?user balance, merchant settlement, escrowed funds
Authority누가 움직일 수 있는가?minter, burner, freezer, pauser, upgrader, relayer
State어떤 상태가 깨지면 안 되는가?totalSupply, payment status, refund idempotency
Interaction외부 호출과 callback이 있는가?token transfer, receiver hook, oracle, bridge
Timedeadline, finality, challenge window가 있는가?permit deadline, ERC-3009 validBefore, CCTP finality
Upgradecode나 storage가 바뀔 수 있는가?proxy implementation, storage layout, admin key
Operations배포 후 누가 감시하고 멈출 수 있는가?monitoring, pause, manual review, timelock

이 표는 리뷰를 빠르게 만든다. 예를 들어 refund() 함수를 보면 Asset은 escrowed funds, Authority는 payer/merchant/admin, State는 Refunded, Interaction은 token transfer, Time은 refund deadline, Operations는 failed refund queue가 된다.

3. Solidity 공식 문서가 주는 기준

Solidity 공식 문서는 스마트컨트랙트가 토큰이나 더 가치 있는 것을 다룰 수 있고, 실행과 소스가 공개되는 경우가 많으므로 예상하지 못한 사용을 막는 것이 특히 중요하다고 설명한다. 여기서 중요한 태도는 "정상 입력에서 잘 된다"보다 "예상하지 못한 actor와 호출 순서에서도 깨지지 않는다"를 확인하는 것이다.

4. 주요 위험군은 stablecoin 예시로 연결한다

표 자료가로 스크롤 · 크게 보기 지원
위험설명스테이블코인 예시
Reentrancy외부 호출이 다시 contract를 호출refund/withdraw 중 중복 출금
Access control권한 체크 누락아무나 mint/freeze 가능
Approval raceallowance 변경 경쟁spender가 이전 allowance 사용
Signature replay같은 서명 재사용permit/ERC-3009 nonce 누락
Domain replay다른 chain/contract에서 재사용잘못된 EIP-712 domain
Gas/loop DoSloop가 커져 실행 불가holders 전체 순회
Upgrade riskstorage/logic 변경token accounting 깨짐
Oracle/price risk외부 가격 잘못 사용RWA NAV, collateral valuation
크게 보기
위험설명스테이블코인 예시
Reentrancy외부 호출이 다시 contract를 호출refund/withdraw 중 중복 출금
Access control권한 체크 누락아무나 mint/freeze 가능
Approval raceallowance 변경 경쟁spender가 이전 allowance 사용
Signature replay같은 서명 재사용permit/ERC-3009 nonce 누락
Domain replay다른 chain/contract에서 재사용잘못된 EIP-712 domain
Gas/loop DoSloop가 커져 실행 불가holders 전체 순회
Upgrade riskstorage/logic 변경token accounting 깨짐
Oracle/price risk외부 가격 잘못 사용RWA NAV, collateral valuation

위험군을 외우는 것만으로는 부족하다. 각 위험군이 어떤 함수와 상태에 연결되는지 표시해야 한다. 예를 들어 signature replay는 permit, transferWithAuthorization, x402 settlement에서 nonce와 domain 검증으로 내려간다.

5. Checks-Effects-Interactions는 상태 전이 언어로 읽는다

공식 Solidity 문서는 reentrancy 방어의 기본으로 checks-effects-interactions 패턴을 제시한다. 결제 코드에서는 아래처럼 읽는다.

표 자료가로 스크롤 · 크게 보기 지원
단계리뷰 질문Checkout 예시
Checks권한, 입력값, 상태가 맞는가?payment가 Paid이고 refund window 안인가?
Effects내부 상태를 먼저 바꾸는가?RefundRequested 또는 Refunded로 전이하는가?
Interactions외부 호출 실패와 callback을 다루는가?token transfer 실패 시 RefundFailed로 남기는가?
크게 보기
단계리뷰 질문Checkout 예시
Checks권한, 입력값, 상태가 맞는가?payment가 Paid이고 refund window 안인가?
Effects내부 상태를 먼저 바꾸는가?RefundRequested 또는 Refunded로 전이하는가?
Interactions외부 호출 실패와 callback을 다루는가?token transfer 실패 시 RefundFailed로 남기는가?

주의할 점은 항상 상태를 먼저 바꾸라는 기계적 규칙이 아니라는 것이다. 외부 token transfer 실패 시 전체 transaction을 revert할지, 별도 failed state로 남길지 제품 요구사항과 회계 정책을 함께 봐야 한다.

6. Fail-safe mode는 운영 기능이다

Solidity 공식 문서는 fail-safe mode도 권장한다. 스테이블코인/결제 시스템에서 fail-safe는 단순한 pause() 함수가 아니라 사용자의 돈을 더 큰 손실로부터 보호하는 운영 기능이다.

표 자료가로 스크롤 · 크게 보기 지원
Fail-safe막으려는 사고리뷰 포인트
Pause전역 결제/정산 오류 확산pause 권한, paused 중 refund 정책
Freeze특정 주소 제재/도난 자금 이동sender/receiver/spender/authorizer 모두 막는가
Emergency withdrawalescrow 자금 장기 고착누가, 언제, 어떤 증거로 실행하는가
Manual review queue자동 처리 위험어떤 상태가 queue로 들어가는가
Rate limit대량 mint/withdrawal 사고한도 변경 권한과 event monitoring
Admin timelock악성/오류 upgrade 지연delay, proposer/executor, emergency path
Monitoring alert사고 조기 감지role, mint, freeze, pause, upgrade event
크게 보기
Fail-safe막으려는 사고리뷰 포인트
Pause전역 결제/정산 오류 확산pause 권한, paused 중 refund 정책
Freeze특정 주소 제재/도난 자금 이동sender/receiver/spender/authorizer 모두 막는가
Emergency withdrawalescrow 자금 장기 고착누가, 언제, 어떤 증거로 실행하는가
Manual review queue자동 처리 위험어떤 상태가 queue로 들어가는가
Rate limit대량 mint/withdrawal 사고한도 변경 권한과 event monitoring
Admin timelock악성/오류 upgrade 지연delay, proposer/executor, emergency path
Monitoring alert사고 조기 감지role, mint, freeze, pause, upgrade event

7. 작은 contract가 좋은 이유

복잡한 contract는 테스트와 리뷰가 어렵다. 결제 시스템도 한 contract에 모든 기능을 넣지 말고 역할을 나눈다. 역할이 나뉘면 권한과 invariant도 더 선명해진다.

표 자료가로 스크롤 · 크게 보기 지원
Contract맡는 역할먼저 볼 위험
Stablecoinbalance, mint/burn, freeze, permit, authorizationsupply, role, freeze bypass, nonce
Checkoutinvoice와 payment statedouble payment, stale invoice, relayer failure
Escrow보관, claim, refundreentrancy, stuck funds, refund idempotency
Routerchain/token route 선택wrong chain, fake token, slippage
Policyspend limit, allowlistadmin abuse, stale config, missing event
크게 보기
Contract맡는 역할먼저 볼 위험
Stablecoinbalance, mint/burn, freeze, permit, authorizationsupply, role, freeze bypass, nonce
Checkoutinvoice와 payment statedouble payment, stale invoice, relayer failure
Escrow보관, claim, refundreentrancy, stuck funds, refund idempotency
Routerchain/token route 선택wrong chain, fake token, slippage
Policyspend limit, allowlistadmin abuse, stale config, missing event

8. 첫 리뷰표 예시

표 자료가로 스크롤 · 크게 보기 지원
함수AssetAuthorityState changeExternal interaction첫 질문
mint(to, amount)totalSupply, user balanceMINTER_ROLEsupply 증가없음reserve/issuer approval 없이 호출 가능한가?
freeze(account)account mobilityFREEZER_ROLEfrozen flag없음permit/ERC-3009 경로도 막히는가?
permit(owner, spender, value, deadline, sig)allowancesignernonce/allowance없음domain, nonce, deadline이 맞는가?
payWithAuthorization(...)user balance, merchant receivablesigner/relayerpayment statustoken transferrelayer 지연과 nonce 사용 상태를 분리하는가?
refund(paymentId)escrow/merchant ledgerpayer/admin/systemrefund statustoken transferdouble refund와 transfer failure를 어떻게 처리하는가?
크게 보기
함수AssetAuthorityState changeExternal interaction첫 질문
mint(to, amount)totalSupply, user balanceMINTER_ROLEsupply 증가없음reserve/issuer approval 없이 호출 가능한가?
freeze(account)account mobilityFREEZER_ROLEfrozen flag없음permit/ERC-3009 경로도 막히는가?
permit(owner, spender, value, deadline, sig)allowancesignernonce/allowance없음domain, nonce, deadline이 맞는가?
payWithAuthorization(...)user balance, merchant receivablesigner/relayerpayment statustoken transferrelayer 지연과 nonce 사용 상태를 분리하는가?
refund(paymentId)escrow/merchant ledgerpayer/admin/systemrefund statustoken transferdouble refund와 transfer failure를 어떻게 처리하는가?

이 표가 첫 pass의 제출물이다. 아직 exploit을 찾지 못해도 된다. 먼저 리뷰 대상 함수와 위험을 분류해야 다음 강의에서 role matrix, invariant, fuzz target을 만들 수 있다.

9. 첫 invariant 후보

표 자료가로 스크롤 · 크게 보기 지원
영역invariant 후보
SupplytotalSupply는 mint와 burn으로만 변하고, balances 합과 설명 가능한 차이만 가진다.
Freeze/Pausefrozen account는 transfer, transferFrom, permit, ERC-3009 경로로 자산을 움직일 수 없다.
Noncepermit/ERC-3009 nonce는 재사용되지 않고 chain/contract domain 밖에서 유효하지 않다.
Payment하나의 payment intent는 한 번만 Paid가 되고, refund는 원 결제 금액을 초과할 수 없다.
Settlementmerchant settlement는 Paid 또는 Delivered 상태 없이 실행되지 않는다.
크게 보기
영역invariant 후보
SupplytotalSupply는 mint와 burn으로만 변하고, balances 합과 설명 가능한 차이만 가진다.
Freeze/Pausefrozen account는 transfer, transferFrom, permit, ERC-3009 경로로 자산을 움직일 수 없다.
Noncepermit/ERC-3009 nonce는 재사용되지 않고 chain/contract domain 밖에서 유효하지 않다.
Payment하나의 payment intent는 한 번만 Paid가 되고, refund는 원 결제 금액을 초과할 수 없다.
Settlementmerchant settlement는 Paid 또는 Delivered 상태 없이 실행되지 않는다.

강의 포인트

표 자료가로 스크롤 · 크게 보기 지원
관점강의 중 확인할 질문학습 후 남길 증거
Asset어떤 돈이나 권리가 위험한가?asset list
Authority누가 상태를 바꾸는가?role/function matrix
State어떤 상태가 절대 깨지면 안 되는가?invariant 후보
Interaction외부 호출과 실패 상태가 있는가?CEI/reentrancy review note
Operations멈추고 복구하는 방법이 있는가?fail-safe 표
크게 보기
관점강의 중 확인할 질문학습 후 남길 증거
Asset어떤 돈이나 권리가 위험한가?asset list
Authority누가 상태를 바꾸는가?role/function matrix
State어떤 상태가 절대 깨지면 안 되는가?invariant 후보
Interaction외부 호출과 실패 상태가 있는가?CEI/reentrancy review note
Operations멈추고 복구하는 방법이 있는가?fail-safe 표

실무 예시

컨트랙트상황: MockStablecoinCheckout 코드 리뷰를 맡았다. contract에는 mint, freeze, permit, pay, refund, settleMerchant가 있다. 첫날 해야 할 일은 모든 취약점을 찾는 것이 아니라, 각 함수가 어떤 위험 영역에 속하는지 표시하는 것이다.

표 자료가로 스크롤 · 크게 보기 지원
함수첫 pass에서 남길 메모
mintrole check, mint event, supply invariant, reserve approval assumption
freezebypass path, approval/permit side effect, support flow
permitEIP-712 domain, deadline, nonce, signer recovery
paypayment state, token transfer failure, relayer replay
refundidempotency, CEI, failed transfer state, merchant ledger
settleMerchantauthorization, settlement amount, slippage/fee, double settlement
크게 보기
함수첫 pass에서 남길 메모
mintrole check, mint event, supply invariant, reserve approval assumption
freezebypass path, approval/permit side effect, support flow
permitEIP-712 domain, deadline, nonce, signer recovery
paypayment state, token transfer failure, relayer replay
refundidempotency, CEI, failed transfer state, merchant ledger
settleMerchantauthorization, settlement amount, slippage/fee, double settlement

이 메모가 있어야 Foundry invariant와 Slither/Echidna triage가 의미를 가진다. 도구를 먼저 돌리면 warning은 나오지만, 무엇이 진짜 출시 차단 이슈인지 판단하기 어렵다.

코드로 확인하기

앞의 보안 기준을 코드와 테스트로 확인한다. 함수가 어떤 전제를 세우고, 테스트가 어떤 실패 조건을 고정하는지 함께 읽는다.

컨트랙트Checks-Effects-Interactions를 결제 환불에 적용

환불 함수 한 줄이 CEI 패턴의 핵심 차이를 만든다. 외부 token transfer 전에 status를 먼저 바꿔야 reentry 경로가 막힌다.

CODE SURFACEsolidity
// 잘못된 예 — interaction이 effect 전에 일어난다function refund(bytes32 paymentId) external {    Payment storage p = payments[paymentId];    if (p.status != Status.Paid) revert();    IERC20(p.token).transfer(p.payer, p.amount); // 외부 호출 먼저    p.status = Status.Refunded;                  // 상태 변경이 나중}// CEI에 맞춘 예 — checks → effects → interactionsfunction refund(bytes32 paymentId) external nonReentrant {    Payment storage p = payments[paymentId];    if (p.status != Status.Paid) revert PaymentNotPaid(paymentId);    if (msg.sender != p.merchant) revert OnlyMerchant(msg.sender);    p.status = Status.Refunded;                  // 효과 먼저 확정    emit PaymentRefunded(paymentId);    IERC20(p.token).safeTransfer(p.payer, p.amount); // 외부 호출은 마지막}

컨트랙트함수 단위 리뷰표를 코드 주석으로 박기

리뷰표를 별도 문서로 만드는 것보다 NatSpec에 박아두면 PR diff에 같이 보이고 staleness가 줄어든다.

CODE SURFACEsolidity
/// @notice 결제 환불을 실행한다./// @dev Asset: escrowed funds in this contract///      Authority: payment.merchant 만 호출 가능///      State: Status.Paid -> Status.Refunded (단방향)///      Interaction: token.safeTransfer 1회 (CEI 마지막 단계)///      Time: refund 기한은 invoice 만료 시점까지///      Invariant: 같은 paymentId 에 대해 PaymentRefunded 는 최대 1회 emitfunction refund(bytes32 paymentId) external nonReentrant { ... }

운영Fail-safe 모드 진입/이탈 절차

pause는 단순 토글이 아니다. 진입 시점에 어떤 상태를 동결할지, 이탈 후 백로그를 어떻게 처리할지가 같이 결정되어야 한다.

CODE SURFACEshell
# 1) 이상 신호 확인 (monitoring 대시보드)forge cast call $MOCK_STABLE "paused()" --rpc-url $RPC# 2) PAUSER_ROLE 보유 주소에서 pause 실행forge cast send $MOCK_STABLE "pause()" --private-key $PAUSER_KEY --rpc-url $RPC# 3) 후속 조치 — refund 큐 동결 여부 / 운영자 통보 / 외부 공지# 4) 원인 해소 후 unpause + 백로그 재처리 절차 문서화forge cast send $MOCK_STABLE "unpause()" --private-key $PAUSER_KEY --rpc-url $RPC

흔한 오해와 실패 시나리오

표 자료가로 스크롤 · 크게 보기 지원
오해실제로 확인할 것
취약점 이름을 많이 알면 리뷰가 된다고 생각한다.asset, authority, state, interaction 표가 먼저다.
unit test가 통과하면 보안 테스트가 끝났다고 본다.호출 순서, actor, time, upgrade, operations를 따로 흔들어야 한다.
pause가 있으면 fail-safe가 완성됐다고 본다.paused 중 refund, emergency withdrawal, manual review 정책이 필요하다.
도구가 찾아주는 항목만 고치면 된다고 본다.도구는 triage 입력이고 business logic invariant는 사람이 정의해야 한다.
크게 보기
오해실제로 확인할 것
취약점 이름을 많이 알면 리뷰가 된다고 생각한다.asset, authority, state, interaction 표가 먼저다.
unit test가 통과하면 보안 테스트가 끝났다고 본다.호출 순서, actor, time, upgrade, operations를 따로 흔들어야 한다.
pause가 있으면 fail-safe가 완성됐다고 본다.paused 중 refund, emergency withdrawal, manual review 정책이 필요하다.
도구가 찾아주는 항목만 고치면 된다고 본다.도구는 triage 입력이고 business logic invariant는 사람이 정의해야 한다.

실습 과제

  1. 컨트랙트Security first-pass 리뷰표 만들기: Mock stablecoin 또는 checkout contract의 함수 목록을 asset, authority, state change, external interaction, time condition, fail-safe 여부로 분류한다.
  2. 컨트랙트첫 invariant 후보 작성하기: supply/balance, freeze/pause, permit/ERC-3009 nonce, refund/settlement 중 최소 3개 invariant 후보를 문장으로 작성한다.
  3. 운영Fail-safe 상태 설계하기: pause, freeze, emergency withdrawal, manual review, rate limit, monitoring alert가 각각 어떤 사고를 막는지 checkout 예시로 정리한다.

완료 기준

  1. 기본 취약점 범주를 설명했다.
  2. 리뷰 대상 함수 목록을 작성했다.
  3. 첫 불변식 후보를 3개 적었다.
  4. 외부 호출, 권한 함수, fail-safe 상태를 표시한 리뷰 표를 만들었다.

근거 자료

Final checkpoint

읽기를 마쳤다면 여기서 완료 처리한다

  • 기본 취약점 범주를 설명했다.
  • 리뷰 대상 함수 목록을 작성했다.
  • 첫 불변식 후보를 3개 적었다.

학습 자료 근거

보안 테스트 학습맵
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서
Solidity 보안 기초
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서
Solidity Security Considerations
https://docs.soliditylang.org/en/latest/security-considerations.html