SettleLab
전체 코스
LESSON 02Labs and Capstone

Mock 스테이블코인 랩

핵심1시간근거 2

학습 결과

  • mint, burn, transfer, pause가 있는 mock token을 구현한다.
  • 권한과 이벤트를 테스트한다.

선행 조건

  • Solidity 보안 기초

완료 기준

  • 핵심 함수 테스트가 통과했다.
  • 이벤트 스키마를 검토했다.
  • 다음 invariant 후보를 기록했다.

Mock 스테이블코인 랩

도입

첫 번째 랩은 학습용 stablecoin을 직접 읽고 검증하는 과정이다. 이 token은 단순한 ERC-20이 아니다. mint, burn, freeze, pause, permit, transferWithAuthorization, role admin 보호가 모두 들어 있어 이후 checkout과 escrow 실습의 공통 기반이 된다.

이 랩에서 중요한 질문은 "토큰을 전송할 수 있는가"가 아니라 "어떤 권한과 상태가 전송을 막아야 하는가"다. freeze가 transfer만 막고 permit은 열어두면, 사용자는 얼어붙은 계정으로도 승인 경로를 만들 수 있다. pause가 mint/burn에 적용되지 않으면 비상 중에도 공급량이 변할 수 있다. 이런 판단을 코드와 테스트로 확인한다.

학습 목표

  • mint, burn, transfer, pause가 있는 mock token을 구현한다.
  • 권한과 이벤트를 테스트한다.

개념 설명

구조 맵가로 스크롤 · 크게 보기 지원
Mock 스테이블코인 랩 구조 맵이 시각화는 랩 산출물을 캡스톤 설계로 옮길 때 actor, 권한, 데이터 증거가 어느 레이어에서 갈라지는지를 보여주며, 'Mock 스테이블코인 랩'에서 남겨야 할 설계 증거를 좁힌다.
01

핵심 개념

  • mint, burn, transfer, pause가 있는 mock token을 구현한다.
  • 권한과 이벤트를 테스트한다.
02

검증 지점

  • 랩 산출물이 캡스톤의 어떤 장과 테스트 증거로 재사용되는가
  • 실패 로그가 남는가
  • 핵심 함수 테스트가 통과했다.
03

실습 산출물

  • Mock token 구현
  • 권한 검수
  • 운영 대시보드까지 닫히는가
크게 보기
01

핵심 개념

  • mint, burn, transfer, pause가 있는 mock token을 구현한다.
  • 권한과 이벤트를 테스트한다.
02

검증 지점

  • 랩 산출물이 캡스톤의 어떤 장과 테스트 증거로 재사용되는가
  • 실패 로그가 남는가
  • 핵심 함수 테스트가 통과했다.
03

실습 산출물

  • Mock token 구현
  • 권한 검수
  • 운영 대시보드까지 닫히는가

코드 위치는 08-실습/mock-stablecoin-lab/src/MockStablecoin.sol이다. 테스트는 test/MockStablecoin.t.soltest/MockStablecoinInvariant.t.sol에서 확인한다.

먼저 권한과 상태를 분리해서 읽는다.

컨트랙트코드 영역별 점검 표

표 자료가로 스크롤 · 크게 보기 지원
영역코드 요소확인할 질문
발행/소각MINTER_ROLE, BURNER_ROLE, mint, burn공급량 변경이 role과 pause/freeze 조건을 모두 통과하는가
이동 제한paused, isFrozen, _requireCanMovetransfer, transferFrom, signed transfer가 같은 이동 제한을 공유하는가
승인approve, permit, _approvefrozen owner 또는 spender가 approval을 만들 수 없는가
서명 전송transferWithAuthorization, authorizationStatevalid window와 nonce 재사용 방지가 전송 전에 확인되는가
관리자 보호DEFAULT_ADMIN_ROLE, roleMemberCount, LastAdmin마지막 admin을 실수로 제거할 수 없는가
크게 보기
영역코드 요소확인할 질문
발행/소각MINTER_ROLE, BURNER_ROLE, mint, burn공급량 변경이 role과 pause/freeze 조건을 모두 통과하는가
이동 제한paused, isFrozen, _requireCanMovetransfer, transferFrom, signed transfer가 같은 이동 제한을 공유하는가
승인approve, permit, _approvefrozen owner 또는 spender가 approval을 만들 수 없는가
서명 전송transferWithAuthorization, authorizationStatevalid window와 nonce 재사용 방지가 전송 전에 확인되는가
관리자 보호DEFAULT_ADMIN_ROLE, roleMemberCount, LastAdmin마지막 admin을 실수로 제거할 수 없는가

테스트를 실행한다.

CODE SURFACEshell
forge test

이 랩의 흐름은 아래와 같다.

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

강의 포인트

표 자료가로 스크롤 · 크게 보기 지원
관점확인할 질문증거로 남길 것
권한mint, burn, freeze, pause 권한이 분리되어 있는가role matrix
상태 제한pause와 freeze가 transfer, approve, permit, authorization에 일관되게 적용되는가실패 테스트 목록
공급량mint와 burn이 totalSupply와 balances를 같은 방향으로 갱신하는가balance/supply 증거
서명permit nonce와 authorization nonce가 어떤 방식으로 재사용을 막는가nonce 비교표
운영마지막 admin 보호와 이벤트가 운영 추적에 충분한가event schema 점검
크게 보기
관점확인할 질문증거로 남길 것
권한mint, burn, freeze, pause 권한이 분리되어 있는가role matrix
상태 제한pause와 freeze가 transfer, approve, permit, authorization에 일관되게 적용되는가실패 테스트 목록
공급량mint와 burn이 totalSupply와 balances를 같은 방향으로 갱신하는가balance/supply 증거
서명permit nonce와 authorization nonce가 어떤 방식으로 재사용을 막는가nonce 비교표
운영마지막 admin 보호와 이벤트가 운영 추적에 충분한가event schema 점검

코드로 확인하기

앞에서 만든 설계를 실습 코드로 연결한다. 예시는 그대로 외우는 대상이 아니라, 구현 파일에서 어떤 줄을 읽고 어떤 테스트를 붙일지 정하는 기준이다.

이 강의에서 등장하는 모든 개념은 아래의 실제 컨트랙트 코드 안에서 일어난다. 표·다이어그램과 매핑해 가며 읽는다.

컨트랙트EIP-712 도메인과 typehash 상수

도메인 분리자, permit, ERC-3009 typehash가 컴파일타임 상수로 박혀 있다. 같은 서명이 다른 컨트랙트·체인에서 재사용되지 않도록 한다.

컨트랙트MockStablecoin 도메인·typehash 상수 파일: 08-실습/mock-stablecoin-lab/src/MockStablecoin.sol (라인 1-30)

DEFAULT_ADMIN_ROLE/MINTER_ROLE 등 5개 역할과 PERMIT_TYPEHASH, TRANSFER_WITH_AUTHORIZATION_TYPEHASH가 정의된 헤더.

CODE SURFACEsolidity
// SPDX-License-Identifier: MITpragma solidity 0.8.24;contract MockStablecoin {    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;    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 PERMIT_TYPEHASH = keccak256(        "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"    );    bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = keccak256(        "TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"    );    string public name;    string public symbol;    string public constant version = "1";    uint8 public immutable decimals;    uint256 public totalSupply;    bytes32 public immutable DOMAIN_SEPARATOR;    mapping(address account => uint256 balance) private _balances;    mapping(address owner => mapping(address spender => uint256 allowance)) private _allowances;    mapping(address owner => uint256 nonce) public nonces;    mapping(address authorizer => mapping(bytes32 nonce => bool used)) public authorizationState;

컨트랙트이동 제한 게이트 _requireCanMove

transfer/transferFrom/permit 후의 transferFrom/ERC-3009 모든 경로가 같은 게이트를 통과해야 freeze·pause가 빠짐없이 적용된다. 우회 가능 함수가 있는지 확인하는 게 이 랩의 핵심이다.

컨트랙트이동 제한 공통 게이트 파일: 08-실습/mock-stablecoin-lab/src/MockStablecoin.sol (라인 265-280)

freeze와 pause를 모든 이동 경로가 공유하도록 하는 내부 헬퍼.

CODE SURFACEsolidity
    function _requireCanMove(address from, address to) internal view {        _requireNotPaused();        _requireNotFrozen(from);        _requireNotFrozen(to);    }    function _requireNotPaused() internal view {        if (paused) revert TokenPaused();    }    function _requireNotFrozen(address account) internal view {        if (account == address(0)) revert ZeroAddress();        if (isFrozen[account]) revert FrozenAccount(account);    }    function _transfer(address from, address to, uint256 amount) internal {

컨트랙트ERC-3009 서명 결제 transferWithAuthorization

validAfter/validBefore 시간 창, authorizationState의 nonce 사용 표시, ecrecover 검증, 그리고 위의 _requireCanMove가 같은 함수 안에 모여 있다.

컨트랙트transferWithAuthorization 본문 파일: 08-실습/mock-stablecoin-lab/src/MockStablecoin.sol (라인 206-241)

기간·nonce 재사용·이동 제한·서명 검증 순서로 결제를 실행한다.

CODE SURFACEsolidity
    function transferWithAuthorization(        address from,        address to,        uint256 value,        uint256 validAfter,        uint256 validBefore,        bytes32 nonce,        uint8 v,        bytes32 r,        bytes32 s    ) external {        if (block.timestamp <= validAfter) revert AuthorizationNotYetValid(validAfter);        if (block.timestamp >= validBefore) revert AuthorizationExpired(validBefore);        if (authorizationState[from][nonce]) revert AuthorizationAlreadyUsed(from, nonce);        _requireCanMove(from, to);        bytes32 structHash = keccak256(            abi.encode(                TRANSFER_WITH_AUTHORIZATION_TYPEHASH,                from,                to,                value,                validAfter,                validBefore,                nonce            )        );        bytes32 digest = _hashTypedData(structHash);        if (ecrecover(digest, v, r, s) != from) revert InvalidSignature();        authorizationState[from][nonce] = true;        emit AuthorizationUsed(from, nonce);        _transfer(from, to, value);    }

운영Foundry 테스트 실행 커맨드

위 컨트랙트는 forge test로 즉시 돌려볼 수 있다. 모든 권한·서명·freeze 분기를 테스트가 다룬다.

CODE SURFACEshell
cd 08-/mock-stablecoin-labforge test --match-contract MockStablecoin -vv

실무 예시

컨트랙트예를 들어 규제 대응으로 특정 계정을 freeze해야 한다고 가정한다. 이때 단순히 transfer만 막으면 부족하다.

표 자료가로 스크롤 · 크게 보기 지원
경로freeze 적용이 필요한 이유기대 결과
transferfrozen account가 직접 자금을 보낼 수 있다revert
approvefrozen owner가 spender에게 권한을 남길 수 있다revert
transferFrom제3자가 frozen account의 allowance를 소진할 수 있다revert
permitfrozen owner가 gasless approval을 만들 수 있다revert
transferWithAuthorizationfrozen signer의 signed payment가 나중에 실행될 수 있다revert
크게 보기
경로freeze 적용이 필요한 이유기대 결과
transferfrozen account가 직접 자금을 보낼 수 있다revert
approvefrozen owner가 spender에게 권한을 남길 수 있다revert
transferFrom제3자가 frozen account의 allowance를 소진할 수 있다revert
permitfrozen owner가 gasless approval을 만들 수 있다revert
transferWithAuthorizationfrozen signer의 signed payment가 나중에 실행될 수 있다revert

이 표를 테스트 이름으로 바꾸면 좋은 실습 산출물이 된다. 각 테스트는 "어떤 계정이 얼어붙었는지", "어떤 함수가 호출됐는지", "어떤 error가 나와야 하는지"를 명시해야 한다.

흔한 오해와 실패 시나리오

표 자료가로 스크롤 · 크게 보기 지원
오해실제로 확인할 것
ERC-20 함수가 있으면 stablecoin 학습이 끝났다고 본다.발행자 권한, freeze, pause, reserve 연결, 이벤트 인덱싱까지 검토해야 한다.
freeze는 잔액 이동만 막으면 된다고 본다.approval과 signed authorization도 미래 이동 권한을 만들기 때문에 함께 막아야 한다.
sequential nonce와 random nonce를 같은 문제로 본다.permit은 owner별 증가 nonce를 쓰고, ERC-3009 스타일 authorization은 bytes32 nonce 사용 여부를 기록한다.
학습용 ecrecover 구현을 그대로 재사용한다.production 후보에서는 OpenZeppelin ECDSA/EIP712와 signature malleability 검토가 필요하다.
role revoke 테스트를 생략한다.마지막 admin 제거 방지가 없으면 운영자가 권한 복구 불능 상태를 만들 수 있다.
크게 보기
오해실제로 확인할 것
ERC-20 함수가 있으면 stablecoin 학습이 끝났다고 본다.발행자 권한, freeze, pause, reserve 연결, 이벤트 인덱싱까지 검토해야 한다.
freeze는 잔액 이동만 막으면 된다고 본다.approval과 signed authorization도 미래 이동 권한을 만들기 때문에 함께 막아야 한다.
sequential nonce와 random nonce를 같은 문제로 본다.permit은 owner별 증가 nonce를 쓰고, ERC-3009 스타일 authorization은 bytes32 nonce 사용 여부를 기록한다.
학습용 ecrecover 구현을 그대로 재사용한다.production 후보에서는 OpenZeppelin ECDSA/EIP712와 signature malleability 검토가 필요하다.
role revoke 테스트를 생략한다.마지막 admin 제거 방지가 없으면 운영자가 권한 복구 불능 상태를 만들 수 있다.

실습 과제

  1. 컨트랙트Mock token 구현 검토: MockStablecoin.sol에서 role, pause, freeze, permit, authorization 흐름을 읽고 위의 role matrix를 자신의 말로 완성한다.
  2. 컨트랙트권한 검수: mint, burn, freeze, pause, grantRole, revokeRole, transferDefaultAdminRole마다 caller, 성공 조건, 실패 조건, event를 표로 정리한다.
  3. 컨트랙트freeze 우회 테스트 설계: transfer, approve, transferFrom, permit, transferWithAuthorization 각각에 대해 frozen owner/spender/from/to 케이스를 테스트 목록으로 작성한다.
  4. 운영다음 invariant 후보 기록: totalSupply와 balances 합계, authorization nonce 재사용, 마지막 admin 보호 중 최소 2개를 invariant 후보로 남긴다.

완료 기준

  1. 핵심 함수 테스트가 통과했다.
  2. 이벤트 스키마를 검토했다.
  3. 다음 invariant 후보를 기록했다.

근거 자료

  • Mock 스테이블코인 랩: 08-실습/01-Mock-스테이블코인-랩.md
  • README: 08-실습/mock-stablecoin-lab/README.md
Final checkpoint

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

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

  • 핵심 함수 테스트가 통과했다.
  • 이벤트 스키마를 검토했다.
  • 다음 invariant 후보를 기록했다.

학습 자료 근거

Mock 스테이블코인 랩
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서
README
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서