SettleLab
전체 코스
LESSON 06Labs and Capstone

CCTP 시뮬레이터 랩

심화1시간근거 2

학습 결과

  • burn, attestation, mint 단계를 시뮬레이션한다.
  • cross-chain 결제 지연과 실패를 상태머신으로 테스트한다.

선행 조건

  • CCTP와 브릿지 신뢰 모델

완료 기준

  • 세 단계 상태가 구현되거나 문서화됐다.
  • timeout/retry 정책을 만들었다.
  • 사용자 상태 문구를 작성했다.

CCTP 시뮬레이터 랩

도입

CCTP 시뮬레이터는 실제 Circle API를 붙이는 랩이 아니다. cross-chain USDC 이동에서 사용자가 경험하는 핵심 상태를 작은 contract로 재현하는 랩이다. source token을 burn하고, attester가 메시지를 승인하거나 지연/실패로 표시하고, destination token을 mint한다.

이 랩의 핵심은 "burn이 성공했으니 결제가 끝났다"는 오해를 깨는 것이다. burn 이후에는 attestation 지연, 실패, destination mint 대기 상태가 남는다. checkout 제품은 이 중간 상태를 사용자와 운영자에게 설명할 수 있어야 한다.

이 랩은 앞의 두 CCTP 강의를 구현으로 확인하는 단계다. 결제 상태 문구는 CCTP와 USDC 크로스체인 결제, trust boundary 판단은 CCTP와 브릿지 신뢰 모델을 기준으로 삼는다.

학습 목표

  • burn, attestation, mint 단계를 시뮬레이션한다.
  • cross-chain 결제 지연과 실패를 상태머신으로 테스트한다.

개념 설명

대시보드가로 스크롤 · 크게 보기 지원
CCTP 시뮬레이터 랩 운영 대시보드 초안이 시각화는 랩 산출물을 캡스톤 설계로 옮길 때 정상 지연과 장애를 구분하기 위해 어떤 지표를 먼저 볼지를 보여주며, 'CCTP 시뮬레이터 랩'에서 남겨야 할 설계 증거를 좁힌다.
Lab outputs
정상

burn, attestation, mint 단계를 시뮬레이션한다.

Open risks
주의

실패 로그가 남는가

Test evidence
검토

CCTP flow 시뮬레이션

Capstone sections
대기

세 단계 상태가 구현되거나 문서화됐다.

medium가정 불일치랩 산출물이 캡스톤의 어떤 장과 테스트 증거로 재사용되는가
high실패 상태 누락실패 로그가 남는가
watch산출물 미완료CCTP flow 시뮬레이션
크게 보기
Lab outputs
정상

burn, attestation, mint 단계를 시뮬레이션한다.

Open risks
주의

실패 로그가 남는가

Test evidence
검토

CCTP flow 시뮬레이션

Capstone sections
대기

세 단계 상태가 구현되거나 문서화됐다.

medium가정 불일치랩 산출물이 캡스톤의 어떤 장과 테스트 증거로 재사용되는가
high실패 상태 누락실패 로그가 남는가
watch산출물 미완료CCTP flow 시뮬레이션

대상 코드는 08-실습/mock-stablecoin-lab/src/CctpSimulator.sol이고, 테스트는 test/CctpSimulator.t.sol이다. source token과 destination token은 모두 MockStablecoin으로 두고, simulator가 source token burn 권한과 destination token mint 권한을 가진다.

표 자료가로 스크롤 · 크게 보기 지원
구성 요소역할확인할 질문
source tokensource chain의 mock USDCburn이 sender balance와 source totalSupply를 줄이는가
destination tokendestination chain의 mock USDCmint가 recipient balance와 destination totalSupply를 늘리는가
transfer registrytransfers[transferId]transferId 재사용과 unknown transfer를 막는가
attesterattestation, delay, fail 권한권한 없는 caller가 상태를 바꿀 수 없는가
combined supplysource + destination supplyburn/mint 완료 후 합계가 보존되는가
크게 보기
구성 요소역할확인할 질문
source tokensource chain의 mock USDCburn이 sender balance와 source totalSupply를 줄이는가
destination tokendestination chain의 mock USDCmint가 recipient balance와 destination totalSupply를 늘리는가
transfer registrytransfers[transferId]transferId 재사용과 unknown transfer를 막는가
attesterattestation, delay, fail 권한권한 없는 caller가 상태를 바꿀 수 없는가
combined supplysource + destination supplyburn/mint 완료 후 합계가 보존되는가

상태는 다섯 개로 충분히 작지만, cross-chain UX의 핵심을 담고 있다.

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

테스트가 확인하는 내용은 다음과 같다.

표 자료가로 스크롤 · 크게 보기 지원
테스트 관점의미
burn, attest, mint 후 supply 보존wrapped bridge가 아니라 burn/mint 모델로 읽는 연습
attestation 없는 mint 실패destination mint는 source burn만으로 충분하지 않다
double mint 실패final state에서 재실행을 막아야 한다
zero recipient 실패burn 전에 입력 검증이 끝나야 한다
delayed transfer사용자에게 pending 상태를 보여줄 수 있어야 한다
unauthorized attesterattestation 권한은 운영 핵심 권한이다
duplicate transferIdmessage registry가 replay와 중복 요청을 막아야 한다
크게 보기
테스트 관점의미
burn, attest, mint 후 supply 보존wrapped bridge가 아니라 burn/mint 모델로 읽는 연습
attestation 없는 mint 실패destination mint는 source burn만으로 충분하지 않다
double mint 실패final state에서 재실행을 막아야 한다
zero recipient 실패burn 전에 입력 검증이 끝나야 한다
delayed transfer사용자에게 pending 상태를 보여줄 수 있어야 한다
unauthorized attesterattestation 권한은 운영 핵심 권한이다
duplicate transferIdmessage registry가 replay와 중복 요청을 막아야 한다

코드로 확인하기

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

컨트랙트Status / TransferRequest / 에러 정의

Burned/Delayed/Attested/Minted/Failed 5상태가 enum으로 박혀 있다. 각 에러는 어느 상태에서 어떤 호출이 실패해야 하는지 명시한다.

컨트랙트CctpSimulator — enum/struct/error/modifier 파일: 08-실습/mock-stablecoin-lab/src/CctpSimulator.sol (라인 9-55)

attester modifier와 상태별 에러를 한꺼번에 확인한다.

CODE SURFACEsolidity
contract CctpSimulator {    enum Status {        Unknown,        Burned,        Delayed,        Attested,        Minted,        Failed    }    struct TransferRequest {        address sender;        address recipient;        uint256 amount;        Status status;    }    ICctpMintBurnToken public immutable sourceToken;    ICctpMintBurnToken public immutable destinationToken;    address public immutable attester;    mapping(bytes32 transferId => TransferRequest request) public transfers;    event TransferBurned(        bytes32 indexed transferId,        address indexed sender,        address indexed recipient,        uint256 amount    );    event TransferDelayed(bytes32 indexed transferId);    event TransferAttested(bytes32 indexed transferId);    event TransferMinted(bytes32 indexed transferId, address indexed recipient, uint256 amount);    event TransferFailed(bytes32 indexed transferId);    error InvalidAddress();    error InvalidAmount();    error UnauthorizedAttester(address caller);    error TransferAlreadyExists(bytes32 transferId);    error UnknownTransfer(bytes32 transferId);    error TransferNotBurned(bytes32 transferId, Status status);    error TransferNotAttested(bytes32 transferId, Status status);    error TransferFinalized(bytes32 transferId, Status status);    modifier onlyAttester() {        if (msg.sender != attester) revert UnauthorizedAttester(msg.sender);        _;    }

컨트랙트requestTransfer — source burn 진입점

burn은 사용자 트랜잭션 한 번에 끝나지만, attestation과 mint는 별도 트랜잭션이다. 사용자에게는 한 결제로 보이지만 컨트랙트는 세 단계로 기록한다.

컨트랙트requestTransfer 본문 파일: 08-실습/mock-stablecoin-lab/src/CctpSimulator.sol (라인 69-86)

duplicate transferId, zero recipient, zero amount를 입력 단계에서 차단한다.

CODE SURFACEsolidity
    function requestTransfer(bytes32 transferId, address recipient, uint256 amount) external {        if (transfers[transferId].status != Status.Unknown) {            revert TransferAlreadyExists(transferId);        }        if (recipient == address(0)) revert InvalidAddress();        if (amount == 0) revert InvalidAmount();        transfers[transferId] = TransferRequest({            sender: msg.sender,            recipient: recipient,            amount: amount,            status: Status.Burned        });        sourceToken.burn(msg.sender, amount);        emit TransferBurned(transferId, msg.sender, recipient, amount);    }

컨트랙트attest / fail / mint — attester 권한 흐름

attester만 호출 가능한 onlyAttester modifier가 attestation 흐름의 신뢰 경계를 정의한다. mint는 누구나 호출할 수 있지만 status가 Attested여야만 통과한다.

컨트랙트markDelayed/attest/mint/fail 본문 파일: 08-실습/mock-stablecoin-lab/src/CctpSimulator.sol (라인 88-130)

각 상태 전이의 권한 분리와 final state 보호.

CODE SURFACEsolidity
    function markDelayed(bytes32 transferId) external onlyAttester {        TransferRequest storage request = _existingTransfer(transferId);        if (request.status != Status.Burned) {            revert TransferNotBurned(transferId, request.status);        }        request.status = Status.Delayed;        emit TransferDelayed(transferId);    }    function attest(bytes32 transferId) external onlyAttester {        TransferRequest storage request = _existingTransfer(transferId);        if (request.status != Status.Burned && request.status != Status.Delayed) {            revert TransferNotBurned(transferId, request.status);        }        request.status = Status.Attested;        emit TransferAttested(transferId);    }    function mint(bytes32 transferId) external {        TransferRequest storage request = _existingTransfer(transferId);        if (request.status != Status.Attested) {            revert TransferNotAttested(transferId, request.status);        }        request.status = Status.Minted;        destinationToken.mint(request.recipient, request.amount);        emit TransferMinted(transferId, request.recipient, request.amount);    }    function fail(bytes32 transferId) external onlyAttester {        TransferRequest storage request = _existingTransfer(transferId);        if (request.status == Status.Minted || request.status == Status.Failed) {            revert TransferFinalized(transferId, request.status);        }        request.status = Status.Failed;        emit TransferFailed(transferId);    }    function _existingTransfer(bytes32 transferId)

클라이언트사용자에게 보여줄 상태 매핑

5개 컨트랙트 상태를 사용자 UI의 "결제 진행 중", "재시도 안내" 같은 문구로 변환하는 헬퍼.

CODE SURFACEtypescript
type ContractStatus = "Burned" | "Delayed" | "Attested" | "Minted" | "Failed";export function userCopyFor(status: ContractStatus, elapsedSeconds: number) {  switch (status) {    case "Burned":      return { tone: "processing", text: "출발 체인에서 소각을 확인 중입니다." };    case "Delayed":      return {        tone: elapsedSeconds > 600 ? "warn" : "processing",        text: elapsedSeconds > 600          ? "공증이 지연되고 있습니다. 잠시 후 자동으로 다시 시도됩니다."          : "공증을 기다리고 있습니다."      };    case "Attested":      return { tone: "processing", text: "도착 체인에서 발행을 준비 중입니다." };    case "Minted":      return { tone: "success", text: "도착 체인에서 수령이 완료됐습니다." };    case "Failed":      return { tone: "error", text: "전송이 실패했습니다. 지원팀이 복구 경로를 확인 중입니다." };  }}

강의 포인트

표 자료가로 스크롤 · 크게 보기 지원
관점확인할 질문증거로 남길 것
상태 정의Burned, Delayed, Attested, Minted, Failed가 사용자 화면에서 어떻게 보이는가상태별 사용자 문구
권한누가 attest, delay, fail을 호출할 수 있는가attester 권한표
replay 방지transferId 중복과 double mint를 막는가실패 테스트 증거
supply 보존source burn과 destination mint가 합계 보존으로 이어지는가combined supply 계산
복구delay 또는 fail 상태에서 어떤 retry/manual recovery가 필요한가runbook 초안
크게 보기
관점확인할 질문증거로 남길 것
상태 정의Burned, Delayed, Attested, Minted, Failed가 사용자 화면에서 어떻게 보이는가상태별 사용자 문구
권한누가 attest, delay, fail을 호출할 수 있는가attester 권한표
replay 방지transferId 중복과 double mint를 막는가실패 테스트 증거
supply 보존source burn과 destination mint가 합계 보존으로 이어지는가combined supply 계산
복구delay 또는 fail 상태에서 어떤 retry/manual recovery가 필요한가runbook 초안

실무 예시

클라이언트[OPS] 사용자가 source chain에서 100 USDC를 destination chain으로 보내는 checkout을 시작했다. source burn transaction은 성공했고 사용자의 source balance는 줄었다. 그러나 attestation이 아직 도착하지 않았다.

표 자료가로 스크롤 · 크게 보기 지원
내부 상태사용자 문구운영자가 볼 증거
Burned전송 요청을 접수했고 source chain에서 소각을 확인 중입니다transferId, source tx, sender, amount
Delayedattestation이 지연되고 있어 완료까지 시간이 더 걸립니다delay timestamp, attester status
Attesteddestination mint를 준비 중입니다attestation event, recipient
Minteddestination chain에서 수령이 완료됐습니다destination mint tx, recipient balance
Failed전송이 실패했습니다. 지원팀이 복구 경로를 확인합니다fail reason, manual recovery owner
크게 보기
내부 상태사용자 문구운영자가 볼 증거
Burned전송 요청을 접수했고 source chain에서 소각을 확인 중입니다transferId, source tx, sender, amount
Delayedattestation이 지연되고 있어 완료까지 시간이 더 걸립니다delay timestamp, attester status
Attesteddestination mint를 준비 중입니다attestation event, recipient
Minteddestination chain에서 수령이 완료됐습니다destination mint tx, recipient balance
Failed전송이 실패했습니다. 지원팀이 복구 경로를 확인합니다fail reason, manual recovery owner

이 표가 없으면 사용자는 "돈이 사라졌다"고 느낀다. cross-chain 결제 UI의 핵심은 중간 상태를 숨기지 않고 정확하게 표현하는 것이다.

흔한 오해와 실패 시나리오

표 자료가로 스크롤 · 크게 보기 지원
오해실제로 확인할 것
source burn 성공을 결제 완료로 본다.destination mint 전까지는 pending 또는 processing 상태다.
attestation 지연을 예외로만 본다.지연은 정상 운영 상태 중 하나이며 timeout과 user copy가 필요하다.
double mint는 테스트할 필요가 없다고 본다.final state 재실행은 공급량 증가 사고로 이어질 수 있다.
transferId를 단순 로그 id로 본다.transferId는 message registry의 중복 방지 키다.
권한 없는 attest 실패를 생략한다.attester 권한은 cross-chain trust boundary를 대표한다.
크게 보기
오해실제로 확인할 것
source burn 성공을 결제 완료로 본다.destination mint 전까지는 pending 또는 processing 상태다.
attestation 지연을 예외로만 본다.지연은 정상 운영 상태 중 하나이며 timeout과 user copy가 필요하다.
double mint는 테스트할 필요가 없다고 본다.final state 재실행은 공급량 증가 사고로 이어질 수 있다.
transferId를 단순 로그 id로 본다.transferId는 message registry의 중복 방지 키다.
권한 없는 attest 실패를 생략한다.attester 권한은 cross-chain trust boundary를 대표한다.

실습 과제

  1. 컨트랙트CCTP flow 시뮬레이션: requestTransfer, markDelayed, attest, mint, fail을 상태 전이표로 정리한다.
  2. 운영복구 경로 검수: Burned 또는 Delayed 상태가 일정 시간 이상 지속될 때 retry, manual recovery, user notification을 어떻게 처리할지 쓴다.
  3. 클라이언트사용자 상태 문구 작성: 위의 5개 상태마다 사용자에게 보여줄 짧은 문구와 운영 로그 필드를 작성한다.
  4. 백엔드capstone 연결: final checkout 설계에서 cross-chain payment를 Paid, PendingAttestation, Minted, Failed 같은 제품 상태로 어떻게 매핑할지 정한다.

완료 기준

  1. 세 단계 상태가 구현되거나 문서화됐다.
  2. timeout/retry 정책을 만들었다.
  3. 사용자 상태 문구를 작성했다.

근거 자료

Final checkpoint

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

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

  • 세 단계 상태가 구현되거나 문서화됐다.
  • timeout/retry 정책을 만들었다.
  • 사용자 상태 문구를 작성했다.

학습 자료 근거

CCTP 시뮬레이터 랩
이 LMS 레슨의 개념, 예시, 과제 구성을 잡는 데 사용한 근거 문서.
내부 참고 문서
Circle CCTP Documentation
https://developers.circle.com/cctp