SettleLab
전체 코스
LESSON 02Security Testing

권한관리, AccessControl, Pausable

핵심40분근거 5

학습 결과

  • 역할 기반 권한과 pause 권한의 책임을 분리한다.
  • 운영자 키 탈취와 과도한 권한 집중을 위협 모델에 넣는다.
  • freeze, pause, upgrade, mint 권한의 범위와 모니터링 신호를 설계한다.

선행 조건

  • Solidity 보안 기초

완료 기준

  • role matrix를 만들었다.
  • pause 범위를 설명했다.
  • 권한 변경 모니터링 항목을 정의했다.

권한관리, AccessControl, Pausable

도입

스테이블코인에서 access control은 보안의 중심이다. 누가 mint할 수 있는지, 누가 주소를 freeze할 수 있는지, 누가 전체 transfer를 pause할 수 있는지에 따라 사용자 자금과 merchant settlement가 직접 영향을 받는다. 권한 설계는 컨트랙트 내부 문제가 아니라 운영, support, compliance, dashboard까지 이어지는 제품 설계다.

OpenZeppelin 문서는 access control이 mint, freeze, upgrade 같은 민감한 작업을 누가 수행할 수 있는지 결정한다고 설명한다. 또한 Pausable은 import만으로 동작하지 않고 실제 함수에 whenNotPaused 같은 modifier를 적용해야 한다. 이 강의는 이 두 사실을 stablecoin checkout의 release gate로 바꾼다.

학습 목표

  • 역할 기반 권한과 pause 권한의 책임을 분리한다.
  • 운영자 키 탈취와 과도한 권한 집중을 위협 모델에 넣는다.
  • freeze, pause, upgrade, mint 권한의 범위와 모니터링 신호를 설계한다.

개념 설명

상태머신가로 스크롤 · 크게 보기 지원
권한관리, AccessControl, Pausable 상태머신이 시각화는 보안 리뷰와 테스트 캠페인에서 상태 전이가 어떤 증거와 실패 조건으로 움직이는지를 보여주며, '권한관리, AccessControl, Pausable'에서 남겨야 할 설계 증거를 좁힌다.
State 1

RoleMapped

역할 기반 권한과 pause 권한의 책임을 분리한다.

minter, burner, freezer, pauser, upgrader 권한의 소유자를 분리한다.
State 2

UnauthorizedCallRejected

권한 경계가 테스트되는가

권한 없는 mint, freeze, pause, upgrade 호출이 테스트에서 거절된다.
State 3

EmergencyPaused

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

pause 사유, 승인자, 해제 조건, 사용자 안내가 incident log에 남는다.
State 4

RunbookLinked

Stablecoin role matrix 만들기

role matrix를 만들었다.
크게 보기
State 1

RoleMapped

역할 기반 권한과 pause 권한의 책임을 분리한다.

minter, burner, freezer, pauser, upgrader 권한의 소유자를 분리한다.
State 2

UnauthorizedCallRejected

권한 경계가 테스트되는가

권한 없는 mint, freeze, pause, upgrade 호출이 테스트에서 거절된다.
State 3

EmergencyPaused

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

pause 사유, 승인자, 해제 조건, 사용자 안내가 incident log에 남는다.
State 4

RunbookLinked

Stablecoin role matrix 만들기

role matrix를 만들었다.

1. Ownable, AccessControl, AccessManager는 운영 복잡도가 다르다

표 자료가로 스크롤 · 크게 보기 지원
방식장점위험언제 적합한가
Ownable단순하고 빠르다.owner 하나에 권한이 집중된다.prototype, 작은 내부 도구
AccessControlrole을 기능별로 나눌 수 있다.role admin 설계를 잘못하면 더 위험하다.stablecoin, checkout, treasury
AccessManager여러 contract 권한을 중앙 관리할 수 있다.운영 모델과 delay 설정이 복잡하다.여러 contract가 같은 governance를 공유할 때
Timelock사용자와 운영팀에 exit/review window를 준다.긴급 대응이 느려질 수 있다.upgrade, parameter change
Multisigkey 탈취와 실수를 줄인다.signer 운영과 quorum 관리가 필요하다.production admin, pauser, upgrader
크게 보기
방식장점위험언제 적합한가
Ownable단순하고 빠르다.owner 하나에 권한이 집중된다.prototype, 작은 내부 도구
AccessControlrole을 기능별로 나눌 수 있다.role admin 설계를 잘못하면 더 위험하다.stablecoin, checkout, treasury
AccessManager여러 contract 권한을 중앙 관리할 수 있다.운영 모델과 delay 설정이 복잡하다.여러 contract가 같은 governance를 공유할 때
Timelock사용자와 운영팀에 exit/review window를 준다.긴급 대응이 느려질 수 있다.upgrade, parameter change
Multisigkey 탈취와 실수를 줄인다.signer 운영과 quorum 관리가 필요하다.production admin, pauser, upgrader

첫 설계 원칙은 "권한을 나누되, 각 권한의 admin도 같이 설계한다"이다. MINTER_ROLEFREEZER_ROLE을 나눠도 DEFAULT_ADMIN_ROLE 하나가 둘 다 즉시 바꿀 수 있으면 실제 위험은 admin에 모인다.

2. Stablecoin role matrix

표 자료가로 스크롤 · 크게 보기 지원
Role허용 함수금지해야 할 것권장 owner모니터링 event
DEFAULT_ADMIN_ROLErole grant/revoke, admin 설정직접 mint, freeze, balance 조정multisig + delayRoleGranted, RoleRevoked
MINTER_ROLEissuer 승인된 mintrole 변경, freeze, upgradeissuer ops multisigmint event, amount anomaly
BURNER_ROLEredeem/treasury burnmint, freeze, role 변경treasury/redeem opsburn event, redemption batch
FREEZER_ROLEfreeze/unfreezeuser balance 이동, role 변경compliance multisigfreeze spike, unfreeze
PAUSER_ROLEpause/unpauserole 변경, upgradeincident response multisigPaused, Unpaused
UPGRADER_ROLEimplementation upgrademint/freeze와 겸직governance/timelockUpgraded, admin change
RELAYER_ROLEsigned payment submitamount 변경, nonce resetbackend service key with limitssubmission rate, failure spike
크게 보기
Role허용 함수금지해야 할 것권장 owner모니터링 event
DEFAULT_ADMIN_ROLErole grant/revoke, admin 설정직접 mint, freeze, balance 조정multisig + delayRoleGranted, RoleRevoked
MINTER_ROLEissuer 승인된 mintrole 변경, freeze, upgradeissuer ops multisigmint event, amount anomaly
BURNER_ROLEredeem/treasury burnmint, freeze, role 변경treasury/redeem opsburn event, redemption batch
FREEZER_ROLEfreeze/unfreezeuser balance 이동, role 변경compliance multisigfreeze spike, unfreeze
PAUSER_ROLEpause/unpauserole 변경, upgradeincident response multisigPaused, Unpaused
UPGRADER_ROLEimplementation upgrademint/freeze와 겸직governance/timelockUpgraded, admin change
RELAYER_ROLEsigned payment submitamount 변경, nonce resetbackend service key with limitssubmission rate, failure spike

이 표에는 반드시 "금지해야 할 것"이 들어가야 한다. 허용 함수만 적으면 권한 집중을 놓친다. 예를 들어 FREEZER_ROLE은 주소 이동성을 막을 수 있지만, 잔고를 옮기거나 role을 바꾸면 안 된다.

3. DEFAULT_ADMIN_ROLE은 별도 위협 모델로 본다

OpenZeppelin 문서에서는 DEFAULT_ADMIN_ROLE이 다른 role의 admin이고 자기 자신의 admin이기도 하므로 민감한 권한임을 강조한다. Production stablecoin에서는 아래 완화책을 검토한다.

표 자료가로 스크롤 · 크게 보기 지원
위험완화책
단일 EOA admin 탈취multisig, hardware key, signer rotation
admin이 실수로 자기 role 제거two-step admin transfer, default admin rules
즉시 role 변경timelock, proposal/approval/execution 분리
변경 감지 실패role event dashboard, incident alert
emergency 권한 남용사후 review, reason code, support notice
크게 보기
위험완화책
단일 EOA admin 탈취multisig, hardware key, signer rotation
admin이 실수로 자기 role 제거two-step admin transfer, default admin rules
즉시 role 변경timelock, proposal/approval/execution 분리
변경 감지 실패role event dashboard, incident alert
emergency 권한 남용사후 review, reason code, support notice

Admin 설계는 "누가 권한을 갖는가"뿐 아니라 "권한 변경이 얼마나 빨리 효력을 갖는가"를 포함한다. Upgrade와 mint 권한이 같은 multisig에 있으면 key compromise 하나가 code와 supply를 동시에 위험하게 만든다.

4. Pause와 freeze는 다르다

표 자료가로 스크롤 · 크게 보기 지원
기능범위주로 쓰는 상황사용자 영향
Pausecontract 또는 product 전체 기능전역 버그, exploit, route 장애모든 사용자 결제/transfer 제한
Freeze특정 주소 또는 계정제재, 도난 자금, 계정 조사대상 주소만 이동 제한
Route disable특정 chain/token/routeCCTP 지연, liquidity stress일부 결제 수단 제한
Manual review특정 payment/withdrawal이상 거래, reconciliation mismatch개별 건 처리 지연
크게 보기
기능범위주로 쓰는 상황사용자 영향
Pausecontract 또는 product 전체 기능전역 버그, exploit, route 장애모든 사용자 결제/transfer 제한
Freeze특정 주소 또는 계정제재, 도난 자금, 계정 조사대상 주소만 이동 제한
Route disable특정 chain/token/routeCCTP 지연, liquidity stress일부 결제 수단 제한
Manual review특정 payment/withdrawal이상 거래, reconciliation mismatch개별 건 처리 지연

Pause는 강력하지만 거칠다. 전체 checkout을 멈추면 신규 피해는 막을 수 있지만 refund, merchant settlement, emergency withdrawal도 같이 막힐 수 있다. 따라서 pause 상태에서도 어떤 함수가 열려 있어야 하는지 미리 정해야 한다.

5. Pausable은 함수마다 적용 범위를 정해야 한다

OpenZeppelin Pausable 문서 기준으로 module을 포함하는 것만으로 함수가 자동으로 멈추지 않는다. whenNotPaused, whenPaused, _requireNotPaused 같은 조건을 어디에 적용할지 설계해야 한다.

표 자료가로 스크롤 · 크게 보기 지원
함수pause 중 기본 정책이유
transfer중지피해 확산 방지
transferFrom중지allowance 경로 우회 방지
approve정책 결정 필요사고 중 새 allowance를 허용할지 검토
permit중지 또는 제한paused 중 allowance 생성 우회 방지
transferWithAuthorization중지signed transfer 우회 방지
mint중지사고 중 supply 증가 방지
burn조건부 허용 가능redemption/emergency unwind 필요 가능
checkout claim중지 또는 manual reviewservice delivery와 정산 분리 필요
checkout refund조건부 허용 가능사용자 보호와 회계 정책에 따라 다름
크게 보기
함수pause 중 기본 정책이유
transfer중지피해 확산 방지
transferFrom중지allowance 경로 우회 방지
approve정책 결정 필요사고 중 새 allowance를 허용할지 검토
permit중지 또는 제한paused 중 allowance 생성 우회 방지
transferWithAuthorization중지signed transfer 우회 방지
mint중지사고 중 supply 증가 방지
burn조건부 허용 가능redemption/emergency unwind 필요 가능
checkout claim중지 또는 manual reviewservice delivery와 정산 분리 필요
checkout refund조건부 허용 가능사용자 보호와 회계 정책에 따라 다름

중요한 것은 정책을 테스트로 고정하는 것이다. "pause하면 멈춘다"가 아니라 어떤 함수가 revert하고 어떤 함수가 manual review로 넘어가는지를 표로 남긴다.

6. Emergency pause runbook

흐름도가로 스크롤 · 크게 보기 지원
강의 흐름도상태, 책임, 검증 지점을 순서대로 읽기 위한 다이어그램이다.
크게 보기

Runbook에는 unpause 조건도 있어야 한다. Pause는 실행보다 해제가 더 위험할 수 있다. Root cause가 확인되지 않았는데 unpause하면 같은 사고가 반복된다.

7. 테스트와 모니터링 항목

표 자료가로 스크롤 · 크게 보기 지원
테스트기대 결과
권한 없는 주소가 mint 호출revert
권한 없는 주소가 freeze 호출revert
마지막 admin 제거 시도실패 또는 delay/2-step 요구
pause 상태에서 transferrevert
pause 상태에서 permit 또는 ERC-3009정책대로 revert 또는 제한
freeze 대상 주소의 transferFromspender 경로도 실패
freeze와 pause가 동시에 적용우선순위와 revert reason이 문서와 일치
unpause 후 정상 transfer정상 복구
크게 보기
테스트기대 결과
권한 없는 주소가 mint 호출revert
권한 없는 주소가 freeze 호출revert
마지막 admin 제거 시도실패 또는 delay/2-step 요구
pause 상태에서 transferrevert
pause 상태에서 permit 또는 ERC-3009정책대로 revert 또는 제한
freeze 대상 주소의 transferFromspender 경로도 실패
freeze와 pause가 동시에 적용우선순위와 revert reason이 문서와 일치
unpause 후 정상 transfer정상 복구
표 자료가로 스크롤 · 크게 보기 지원
Dashboard signalAction
RoleGranted/RoleRevokedproduction multisig 여부 확인
unexpected mintpause 검토, issuer 확인
freeze spikecompliance/support 동기화
Pausedroute status와 user notice 업데이트
Unpausedsmoke test와 settlement resume 확인
upgrade eventstorage/layout smoke test, admin 확인
크게 보기
Dashboard signalAction
RoleGranted/RoleRevokedproduction multisig 여부 확인
unexpected mintpause 검토, issuer 확인
freeze spikecompliance/support 동기화
Pausedroute status와 user notice 업데이트
Unpausedsmoke test와 settlement resume 확인
upgrade eventstorage/layout smoke test, admin 확인

코드로 확인하기

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

컨트랙트OpenZeppelin AccessControl 기반 stablecoin 권한 패턴

Mock 학습용 코드와 달리 실제 배포 후보는 OZ AccessControl을 상속한다. _grantRole로 초기 권한을 분리하고, onlyRole로 함수마다 정확한 역할을 요구한다.

CODE SURFACEsolidity
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";contract StablecoinV2 is AccessControl, Pausable {    bytes32 public constant MINTER_ROLE   = keccak256("MINTER_ROLE");    bytes32 public constant BURNER_ROLE   = keccak256("BURNER_ROLE");    bytes32 public constant FREEZER_ROLE  = keccak256("FREEZER_ROLE");    bytes32 public constant PAUSER_ROLE   = keccak256("PAUSER_ROLE");    bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");    constructor(address adminMultisig) {        _grantRole(DEFAULT_ADMIN_ROLE, adminMultisig);        // MINTER 등은 adminMultisig가 추후 grantRole 로 분리 부여 — 단일 EOA 집중 방지    }    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) whenNotPaused {        _mint(to, amount);    }    function pause() external onlyRole(PAUSER_ROLE) {        _pause();    }}

컨트랙트Pausable 적용 범위를 함수별로 분기

whenNotPaused는 modifier로 명시한 함수에만 적용된다. burn/refund는 사고 중에도 열어둘 수 있게 정책에 따라 분리한다.

CODE SURFACEsolidity
function transfer(address to, uint256 amount) public override whenNotPaused returns (bool) {    return super.transfer(to, amount);}function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) {    // pause 중에도 redemption은 열어둔다 — whenNotPaused 일부러 누락    _burn(from, amount);}function refund(bytes32 paymentId) external nonReentrant {    // refund는 사용자 보호 차원에서 pause 중에도 허용    Payment storage p = payments[paymentId];    if (p.status != Status.Paid) revert PaymentNotPaid(paymentId);    p.status = Status.Refunded;    IERC20(p.token).safeTransfer(p.payer, p.amount);}

컨트랙트AccessControlDefaultAdminRules — 2-step + delay

OZ 4.9+의 AccessControlDefaultAdminRules는 DEFAULT_ADMIN_ROLE을 2-step transfer + delay로 보호한다. admin 키 탈취·실수의 충격을 줄인다.

CODE SURFACEsolidity
import {AccessControlDefaultAdminRules}  from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol";contract StablecoinV2 is AccessControlDefaultAdminRules {    constructor(address adminMultisig)        AccessControlDefaultAdminRules(3 days, adminMultisig) // 3일 지연    {}    // beginDefaultAdminTransfer → acceptDefaultAdminTransfer 두 단계 + 3일 대기    // cancelDefaultAdminTransfer 로 취소 가능}

운영Role 이벤트 모니터링 — viem watcher 예시

권한 변경은 사고 신호다. RoleGranted/Revoked/Paused/Unpaused를 production multisig 가 아닌 곳에서 발생하면 즉시 알림.

CODE SURFACEtypescript
import { createPublicClient, http, parseAbi } from "viem";import { mainnet } from "viem/chains";const TRUSTED_ADMIN = "0xMultisigAddress";const client = createPublicClient({ chain: mainnet, transport: http() });const abi = parseAbi([  "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",  "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)"]);client.watchContractEvent({  address: STABLECOIN_ADDRESS,  abi,  eventName: "RoleGranted",  onLogs: (logs) => {    for (const log of logs) {      if (log.args.sender?.toLowerCase() !== TRUSTED_ADMIN.toLowerCase()) {        pageOncall({ severity: "critical", reason: "RoleGranted by untrusted sender", log });      }    }  }});

강의 포인트

표 자료가로 스크롤 · 크게 보기 지원
관점강의 중 확인할 질문학습 후 남길 증거
Role matrix각 role이 무엇을 할 수 있고 무엇을 하면 안 되는가?stablecoin role matrix
Admin riskDEFAULT_ADMIN_ROLE이 어디에 있고 변경 delay가 있는가?admin threat model
Pause scopepause 중 어떤 함수가 막히고 어떤 함수가 열리는가?pause function coverage
Monitoringrole/pause/freeze/upgrade event가 dashboard에 잡히는가?alert list와 runbook
크게 보기
관점강의 중 확인할 질문학습 후 남길 증거
Role matrix각 role이 무엇을 할 수 있고 무엇을 하면 안 되는가?stablecoin role matrix
Admin riskDEFAULT_ADMIN_ROLE이 어디에 있고 변경 delay가 있는가?admin threat model
Pause scopepause 중 어떤 함수가 막히고 어떤 함수가 열리는가?pause function coverage
Monitoringrole/pause/freeze/upgrade event가 dashboard에 잡히는가?alert list와 runbook

실무 예시

운영[CONTRACT] 상황: checkout 팀이 PAUSER_ROLE을 backend deployer EOA에 주고, FREEZER_ROLEUPGRADER_ROLE도 같은 주소에 두려 한다. 테스트넷에서는 편하지만 production에서는 key 하나가 checkout 전체, 사용자 주소 freeze, implementation upgrade를 동시에 통제한다.

표 자료가로 스크롤 · 크게 보기 지원
문제위험수정 방향
deployer EOA가 pauserkey 탈취 시 전역 중단multisig pauser, incident channel
freezer와 upgrader 겸직compliance 조치와 code 변경 권한 집중freezer는 compliance multisig, upgrade는 timelock
pause 중 refund 정책 없음사고 중 사용자 자금 고착refund/emergency path 사전 정의
role event dashboard 없음권한 변경 감지 지연RoleGranted, RoleRevoked, Paused alert
크게 보기
문제위험수정 방향
deployer EOA가 pauserkey 탈취 시 전역 중단multisig pauser, incident channel
freezer와 upgrader 겸직compliance 조치와 code 변경 권한 집중freezer는 compliance multisig, upgrade는 timelock
pause 중 refund 정책 없음사고 중 사용자 자금 고착refund/emergency path 사전 정의
role event dashboard 없음권한 변경 감지 지연RoleGranted, RoleRevoked, Paused alert

이 예시의 산출물은 코드 수정 제안만이 아니다. Role owner, multisig threshold, timelock delay, pause 대상 함수, unpause 조건을 포함한 운영 문서가 필요하다.

흔한 오해와 실패 시나리오

표 자료가로 스크롤 · 크게 보기 지원
오해실제로 확인할 것
AccessControl을 쓰면 권한 설계가 자동으로 안전해진다.role admin, multisig, timelock, monitoring을 따로 설계해야 한다.
Pausable을 import하면 모든 함수가 멈춘다.modifier가 붙은 함수만 멈춘다.
pause와 freeze는 같은 기능이라고 본다.pause는 전역, freeze는 주소 단위다.
admin key는 개발팀만 알면 된다고 본다.support, compliance, finance가 영향을 받으므로 운영 runbook이 필요하다.
크게 보기
오해실제로 확인할 것
AccessControl을 쓰면 권한 설계가 자동으로 안전해진다.role admin, multisig, timelock, monitoring을 따로 설계해야 한다.
Pausable을 import하면 모든 함수가 멈춘다.modifier가 붙은 함수만 멈춘다.
pause와 freeze는 같은 기능이라고 본다.pause는 전역, freeze는 주소 단위다.
admin key는 개발팀만 알면 된다고 본다.support, compliance, finance가 영향을 받으므로 운영 runbook이 필요하다.

실습 과제

  1. 컨트랙트Stablecoin role matrix 만들기: DEFAULT_ADMIN, MINTER, BURNER, FREEZER, PAUSER, UPGRADER, RELAYER role별 owner, admin role, 허용 함수, 금지 함수, multisig/timelock 요구, dashboard event를 표로 정리한다.
  2. 운영Emergency pause runbook 작성하기: pause trigger, 승인자, pause 대상 함수, refund/emergency path, unpause 조건, 사용자 안내, dashboard alert를 단계별로 작성한다.
  3. 컨트랙트Freeze/Pause 우회 테스트 설계하기: frozen 또는 paused 상태에서 transfer, transferFrom, approve, permit, ERC-3009, checkout claim/refund가 어떻게 동작해야 하는지 테스트 표로 작성한다.

완료 기준

  1. role matrix를 만들었다.
  2. pause 범위를 설명했다.
  3. 권한 변경 모니터링 항목을 정의했다.
  4. emergency pause와 unpause 승인 절차를 runbook으로 작성했다.

근거 자료

Final checkpoint

읽기를 마쳤다면 여기서 기록한다

아래 버튼은 읽기 진도를 저장한다. 체크리스트, 과제, 랩 산출물은 위 Workbook에서 따로 관리한다.

  • role matrix를 만들었다.
  • pause 범위를 설명했다.
  • 권한 변경 모니터링 항목을 정의했다.

학습 자료 근거

권한관리 AccessControl Pausable
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서
OpenZeppelin Contracts: Access Control
https://docs.openzeppelin.com/contracts/5.x/access-control
OpenZeppelin Contracts API: Access
https://docs.openzeppelin.com/contracts/api/access
OpenZeppelin Contracts API: Pausable
https://docs.openzeppelin.com/contracts/5.x/api/utils
OpenZeppelin AccessControlDefaultAdminRules
https://docs.openzeppelin.com/contracts/4.x/api/access#AccessControlDefaultAdminRules