2026년 8월 13일 기준, HKDAP(Anchorpoint)에 대한 BlockSec 보안 및 컴플라이언스 리뷰.
TL;DR. 우리는 이더리움 메인넷에 라이브로 배포된, 홍콩에서 발행된 최초의 규제 스테이블코인인 HKDAP의 배포 계약을 검토했으며, 이는 프로덕션 준비 상태가 아닙니다. KYC 및 취소(revocation) 통제는 작성된 대로 작동하지 않으며, 거버넌스는 단일 키가 발행, 소각, 동결을 수행할 수 있을 정도로 집중되어 있고, 여러 온체인 속성들이 HKMA 자체의 가이드라인과 충돌합니다. 그 아래에는 공통된 흐름이 있습니다: 계약은 이미 생태계가 제공하고 대규모로 감사(audit)해 온 프리미티브들(멀티시그, 접근 제어 계층, 타임락, ERC-20 자체)을 처음부터 다시 만들어냈으며, 아래에서 다룰 결함들 대부분은 표준 컴포넌트를 재사용하는 부분이 아니라 그 커스텀 장치(machinery) 안에 존재합니다. "Beta Access" 라벨은 이 격차를 좁혀주지 않습니다.
2026년 8월 12일, Anchorpoint는 HKDAP의 1단계를 출시했습니다. 이는 홍콩 달러 스테이블코인입니다. 이는 중요한 출시입니다. Standard Chartered Bank (Hong Kong)가 HKT 및 Animoca Brands와 함께 주도하는 합작 투자사인 Anchorpoint는 36개의 신청 기관 중 HKMA가 부여한 단 두 개의 스테이블코인 발행자 라이선스 중 하나를 보유하고 있으며, HKDAP는 홍콩의 스테이블코인 조례(Stablecoins Ordinance)에 따라 발행된 최초의 스테이블코인 중 하나입니다.
규제 은행의 대부분의 제품과 달리, HKDAP는 직접 검사할 수 있습니다. 이는 이더리움 메인넷에서 실행되며, 계약 소스는 Etherscan에서 검증되었습니다. 이는 유용한 특성입니다: 이런 방식으로 발행된 스테이블코인의 경우, 발행, 전송, 동결을 관리하는 규칙은 문서에 기술되어 있는 것이 아니라, 누구나 읽을 수 있고 작성된 대로 정확히 실행되는 코드로 구현되어 있습니다. 라이선스는 하나의 주장이며, 배포된 계약은 그 주장의 구현체이며, 이는 공개되어 있습니다.
이것이 구체적인 검토를 가능하게 하며, 이것이 바로 우리가 한 일입니다. 우리는 배포된 계약을 두 축으로 검토했습니다. 첫째, 소프트웨어로서: 이는 정확하고 프로덕션 수준인가? 둘째, 규제 대상 스테이블코인으로서: 그 온체인 동작이 HKMA의 라이선스 스테이블코인 발행자 감독에 관한 가이드라인과 일치하는가?
두 축 모두에서 결과는 일관됩니다. 계약에는 작성된 대로 작동하지 않는 컴플라이언스 통제를 포함한 여러 기능적 결함이 있습니다. 그 거버넌스는 매우 집중되어 있으며, 여러 고위험 작업이 단일 키로 실행 가능합니다. 그리고 그 온체인 속성 중 여러 개가 HKMA 가이드라인의 특정 조항과 충돌합니다. 우리의 평가는, 베타 상태임에도, 이 계약이 커머셜 스테이블코인에 요구되는 품질 기준을 충족하지 못한다는 것입니다.
이 글의 나머지 부분은 2026년 8월 13일 기준으로 그 검토를 제시합니다. 우리의 검토는 공개적으로 배포된 코드와 관찰 가능한 온체인 사실에 기반합니다. 우리는 블록체인이 확인할 수 없는 사안, 예를 들어 준비금(reserve) 뒷받침이나 오프체인 키 보관 등에 대해서는 어떠한 주장도 하지 않습니다.
우리가 계약을 찾은 방법
우리는 토큰 목록이 아니라 발행자로부터 시작했습니다. Anchorpoint의 회사 존재는 anchorpoint.hk에 있는 사이트로 연결됩니다. Beta Access 페이지에는 배포 정보가 명시되어 있습니다: 이더리움 메인넷, 프록시 0x87622385F960fcCB3121d6D0A9513bd1D9Bed6cA, 그 소스는 Etherscan에서 검증되어 있습니다.
이로부터 우리는 전체 시스템을 온체인에서 매핑했습니다: 토큰 프록시와 그 구현체(ControllableAHKD, 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc), 이를 관리하는 거버넌스 계약(0x47dd47a776902bee03abdd5aeff43f41b0ffbc2b), 최상위 역할 레지스트리(0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c), 그리고 각각 자체 거버넌스 계약을 가진 다섯 개의 컴플라이언스 모듈. 아래의 모든 관계는 소스만으로 추론한 것이 아니라 스토리지 슬롯을 읽고 메인넷에서 뷰 함수를 호출하여 검증했습니다.
계약의 모습
HKDAP의 세 계층: 거버넌스 통제 평면이 토큰을 관리하며, 토큰은 매 전송마다 다섯 개의 컴플라이언스 모듈에 질의합니다.

HKDAP의 토큰은 OpenZeppelin의 표준 ERC-20 참조 구현을 사용하지 않습니다; 그 ERC-20 로직은 처음부터 작성되었습니다(아래에서 다룰 여러 버그가 여기서 나옵니다). 시스템은 세 개의 계층으로 구성됩니다:
- 토큰.
ControllableAHKD, 업그레이드 가능한 프록시 뒤에 위치. 발행, 소각, 일시정지, 강제 파기 기능을 가진 통제된 ERC-20이며, 컴플라이언스 검사가 모든 전송에 연결되어 있습니다. - 자체 개발한 M-of-N 거버넌스 엔진. 모든 특권 작업(업그레이드, 발행, 소각, 일시정지, 블랙리스트, 동결, 컴플라이언스 모듈 변경)은 단순한 멀티시그가 아니라 요청, 승인, 실행 절차(ceremony)를 거칩니다.
- 다섯 개의 컴플라이언스 모듈. 블랙리스트, 동결, KYC 활성화 서비스, 예치 및 상환 화이트리스트. 각 모듈은 자체적으로 프록시이며 자체 통제 권한(control-authority) 계약에 의해 관리됩니다.
구조적으로, 이 동일한 "통제 권한 + 프록시 + 구현체" 유닛은 여섯 번 반복되며(토큰 + 다섯 개 모듈), 이 여섯 개의 통제 권한은 모두 단 하나의 레지스트리에서 역할을 확인(resolve)합니다. 시스템 내의 모든 통제는 궁극적으로 이 하나의 레지스트리에 수렴하며, 그 안의 역할을 재작성할 수 있는 권한은 매우 소수의 서명자 키로 귀결됩니다. 이는 아래에서 자세히 보여드립니다.
파트 1: 보안 및 버그
이 파트의 결과는 아래에 요약되어 있습니다; 각 행은 명시된 섹션에서 상세히 다룹니다.
| 영역 | 결과 | 위치 | 영향 |
|---|---|---|---|
| 1.1 KYC 및 취소 통제 실패 | KYC 취소는 죽은 코드(dead code) | TokenHolderActivationServerLibrary.sol:221-235 |
isActive는 제공자 등록 취소를 무시함(루프는 절대 실행되지 않으며, ==를 = 대신 사용); 페일-오픈(fail-open) 상태. |
| 검증자 등록 취소가 INACTIVE로 설정되지 않음 | TokenHolderActivationServer.sol:504-511 |
"등록 취소된" 검증자가 여전히 지갑을 온보딩하고 비활성화할 수 있음; 두 번째 호출은 revert됨. | |
| KYC 증명이 온체인에서 검증되지 않음 | TokenHolderActivationServer.sol:575-578 |
증명이 전달되지만 폐기됨; 빈 문자열을 포함한 모든 증명이 통과됨. | |
| 무료 전송 면제를 우회하여 분할 가능 | ControllableAHKD.sol:337-535 |
freeTransferLimit은 호출당 확인되며 누적되지 않음; 비-KYC 활동에 대한 상한선으로 사용될 경우, 한도 미달의 하위 전송으로 분할하여 우회 가능. |
|
| 1.2 거버넌스가 과도하게 집중됨 | 고위험 작업이 단일 서명 | authorizationMatrix; 발행 트랜잭션 0xa7e53c…b33d7 |
발행 / 소각 / 동결 / KYC 비활성화(role C), 일시정지 / 파기(role D), 블랙리스트 / 동결 해제(role F) 각각 단일 키로 실행됨. |
| 하나의 키 쌍(A + B)이 모든 것을 업그레이드함 | authorizationMatrix |
토큰과 다섯 개 모듈 모두의 upgradeTo는 role A + B; 두 사람이 어떤 구현체든 교체 가능. |
|
| 동일한 쌍이 모든 역할을 재작성함 | 레지스트리 0xa728… authorizationMatrix |
레지스트리의 grantRole / revokeRole도 role A + B(ADMIN_ROLE은 계약 자체만 보유; DEFAULT_ADMIN_ROLE = address(0)); 외부 키는 역할을 직접 변경할 수 없음. |
|
| 하나의 주소가 여섯 개의 역할을 보유 | 역할 레지스트리 0xa728… |
0x2f7f00…는 role C뿐만 아니라 ADMIN_TOKEN_HOLDER와 네 개의 감사(auditor) 역할 모두를 보유; 실행과 감사가 중복됨. |
|
| 취소가 즉시 이뤄지지 않음; 타임락 없음 | HybridControlEngine.sol:158-227 |
카운트된 서명은 역할이 취소된 후 재검증되지 않으며, 지연 없이 최종 서명과 함께 원자적으로 실행됨. | |
| 엔진의 감사 추적(audit trail)이 신뢰할 수 없음 | HybridControlEngine.sol:36-129, 177-196 |
evtApprove는 서명자로 address(0)을 발행함; 활성 요청 목록은 nonce 0을 실제 id와 빈 마커 모두로 사용하여, 역순 탐색 시 첫 요청을 놓침. |
|
| 1.3 일관성 없는 전송 검사 | transfer와 transferFrom이 서로 다른 규칙을 적용 |
ControllableAHKD.sol:313-502 |
화이트리스트 조기 반환(early-return)은 transferFrom을 통해서만 KYC를 우회함; checkingMode는 다른 당사자를 검사함; 동일한 전송이 다르게 통제됨. |
| 1.4 프로덕션 이전 빌드의 흔적 | 디버그 로그, 잘못된 주석, 이름/해시 불일치, 테스트 업로드됨 | UpgradeableProxy.sol:115-119; ControllableAHKD.sol:25 |
프록시 폴백에 console.log(영구적인 가스 소모); 프록시 주석이 코드와 상반됨; 역할 이름/해시 불일치; 테스트를 포함한 117개 파일 업로드; 옵티마이저 실행 수 = 0. |
| 1.5 단 한 번의 업그레이드가 결함을 그대로 남김 | 단일 업그레이드 추적 | 트랜잭션 0x742372…85136, 0xa7a400…630c |
배포 4월 28일 → 업그레이드 7월 10일; A + B 절차의 두 트랜잭션은 한 블록 차이(~12초); 파트 1의 모든 결함이 설치된 구현체에 있음. |
1.1 KYC 및 취소 통제는 작성된 대로 작동하지 않는다
규제 하에서, HKDAP가 서비스하는 B측(기관) 사용자들은 KYC를 통과해야 합니다. 계약에서, 이는 최소 두 가지를 수행해야 함을 의미합니다: 전송 시 KYC를 강제하여 KYC를 통과하지 못한 지갑이 차단되도록 하는 것, 그리고 신원 제공자(identity provider)나 보유자가 제거될 때 접근을 취소하는 것. HKDAP에서, 이 경로는 세 개의 독립적인 지점에서 깨져 있습니다.
KYC 취소는 죽은 코드입니다. 토큰은 KYC 모듈의 isActive(address)를 호출하여 전송을 게이트합니다. isActive는 두 가지를 수행해야 합니다: 첫째, 지갑당 카운터로부터 기본값을 설정하여, 해당 지갑이 비활성화된 것보다 더 많이 KYC 활성화된 경우 활성 상태로 만드는 것; 둘째, 그 지갑을 보증한 신원 제공자가 등록 취소된 경우 그 값을 false로 좁히는 것. 이 두 가지는 두 종류의 취소를 다룹니다: 한 보유자의 KYC를 취소하는 것(카운터)과, 전체 제공자를 취소하여 그것이 온보딩한 모든 지갑이 함께 떨어지도록 하는 것(루프). 그런데 오직 첫 번째만 실행됩니다.
// contracts/hce/erc20/libs/TokenHolderActivationServerLibrary.sol
221 active = ( tokenHolderRegistration[_address].deactivationCount < tokenHolderRegistration[_address].activationCount ) ;
223 uint256 entryCount = 0 ;
224 uint256 index = chainedItemList.firstEntry ; // 0 if such ChainedList is empty
226 while ( entryCount > chainedItemList.entryCount && active ) {
227 ChainedListLibrary.ChainedItem storage chainedItem = identityProvidersByStatuss[index] ; // deregistered provider
230 // NDLR: .... not too sure about that one to be frank
231 active == ( tokenHolderKYCProofDirectoryByProvider[_address][identityProviders[chainedItem.objectId].identifier] == 0 ) ;
233 entryCount++ ;
234 index = chainedItem.pointNext ;
235 }
221행은 실제 대입(=)이며, 이것이 유일하게 실제로 적용됩니다: active는 deactivationCount < activationCount가 됩니다. 루프(226-235행)는 그 값을 좁혀야 하는 부분이지만, 두 가지 이유로 실패합니다. 226행의 조건 entryCount > chainedItemList.entryCount는 0 > N이므로 본문이 절대 실행되지 않습니다; 그리고 실행된다 해도, 231행은 결과가 폐기되는 비교 연산자 ==를 사용하는데, 여기서는 =로 대입해야 합니다. 따라서 isActive는 카운터 비교값만 반환하며 제공자 취소를 완전히 무시합니다. 이는 페일-오픈입니다: 손상된 KYC 제공자를 등록 취소해도 그것이 온보딩한 지갑들의 거래를 막지 못합니다. 그리고 unregisterVerifier가 이 카운터들을 건드리지 않기 때문에, 등록 취소된 제공자의 지갑들은 양수 카운트를 유지하며 계속 활성 상태입니다.
제공자 취소는 다른 쪽에서도 마찬가지로 깨져 있습니다. unregisterVerifier는 연결 리스트 노드만 이동시킬 뿐, 제공자의 디렉토리 상태를 INACTIVE로 설정하는 것은 절대 하지 않습니다:
// contracts/hce/erc20/modules/TokenHolderActivationServer.sol
504 function _unregisterVerifier(string calldata _verifierId) internal {
505 _removeToList(_verifierId, ACTIVE_IDENTIY_PROVIDER, "60a");
507 uint256 _ipIdx = identityProviders.length - 1;
508 uint256 _newIdx = identityProvidersByStatuss.length + 1;
510 _addToList(_ipIdx, _newIdx, INACTIVE_IDENTIY_PROVIDER, _verifierId, "60b");
511 }
상태가 ACTIVE로 유지되기 때문에, "등록 취소된" 검증자는 여전히 보유자를 등록하고 비활성화할 수 있으며, 제공자를 재활성화하도록 의도된 분기는 도달할 수 없고, 두 번째 unregisterVerifier 호출은 언더플로우가 발생해 revert됩니다.
KYC 증명은 온체인에서 절대 검증되지 않습니다. 인가된 검증자가 registerOrRenew를 통해 지갑을 등록하거나 갱신할 때(현재 활성 검증자만 호출할 수 있도록 게이트됨), 흐름은 제출된 증명을 제공자의 스킴에 대해 검증해야 하는 _checkKYCProof에 도달합니다. 인터페이스에 따르면, 그 증명은 "오라클에 대한 URI, 또는 검증 가능한 소스로부터의 서명된 해시"입니다. 이 함수는 그것을 무시합니다:
// contracts/hce/erc20/modules/TokenHolderActivationServer.sol
575 function _checkKYCProof( string memory _ipIdentifier, string memory, string memory _rcCode ) internal view returns ( bool isVerified ) {
576 require( identityProviderDirectory[_ipIdentifier].status == ACTIVE_IDENTIY_PROVIDER, string.concat(ERROR_404, _rcCode, "41b")) ;
577 return true ;
578 }
증명은 실제로 이 함수에 도달합니다. registerOrRenew는 제출된 kycProof를 _registerOrRenew를 통해 아래로 전달하며 두 번째 인자로 넘깁니다. 그러나 575행에서 그 인자는 이름조차 없는 매개변수로 들어가며, 함수 위의 문서 주석에서도 이를 생략하고, 본문은 절대 이를 읽지 않습니다. 이 함수는 제공자가 활성 상태인지만 확인하고 577행에서 true를 반환하므로, 증명은 검사되지 않고 폐기됩니다. 이는 외부인의 우회가 아닙니다(오직 활성 검증자만 여기에 도달할 수 있으므로), 그러나 온체인에서 계약은 KYC 증거에 대한 검증을 전혀 수행하지 않으므로, 그 무결성은 전적으로 오프체인 검증자에게 의존합니다. 손상되거나 부주의한 검증자는 빈 문자열을 포함한 어떠한 증명으로도 어떤 지갑이든 활성화할 수 있습니다.
결국 이 세 가지는 핵심 컴플라이언스 속성인, 접근을 게이트하고 취소하는 능력이 작성된 대로 작동하지 않음을 의미합니다.
이 세 가지를 넘어서, 관련된 취약점이 하나 더 있습니다: 무료 전송 면제는 우회를 위해 분할될 수 있습니다. 토큰에는 freeTransferLimit이 있으며, 이보다 낮은 전송은 isActive(KYC) 검사를 스킵합니다. 그러나 이 한도는 오직 현재 전송의 금액에만 비교됩니다; 계약은 주소별 또는 기간별 누적 합계를 보관하지 않습니다. 따라서 이 한도가 비-KYC 활동에 대한 상한선으로 사용될 경우, 보유자는 이를 각각 한도 미달의 반복 전송으로 분할하여 임의의 총액을 이동시킬 수 있으며, 이는 그 상한선을 무효화합니다.
1.2 거버넌스가 과도하게 집중되어 있으며, 고위험 작업이 단일 서명이다
우리는 온체인에서 모든 역할과 모든 역할 보유자를 열거했습니다. 세부 사항에 앞서 두 가지가 눈에 띕니다.
첫째, M-of-N 절차를 인가하는 역할들은 읽을 수 있는 이름이 없습니다. 배포된 구성에서 이들은 32바이트 해시로만 나타나며, 그중 어느 것도 검증된 소스의 명명된 역할 상수와 일치하지 않습니다(SUPPLY_CONTROLLER_ROLE과 같은 명명된 역할은 서명자가 아니라 계약 자체가 보유합니다). 우리는 여섯 개의 서명자 역할을 A부터 F까지로 표시합니다. 시스템 내 가장 강력한 역할들이 불명확한 식별자라는 사실 자체가 하나의 취약점입니다: 이는 거버넌스를 명명된 역할이 있을 때보다 검토하기 어렵게 만듭니다.
둘째, 보유자의 수가 적습니다. 아래 표는 온체인 역할 레지스트리로부터 읽은 것입니다; 주소는 축약되어 있습니다.
| 역할(우리의 표시) | 온체인 해시 | 보유자 | 무엇을 인가하는가 |
|---|---|---|---|
| A | 0x37f0f656… |
0x747356d525bf47f495af7c6e42f3ab9b8ba87a4b |
upgradeTo / changeAdmin / setControlAuthority의 첫 번째 서명 |
| B | 0x95c27f81… |
0x8117a657df7a1c399231bfe26a096eb8f8ad5973, 0x5d9d9b28e83382e694916b0feeaf7a9badbb659c, 0xebb73f78e253539acceb4e6cc287095788eee5d4 |
거의 모든 두 서명 작업의 두 번째 서명 |
| C | 0xfa2fe896… |
0x2f7f00cc5334fe2861e485ff610f74890a0316ed |
mintToDeposit, burnFrom, freeze, KYC 비활성화 (단일) |
| D | 0x3dce3265… |
0x5092af62a1625fa57404557d3ad417474f3f494c |
pause, destroyBlackFunds, 예치 및 상환 주소 등록/등록 취소, registerVerifier (단일) |
| E | 0xfd21a76d… |
0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2 |
컴플라이언스 모듈 변경 (setBlacklistServer 등) |
| F | 0x510ac1ff… |
0x7daabe5092d3feea5cc19ddbb5380a4eac38f89c |
addBlackList, unfreeze (단일) |
몇 가지 사항이 표에서 도출됩니다.
단일 서명자 고위험 작업. 대부분의 고위험 작업은 정족수 1인 단일 역할만을 요구합니다. 아래 표는 토큰의 거버넌스 계약과 다섯 개 모듈 거버넌스 계약들의 authorizationMatrix로부터 온체인에서 읽은 것입니다(+ B 두 번째 서명이 표시되지 않는 한 단일 서명입니다):
| 작업 | 관리 대상 | 필요 서명 |
|---|---|---|
mintToDeposit, burnFrom |
토큰 | C x1 |
freeze, batchFreeze |
동결 모듈 | C x1 |
deactivate, adminDeactivate (KYC) |
KYC 모듈 | C x1 |
pause, destroyBlackFunds |
토큰 | D x1 |
register, unregister (예치 / 상환) |
디렉토리 모듈 | D x1 |
registerVerifier, unregisterVerifier |
KYC 모듈 | D x1 |
addBlackList, batchBlackList |
블랙리스트 모듈 | F x1 |
unfreeze |
동결 모듈 | F x1 |
removeBlackList |
블랙리스트 모듈 | F x1 + B x1 |
setBlacklistServer, setFreezingServer, setCheckingMode |
토큰 | E x1 + B x1 |
| 디렉토리 서버 및 공급 한도 설정자 | 토큰 | D x1 + B x1 |
upgradeTo / changeAdmin / setControlAuthority(토큰 및 모든 모듈), unpause |
토큰 + 모듈 | A x1 + B x1 |
발행, 동결, KYC 비활성화는 (모두 role C 하에서) 단일 서명입니다; 일시정지, 파기, 디렉토리 변경, 검증자 등록은 role D 하에서 단일 서명입니다; 블랙리스트 등록과 동결 해제는 role F 하에서 단일 서명입니다. 오직 업그레이드와 설정 변경만이 두 번째 서명을 필요로 합니다. 비대칭성에 주목하세요: freeze와 addBlackList는 하나의 서명이 필요하지만 removeBlackList는 두 개의 서명이 필요하며, 따라서 계정을 제한하는 것이 해제하는 것보다 더 쉽습니다.
이는 단지 매트릭스를 읽은 것에 불과한 것이 아닙니다; 실제 발행 트랜잭션에서 관찰할 수 있습니다. 작성 시점 기준 가장 최근의 발행 트랜잭션인 트랜잭션 0xa7e53c…b33d7는 유일한 role-C 계정(0x2f7f00cc5334fe2861e485ff610f74890a0316ed)이 토큰의 거버넌스 계약으로 보낸 단일 트랜잭션입니다. 이는 request를 호출하며, 동일한 트랜잭션이 정족수에 도달하여 address(0)으로부터의 발행 Transfer를 발생시킵니다. 별도의 승인 트랜잭션도 없고 두 번째 서명자도 없습니다. 발행이 요청자 자신의 트랜잭션 안에서 완료되기 때문에, 하나의 키가 발행을 요청하고 실행했습니다.
또한 엔진 어디에도 타임락이 없습니다. 마지막 필요 서명이 도착하는 순간, 해당 작업은 같은 트랜잭션 안에서 실행되며, 검토, 취소, 또는 이의 제기가 이루어질 수 있는 지연 시간이 없습니다; 엔진은 executedAt 타임스탬프를 기록하지만 이를 절대 확인하지 않습니다. 따라서 두 서명이 필요한 작업조차 두 번째 키가 서명하는 즉시 즉시 완료됩니다.
한 쌍의 키가 모든 것을 업그레이드합니다. 토큰과 다섯 개 컴플라이언스 모듈 모두를 업그레이드하는 것은 동일한 요건인 A 더하기 B를 사용합니다. A는 하나의 계정이고 B는 역할을 공유하는 세 개의 계정이므로, 두 사람이 시스템 내 어떠한 구현체든 교체할 수 있습니다.
동일한 쌍이 역할 테이블도 통제합니다. 역할은 오직 레지스트리(HybridControlledAuthority, 0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c)에서 자체 절차를 통해서만 부여되고 취소될 수 있습니다; 외부 키는 이를 직접 변경할 수 없습니다. 그리고 온체인에서 읽은 그 authorizationMatrix는 업그레이드와 동일한 서명을 역할 변경에 대해서도 요구합니다: role A 더하기 role B. 따라서 어떠한 구현체든 교체할 수 있는 두 사람은 role C에 키를 추가하고, 기존 보유자를 제거하며, 전체 역할 테이블을 재작성할 수도 있습니다. 이 두 서명 게이트는 위의 단일 서명 작업들보다는 낫지만, 시스템의 루트에 있는 것으로서는 여전히 낮은 기준입니다. role A는 중복이 없는 단일 계정이기 때문입니다.
하나의 주소, 여섯 개의 역할. role C의 보유자(0x2f7f00cc5334fe2861e485ff610f74890a0316ed)는 명명된 ADMIN_TOKEN_HOLDER_ROLE(KYC 검증자 관리)도 보유하며, 네 개의 감사자 역할(BLACKLIST_AUDITOR_ROLE, FREEZING_AUDITOR_ROLE, WHITELIST_AUDITOR_ROLE, AFL_TOKEN_AUDITOR_HOLDER_ROLE) 모두의 구성원입니다. 그 하나의 키가 손상되면 발행, 동결, KYC 관리 권한이 동시에 상실됩니다.
감사자 역할 자체에도 두 가지 문제가 있습니다. 첫째, 이는 컴플라이언스 목록에 대한 읽기 전용 게터를 게이트하는데, 이는 누가 이를 읽을 수 있는지 제어하기 위한 것으로 보이지만, 공개 체인에서는 이는 의미가 없습니다: 기본 스토리지는 누구나 읽을 수 있기 때문에(스토리지 슬롯을 읽는 것이 우리가 이 시스템을 매핑한 방법입니다), 그 목록은 어차피 공개된 것이며, 이 제한은 설계가 공개 체인에서 실행된다는 점을 고려하지 않았음을 보여줍니다. 둘째, 집중: 네 개의 감사자 역할 모두 동일한 23개 계정에 위치하며, 실행 역할 A, C, D, F의 보유자들이 그 안에 포함되므로, 발행, 소각, 동결, 블랙리스트를 수행하는 동일한 키들이 그러한 작업을 검토해야 할 그룹에도 속해 있습니다.
취소가 즉시 이뤄지지 않습니다. 절차 엔진(ceremony engine)에서, 서명자의 역할은 한 번만 확인되고 정족수가 감소됩니다; 이전 서명자들은 절대 재검증되지 않으므로, 이후 역할을 취소해도 이미 카운트된 표를 철회할 수 없습니다:
// contracts/hce/HybridControlEngine.sol
158 for ( uint256 i=0; i < approvalRequest.ceremony.length; i++ ) {
159 if ( !_hasBeenMandated && !approvalRequest.ceremony[i].completed &&
160 IControlAuthority(authorityContract).hasRole( approvalRequest.ceremony[i].expectation.authority, _signer ) ) {
161 _hasBeenMandated = true ;
163 approvalRequest.ceremony[i].expectation.quota-- ;
167 approvalRequest.ceremony[i].completed = _approvalIntent && approvalRequest.ceremony[i].expectation.quota == 0 ;
168 }
마지막으로, 엔진 자체의 감사 추적(audit trail)은 신뢰할 수 없습니다. 매 승인마다, evtApprove 이벤트는 실제 승인자가 아니라 서명자로 address(0)을 발행합니다; 실제 서명자는 트랜잭션 발신자와 내부 기록에만 남아있으므로, 이벤트 로그로는 누가 요청을 승인했는지 귀속시킬 수 없습니다. 그리고 활성 요청 목록은 nonce 0을 실제 요청 id와 그 빈 마커 모두로 사용하므로, 목록을 역순으로 순회하는 모니터링이나 승인 도구는 nonce 0의 요청을 놓칩니다. 어느 쪼이든 치명적이지는 않지만, 깨끗한 감사 추적을 필요로 하는 규제 시스템에서는 둘 다 이를 감소시킵니다.
1.3 전송 통제는 transfer와 transferFrom 사이에서 일관성이 없다
두 함수 간 차이의 일부는 예상되는 것입니다. transferFrom에서 시작자인 msg.sender는 자금의 출처가 아니라 승인된 지출자(spender)이므로, 코드는 모든 모드에서 실제 출처 from을 명시적으로 확인합니다; transfer는 그렇게 할 필요가 없는데, 여기서는 msg.sender가 출처이기 때문입니다. 그러한 조정은 합리적입니다. 그러나 나머지 두 가지 차이는 시작자에 의해 설명되지 않으며, 이는 동일한 경제적 행위가 다른 규칙에 의해 통제되도록 남깁니다.
첫째, 예치 및 상환 화이트리스트는 오직 transferFrom 경로에서만 참조되며, 여기서 멤버십은 isActive(KYC) 검사를 우회하는 조기 반환(early return)을 트리거합니다. transfer는 절대 이들을 참조하지 않습니다. 수신자가 화이트리스트에 등록되었는지는 누가 전송을 시작했는지와 무관하므로, 동일한 수신자가 transfer를 통해서는 KYC를 적용받지만 transferFrom을 통해서는 이를 건너뛸 수 있습니다:
// contracts/hce/ControllableAHKD.sol : transfer(), "Source" 모드는 발신자만 게이트함
323 else if ( activateMode == ActivateMode.Source )
325 _transferCheckSourceMode(_value, "80h");
// contracts/hce/ControllableAHKD.sol : transferFrom() -> _transferFromCheckDestinationMode(_to)
428 try depositDirectoryServer.isRegistered(_to)
429 returns ( bool isIt, IWhitelistServer.WalletAddress memory ) {
430 if ( isIt ) { return ; }
436 try redemptionDirectoryServer.isRegistered(_to)
437 returns ( bool isIt, IWhitelistServer.WalletAddress memory ) {
438 if ( isIt ) { return ; }
444 try tokenHolderActivationServer.isActive(_to) returns ( bool isIt ) {
445 require(isIt || _value < freeTransferLimit, string.concat(ERROR_404, _rcCode, "/86a" ) ) ;
둘째, checkingMode는 두 함수에서 서로 다른 것을 의미합니다. transfer에서 "Source" 모드는 발신자를 확인합니다; transferFrom에서 "Source" 모드는 지출자(msg.sender)를 확인하며, from은 모든 모드에서 확인됩니다. 따라서 단일 설정 값이 진입점에 따라 두 가지 다른 정책을 적용합니다.
결과적으로, 규제 대상 토큰의 전송 통제는 어떤 함수가 사용되었는지에 따라 달라지며, 이는 이를 추론하기 어렵게 만들고, 설정에 따라 회피 가능하게 만듭니다.
1.4 프로덕션 이전 빌드의 흔적
구체적인 로직 버그를 넘어서, 코드베이스의 여러 특성은 프로덕션 이전 빌드가 메인넷에 배포되었음을 나타냅니다.
프로덕션 내의 디버그 로깅. hardhat/console.log 호출이 프록시의 폴백(fallback) 내부를 포함하여 전체에 남아 있으며, 이는 모든 사용자 트랜잭션에서 실행됩니다. 프록시 자체가 업그레이드 가능하지 않기 때문에, 이 오버헤드는 영구적입니다:
// contracts/proxy/UpgradeableProxy.sol
115 function _beforeFallback() internal virtual override {
116 console.log("iam %s, entering fallback as %s", address(this), msg.sender) ;
117 // require(msg.sender != _getAdmin(), "[UPY]404/02");
118 super._beforeFallback();
119 }
코드와 반대되는 내용을 설명하는 프록시 헤더 주석. 동일한 파일에는 관리자(admin)가 절대 구현체로 폴백할 수 없다고 명시하는 OpenZeppelin의 TransparentUpgradeableProxy 문서가 실려 있습니다. 이 계약은 의도적으로 그 반대를 수행합니다: 117행의 가드가 주석 처리되어 있으며, 관리자는 실제로 폴백합니다. 그 주석을 믿는 검토자는 신뢰 경계(trust boundary)를 잘못 모델링하게 됩니다.
해시와 일치하지 않는 역할 이름. "고위험" 역할은 토큰과 모듈에서 동일한 상수 이름을 사용하지만 다른 keccak 문자열로 선언되어 있어, 두 개의 다른 역할을 생성합니다:
// contracts/hce/ControllableAHKD.sol (토큰) hash = 0xd2b9...
25 bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATED_RISK_OWNER_ROLE') ;
// contracts/hce/erc20/modules/BlackListServer.sol (모듈) hash = 0x4de4...
18 bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATEDRISK_OWNER_ROLE') ;
배포는 각 모듈이 자체 사본을 보유하기 때문에만 문제를 피합니다; 두 번째 역할 이름(AFL_TOKEN_HOLDER_AUDITOR_ROLE)도 동일한 방식으로 뒤바뀌어 있습니다.
기타 흔적. 프록시의 검증 번들은 테스트 스위트를 포함한 117개의 파일을 공개 탐색기에 업로드했으며, 이는 독자에게 내부 테스트와 엣지 케이스를 그대로 넘겨줍니다. 가장 최근의 업그레이드는 오직 컴파일러 경고 정리만 변경했으며, 그 과정에 제3자 감사가 없었습니다. 그리고 옵티마이저는 실행 수 0으로 설정되어 있으며, 이는 대량으로 사용되는 토큰의 핫 패스(hot path)를 더 저렴하게 만드는 것이 아니라 더 비싸게 만듭니다.
이 중 어느 것도 개별적으로는 심각하지 않습니다. 그러나 함께 보면, 이는 코드가 메인넷에서 자산을 보유하는 계약에 요구되는 출시 규율(release discipline)을 거치지 않았음을 나타냅니다.
1.5 계약의 단 한 번의 업그레이드, 추적하기
프록시는 한 번 업그레이드되었습니다. 이는 2026년 4월 28일 구현체 0x8ff12fe3bed22d9e40afb4b98ef4dee28d94699e로 배포되었고, 2026년 7월 10일 현재의 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc로 업그레이드되었습니다. 업그레이드가 온체인 거버넌스 작업이기 때문에, 우리는 누가 이를 승인했는지 정확히 볼 수 있습니다.
upgradeTo는 role A 더하기 role B를 요구합니다. 업그레이드는 인접한 블록에서 이루어진 두 개의 트랜잭션이었으며, 약 12초 간격이었습니다:
- 요청, role A,
0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2로부터, 블록 25500519에서 (트랜잭션0x742372…85136); - 승인, role B,
0x3795300b31429f9d37b0dc805528d9390ce87c50로부터, 블록 25500520에서 (트랜잭션0xa7a400…630c), 이는 정족수에 도달하여 동일한 트랜잭션 안에서 업그레이드를 수행했습니다.
두 가지가 눈에 띕니다. 첫째, 전체 두 서명 절차가 단일 블록 간격 내에 완료되었습니다. 요청부터 실행까지 한 블록이었으며, 변경이 라이브가 되기 전에 두 번째 서명자가 독립적으로 검토할 수 있는 시간대가 없었습니다.
둘째, 어느 서명자도 현재의 역할 레지스트리에서는 식별할 수 없습니다. 이후 역할이 회전(rotate)되었습니다: 요청자 0xa9315a…는 더 이상 role A를 보유하지 않습니다(현재는 role E를 보유합니다), 그리고 두 번째 서명자 0x3795300b…는 현재 어떤 역할도 보유하지 않습니다. 현재의 레지스트리를 읽는 것으로는 누가 업그레이드를 승인했는지 알 수 없으며, 오직 트랜잭션 기록만이 알려줍니다. 이는 "취소가 즉시 이뤄지지 않는다"는 속성을 다른 관점에서 본 것입니다: 역할은 이동하므로, 누가 무엇을 보유하는지의 스냅샷은 누가 무엇을 했는지에 대한 기록이 아닙니다.
그리고 그 업그레이드는 이 검토에서 다룬 결함들을 전혀 고치지 않았습니다. 이것이 설치한 구현체인 0xe42d38b0…는 파트 1 전체가 다루는 바로 그것입니다. 우리는 체인으로부터 업그레이드 전에 제3자 감사가 수행되었는지 알 수 없습니다; 우리가 말할 수 있는 것은, 만약 수행되었다면 파트 1의 결함들이 그것을 견뎌냈다는 것입니다.
파트 2: 홍콩의 스테이블코인 프레임워크와 일치하는가?
홍콩의 스테이블코인 조례는 2025년 8월 1일부터 시행되었으며, 라이선스 발행자는 HKMA의 라이선스 스테이블코인 발행자 감독에 관한 가이드라인 하에 감독됩니다. 우리는 스마트 계약이 자체적으로 충족할 수 있는 조항만 비교했습니다; 준비금 뒷받침, 보관, 오프체인 키 절차는 온체인 검토의 범위를 벗어납니다. 아래의 각 조항에 대해, 우리는 가이드라인이 요구하는 것, 계약이 수행하는 것, 그리고 둘이 어디서 벌어지는지를 명시합니다. 비교는 여기 요약되어 있으며 이어지는 섹션에서 상세히 다룹니다.
| HKMA 조항 | 요구사항 | 계약이 벌어지는 지점 | 판정 |
|---|---|---|---|
| 6.5.3 | 고위험 작업은 단독으로 수행되어서는 안 됨(멀티시그, 그리고 속도 제한이나 타임락과 같은 완화 조치) | 발행, 소각, 일시정지, 동결이 각각 단일 키로 실행됨; 타임락 없음(실행이 최종 서명과 원자적으로 이뤄짐) | 벌어짐 |
| 6.5.4 | 인가된 사람들 간 직무를 분리; 권한을 즉시 취소 | 하나의 계정이 여섯 개의 역할을 보유하여 실행과 감사가 중복됨; 카운트된 승인은 이후의 취소를 견뎌냄 | 벌어짐 |
| 6.5.5 | 모든 코드 변경에 대한 제3자 감사; 정확하고, 일관되며, 취약점이 없어야 함 | 파트 1의 결함이 배포된 구현체에 살아 있음; isActive와 제공자 취소는 이름이 나타내는 대로 작동하지 않음 |
벌어짐 |
| 컴플라이언스 통제의 유효성 | 블랙리스트, 동결, 화이트리스트, KYC 통제가 유효해야 함 | 배포된 코드에서 KYC 게이팅과 제공자 취소가 작동하지 않음 | 벌어짐 |
| 2.2.3 | 동결되거나 파기된 코인이 완전히 뒷받침되고 조정 가능(reconcilable)해야 함 | 파기(destroy)는 address(0)이 아니라 address(this)로의 Transfer를 발생시키며 발행 한도가 사용하는 순 발행 카운터를 그대로 남김; 이벤트로부터 재구성된 공급량이 벌어짐(drift) |
우려 |
6.5.3항: 고위험 작업은 단독으로 수행되어서는 안 됨
요구사항. 고위험 작업은 예를 들어 멀티시그 프로토콜과 같이 단일 당사자가 단독으로 수행할 수 없도록 설계되어야 하며, 가이드라인은 속도 제한 및 시간 지연(타임락) 통제와 같은 추가적인 완화 조치도 나열합니다.
계약이 수행하는 것. 거버넌스 계약의 authorizationMatrix로부터 읽은 것(전체 매트릭스는 1.2절의 표에 있습니다), 공급 및 긴급 작업은 각각 정족수 1인 단일 역할을 요구합니다:
| 작업 | 필요 서명 |
|---|---|
mintToDeposit |
ROLE_C x1 |
burnFrom |
ROLE_C x1 |
freeze |
ROLE_C x1 |
pause |
ROLE_D x1 |
destroyBlackFunds |
ROLE_D x1 |
벌어지는 지점. 발행, 소각, 일시정지, 동결은 각각 하나의 키로 실행될 수 있습니다. 이는 "단일 당사자가 단독으로 수행할 수 없어야 한다"는 요건을 충족하지 못합니다. 타임락도 없습니다: 1.2절에서 보였듯이, 정족수에 도달하면 작업은 같은 트랜잭션 안에서 실행되므로, 가이드라인이 명시한 완화 조치 중 하나인 시간 지연도 부재합니다. 계약은 실제로 공급 속도 제한(whenWithinRiskThresholds)을 구현하므로 그 완화 조치는 존재하지만, 이는 작업 자체에 대한 멀티시그 통제를 대체하지 못합니다.
6.5.4항: 직무 분리와 즉각적인 취소
요구사항. 서로 다른 작업은 서로 다른 인가된 사람들 간에 분리되어야 하며, 인가된 사람의 권한은 즉시 취소 가능해야 합니다.
계약이 수행하는 것. 하나의 외부 소유 계정이 여섯 개의 역할(발행, 동결, KYC 관리, 그리고 네 개의 감사자 역할 모두)을 보유하여, 실행과 감사 역할이 중복됩니다. 역할 변경 자체도 오직 role A 더하기 role B만을 요구하므로(1.2절 참조), 동일한 소규모 그룹이 누가 인가되는지를 결정하며 동시에 실행하고 업그레이드할 수 있습니다. 그리고 이미 카운트된 서명은 서명자의 역할이 이후 취소되더라도 재검증되지 않습니다.
벌어지는 지점. 직무는 분리되지 않고 집중되어 있으며, 취소는 즉시 이뤄지지 않습니다: 취소된 서명자의 이전 승인은 여전히 이후 실행에 카운트됩니다.
6.5.5항: 모든 코드 변경에 대한 감사; 정확성, 일관성, 취약점 없음
요구사항. 자격을 갖춘 제3자는 모든 코드 변경에 대해 스마트 계약을 감사해야 하며, 이들이 (i) 올바르게 구현되었는지, (ii) 의도된 기능과 일치하는지, (iii) 높은 신뢰 수준으로 취약점이 없는지를 확인해야 합니다.
계약이 수행하는 것. 현재의 구현체는 7월 10일 업그레이드로 설치된 것(1.5절에서 추적됨)이며, 파트 1의 결함이 이 안에 살아 있습니다.
벌어지는 지점. 우리는 오프체인에서 감사가 수행되었는지 여부를 알 수 없지만, 결과는 어느 쪽이든 그 기준을 충족하지 못합니다. isActive와 제공자 취소가 이름이 나타내는 대로 작동하지 않기 때문에, 조건 (i)과 (ii)는 충족되지 않으며; 파트 1의 결함이 배포된 코드에 존재하므로, (iii)도 충족되지 않습니다.
컴플라이언스 통제의 유효성
요구사항. 가이드라인의 생명주기 모델(블랙리스트, 동결, 화이트리스트, KYC)은 그러한 통제가 유효하다는 것을 전제로 합니다.
계약이 수행하는 것. 1.1절에서 보였듯이, KYC 게이팅과 제공자 취소는 배포된 코드에서 작동하지 않습니다.
벌어지는 지점. 작동해야 하는 통제가 작동하지 않습니다. 이는 형식적인 것이 아니라 실질적인 격차입니다.
2.2.3항: 동결되거나 파기된 코인은 완전히 뒷받침되고 조정 가능해야 함
요구사항. 집행 조치(enforcement action)에 의해 동결되거나 파기된 스테이블코인은 완전히 뒷받침되어야 하며, 공급과 준비금이 조정 가능해야 합니다.
계약이 수행하는 것. 규제 대상 스테이블코인에 대한 일반적인 구제 흐름은 나쁜 주소의 자금을 소각한 후 나중에 피해자에게 동일한 금액을 별도의 발행으로 재발행하는 것입니다(이는 USDT의 destroyBlackFunds와 issue가 작동하는 방식입니다). HKDAP의 파기는 _totalSupply를 감소시키는데, 이는 소각이지만, balances[address(this)]에는 절대 크레딧하지 않고, address(0)이 아니라 address(this)로의 Transfer를 발생시키며, 발행 한도가 사용하는 순 발행 카운터를 건드리지 않고 그대로 남깁니다:
// contracts/hce/ControllableAHKD.sol
628 function destroyBlackFunds(address _blackListedUser) external override whenNotPaused onlySupplyDestroyer() {
636 uint dirtyFunds = balanceOf(_blackListedUser);
637 balances[_blackListedUser] = 0;
638 _totalSupply = _totalSupply - dirtyFunds ;
639 emit DestroyedBlackFunds(_blackListedUser, dirtyFunds);
640 emit Transfer(_blackListedUser, address(this), dirtyFunds);
641 }
벌어지는 지점. 상태 변화는 소각이지만, 이벤트는 토큰이 계약으로 이동했다고 말하고, 실제로는 그곳에 어떤 것도 보관되지 않습니다(balances[address(this)]는 계속 0으로 유지됩니다). 인덱서는 address(this)가 보유하지 않는 토큰을 크레딧할 것이며, 이벤트로부터 재구성된 총 공급량은 체인과 일치하지 않을 것입니다. 이는 구제 자체를 혼란스럽게도 합니다: 토큰이 보관되는 것이 아니라 소각되기 때문에, 피해자에게의 재발행은 새로운 발행이어야 하는데, 오해를 불러일으키는 Transfer(..., address(this), ...)는 계약이 이제 이를 보관하고 있으며 전달할 수 있다는 것을 암시하지만, 이는 불가능합니다. 깨끗하게 address(0)로 소각하고 별도로 재발행하는 것이 정확하고 조정 가능한 방식일 것입니다. 작성된 대로, 준비금 조정이 의존하는 온체인 회계는 체인의 실제 상태로부터 벌어집니다(drift). 이는 명확한 실패보다는 우려에 해당합니다.
우리는 이러한 결과를 체인이 보여주는 것으로 한정합니다. 준비금이 완전히 뒷받침되는지, 키가 HSM이나 에어갭(air-gapped) 환경에 있는지, 그리고 트랜잭션이 서명 전에 오프체인에서 시뮬레이션되는지는 계약으로부터 보이지 않으며, 우리는 이에 대해 어떠한 주장도 하지 않습니다.
결론
이 두 축의 검토 전반에 걸쳐 그림은 일관됩니다. 소프트웨어로서, HKDAP는 작성된 대로 실행되지 않는 컴플라이언스 통제를 포함한 기능적 결함을 가지고 있으며, 프로덕션 이전 빌드의 여러 흔적을 보여줍니다. 규제 대상 스테이블코인으로서, 그것의 여러 온체인 속성이 HKMA 가이드라인의 특정 조항과 충돌합니다. 우리가 볼 수 없는 오프체인 사안을 제외하고, 온체인 증거를 근거로, 배포된 계약은 커머셜 스테이블코인이 충족해야 할 기준을 아직 충족하지 못합니다.
두 가지 관찰이 이어지며, 이는 명확하게 언급할 가치가 있습니다.
첫째, 공개 체인에서 발행하는 것은 컴플라이언스가 어디서 결정되는지를 바꿉니다. "어떤 단일 당사자도 단독으로 행동할 수 없어야 한다"와 같은 요건은 배포된 코드의 역할 검사에 의해 충족되거나 놓치는 것이며, 그 코드는 공개되어 있습니다. 라이선스나 문서가 아니라 구현체가, 그러한 요건이 실제로 충족되거나 놓치는 지점이며, 누구든 어느 쪽인지 확인할 수 있습니다.
둘째, "Beta Access" 라벨은 배포된 코드의 리스크 프로필을 바꾸지 않습니다. 이 계약은 이더리움 메인넷에서 라이브 상태이며, 실제 키에 의해 관리되고, 홍콩 달러에 대한 청구권(claim)을 나타냅니다. 따라서 이는 어떻게 라벨링되었는지와 무관하게 프로덕션 기준으로 평가되어야 합니다.
구체적인 결과들 아래에는 공통된 흐름이 있습니다. 이 아키텍처는 생태계가 이미 제공하고 대규모로 감사해 온 프리미티브들을 커스텀 형태로 재발명합니다: Safe 멀티시그와 OpenZeppelin의 AccessManager 및 TimelockController로 충분할 곳에 처음부터 만든 승인 엔진과 역할 계층; EnumerableSet 대신 손으로 작성한 연결 리스트 컬렉션; 표준 TransparentUpgradeableProxy 대신 변형된 프록시; OpenZeppelin의 것 대신 손으로 작성한 ERC-20. 이 검토의 결함 대부분은 표준 컴포넌트를 재사용하는 부분이 아니라 그 커스텀 장치 안에 존재합니다. 이는 온체인 개발이 선호하는 작고 감사된 빌딩 블록으로부터 구성된 것이 아니라, 범용 소프트웨어처럼 추상화된 시스템처럼 보이며, 여기서 커스텀 추상화의 모든 계층은 가스, 공격 표면, 그리고 업그레이드 리스크이기도 합니다. 그러한 표준 컴포넌트로 구축된 설계는 더 작고, 더 안전하며, 검토하기 더 쉬울 것이며, 현재 이 시스템에 부족한 것들, 즉 타임락으로부터의 숙고 기간과 명명되고 명료한 역할들을 함께 갖추고 있을 것입니다.
여기서 설명된 문제들은 해결 가능합니다. 고위험 작업에 대해 멀티시그를 복원하고, 실행과 감사를 분리하고, KYC 취소 로직을 고치고, 전송 검사를 통일하고, 디버그 코드를 제거하고, 각 업그레이드 전에 제3자 감사를 요구하는 것이 이들 대부분을 해결할 것입니다. 검증된 소스와 함께 메인넷에 배포하는 것 역시 이 검토를 가능하게 한 것이며, 이는 올바른 기본값입니다. 이러한 종류의 지속적인 온체인 보안 및 컴플라이언스 검토가 BlockSec에서 우리가 하는 일이며, 우리는 기꺼이 도움을 드리고자 합니다.



