Back to Blog

HKDAP 스테이블코인 보안 검토: 운영 중, 라이선스 보유, 준비 미완료

Phalcon Security
August 14, 2026
43 min read
Key Insights
  • HKDAP의 KYC 취소는 데드 코드이며, KYC 증명은 온체인에서 검증되지 않음

  • 단일 키로 발행, 소각, 일시 정지 또는 동결이 가능하며, 두 개의 키가 모든 업그레이드 및 역할을 제어함

  • 여러 온체인 속성이 HKMA 스테이블코인 발행자 가이드라인과 상이함

A BlockSec security and compliance review of HKDAP (Anchorpoint), as of August 13, 2026.
2026년 8월 13일 기준 HKDAP(Anchorpoint)에 대한 BlockSec 보안 및 규정 준수 검토.

TL;DR. We reviewed the deployed contract of HKDAP, the first regulated stablecoin issued in Hong Kong, live on Ethereum mainnet, and it is not production-ready. Its KYC and revocation controls do not work as written, its governance is concentrated enough that a single key can mint, burn, or freeze, and several of its on-chain properties conflict with the HKMA's own guideline. A common thread runs underneath: the contract reinvents from scratch the primitives the ecosystem already provides and audits at scale (a multisig, an access-control layer, a timelock, the ERC-20 itself), and most of the defects live in that custom machinery rather than in the parts that reuse standard components. The "Beta Access" label does not close that gap. TL;DR. 우리는 홍콩에서 발행된 최초의 규제 스테이블코인인 HKDAP의 배포된 계약을 이더리움 메인넷에서 검토했으며, 이 계약은 프로덕션 준비 상태가 아닙니다. KYC 및 철회 통제가 작성된 대로 작동하지 않고, 거버넌스가 지나치게 집중되어 있어 단일 키 하나로 발행(mint), 소각(burn), 동결(freeze)을 수행할 수 있으며, 여러 온체인 속성은 HKMA 자체 지침과 충돌합니다. 공통된 맥락이 깔려 있습니다: 이 계약은 생태계가 이미 제공하고 대규모로 감사하는 기본 구성 요소(멀티시그, 접근 제어 계층, 타임락, ERC-20 자체)를 처음부터 다시 발명했으며, 대부분의 결함은 표준 구성 요소를 재사용하는 부분이 아니라 그 맞춤형 메커니즘에 있습니다. "Beta Access" 라벨은 그 격차를 해소하지 못합니다.

On August 12, 2026, Anchorpoint launched the first phase of HKDAP, a Hong Kong dollar stablecoin. It is a significant launch. Anchorpoint, a joint venture led by Standard Chartered Bank (Hong Kong) with HKT and Animoca Brands, holds one of only two stablecoin issuer licences the HKMA has granted, out of 36 applicants, and HKDAP is among the first stablecoins issued under Hong Kong's Stablecoins Ordinance. 2026년 8월 12일, Anchorpoint는 홍콩 달러 스테이블코인인 HKDAP의 1단계를 출시했습니다. 이는 중요한 출시입니다. Standard Chartered Bank (Hong Kong)이 주도하고 HKT 및 Animoca Brands가 참여한 합작회사인 Anchorpoint는 36개 신청자 중 HKMA가 부여한 단 두 개의 스테이블코인 발행자 라이선스 중 하나를 보유하고 있으며, HKDAP는 홍콩의 스테이블코인 조례(Stablecoins Ordinance)에 따라 발행된 최초의 스테이블코인 중 하나입니다.

Unlike most products from a regulated bank, HKDAP is directly inspectable. It runs on Ethereum mainnet, and its contract source is verified on Etherscan. This is a useful property: for a stablecoin issued this way, the rules that govern issuance, transfer, and freezing are not described in a document, they are implemented in code that anyone can read and that executes exactly as written. A licence is a claim; the deployed contract is the implementation of that claim, and it is public. 규제를 받는 은행의 대부분의 제품과 달리 HKDAP는 직접 검사할 수 있습니다. 이더리움 메인넷에서 실행되며 계약 소스가 Etherscan에서 검증되었습니다. 이는 유용한 속성입니다: 이렇게 발행된 스테이블코인의 경우 발행, 전송, 동결을 규율하는 규칙은 문서에 기술된 것이 아니라 누구나 읽을 수 있고 작성된 대로 정확히 실행되는 코드로 구현됩니다. 라이선스는 주장(claim)이고, 배포된 계약은 그 주장의 구현이며 공개되어 있습니다.

That makes a concrete review possible, and that is what we did. We examined the deployed contract along two axes. First, as software: is it correct and production-grade? Second, as a regulated stablecoin: does its on-chain behavior match the HKMA's Guideline on Supervision of Licensed Stablecoin Issuers? 그 덕분에 구체적인 검토가 가능해졌고, 이것이 우리가 수행한 작업입니다. 우리는 배포된 계약을 두 가지 축으로 조사했습니다. 첫째, 소프트웨어로서: 정확하고 프로덕션급인가? 둘째, 규제 스테이블코인으로서: 온체인 동작이 HKMA의 허가 스테이블코인 발행자 감독 지침과 일치하는가?

The findings are consistent along both axes. The contract contains multiple functional defects, including compliance controls that do not work as written. Its governance is highly concentrated, with several high-risk operations executable by a single key. And a number of its on-chain properties conflict with specific clauses of the HKMA guideline. Our assessment is that, even as a beta, the contract does not meet the quality bar a commercial stablecoin requires. 결과는 두 축에서 일관됩니다. 계약에는 작성된 대로 작동하지 않는 규정 준수 통제를 포함하여 여러 기능적 결함이 있습니다. 거버넌스는 매우 집중되어 있으며, 여러 고위험 작업이 단일 키로 실행될 수 있습니다. 그리고 여러 온체인 속성이 HKMA 지침의 특정 조항과 충돌합니다. 우리의 평가는 베타 단계라고 해도 이 계약이 상업용 스테이블코인에 요구되는 품질 기준을 충족하지 못한다는 것입니다.

The rest of this article presents that review, current as of August 13, 2026. Our review is based on publicly deployed code and observable on-chain facts. We make no claims about matters the blockchain cannot establish, such as reserve backing or off-chain key custody. 이 글의 나머지 부분은 2026년 8월 13일 현재의 검토 내용입니다. 우리의 검토는 공개적으로 배포된 코드와 관찰 가능한 온체인 사실에 기반합니다. 우리는 블록체인이 확인할 수 없는 사항, 예를 들어 준비금 담보나 오프체인 키 보관에 대해서는 어떤 주장도 하지 않습니다.

How we found the contract

계약을 찾은 방법

We started from the issuer, not from a token list. Anchorpoint's company presence links to its site at anchorpoint.hk. The Beta Access page names the deployment: Ethereum mainnet, proxy 0x87622385F960fcCB3121d6D0A9513bd1D9Bed6cA, whose source is verified on Etherscan. 우리는 토큰 목록이 아니라 발행자에서 시작했습니다. Anchorpoint의 회사 존재는 anchorpoint.hk 사이트로 연결됩니다. Beta Access 페이지에는 배포 정보가 명시되어 있습니다: 이더리움 메인넷, 프록시 0x87622385F960fcCB3121d6D0A9513bd1D9Bed6cA, Etherscan에서 소스가 검증되었습니다.

From there we mapped the full system on-chain: the token proxy and its implementation (ControllableAHKD, 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc), the governance contract that administers it (0x47dd47a776902bee03abdd5aeff43f41b0ffbc2b), the top-level role registry (0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c), and five compliance modules, each with its own governance contract. Every relationship below was verified by reading storage slots and calling view functions on mainnet, not inferred from source alone. 거기서부터 전체 시스템을 온체인에서 매핑했습니다: 토큰 프록시와 그 구현(ControllableAHKD, 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc), 이를 관리하는 거버넌스 계약(0x47dd47a776902bee03abdd5aeff43f41b0ffbc2b), 최상위 역할 레지스트리(0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c), 그리고 각각 자체 거버넌스 계약을 가진 5개의 규정 준수 모듈. 아래의 모든 관계는 소스만으로 추론한 것이 아니라 메인넷에서 스토리지 슬롯을 읽고 뷰 함수를 호출하여 검증했습니다.

What the contract looks like

계약의 구조

The three layers of HKDAP: a governance control plane administers the token, and the token queries five compliance modules on every transfer HKDAP의 세 계층: 거버넌스 제어 평면이 토큰을 관리하고, 토큰은 모든 전송에서 5개의 규정 준수 모듈을 조회한다

HKDAP's token does not use OpenZeppelin's standard ERC-20 reference implementation; its ERC-20 logic is written from scratch (this is where several of the bugs below come from). The system has three layers: HKDAP의 토큰은 OpenZeppelin의 표준 ERC-20 참조 구현을 사용하지 않습니다. ERC-20 로직은 처음부터 작성되었습니다(아래의 여러 버그가 여기서 비롯됩니다). 시스템은 세 계층으로 구성됩니다:

  • The token. ControllableAHKD, behind an upgradeable proxy. A controlled ERC-20 with mint, burn, pause, and forced-destroy, plus compliance checks wired into every transfer.
  • A home-grown M-of-N governance engine. Every privileged action (upgrade, mint, burn, pause, blacklist, freeze, change a compliance module) goes through a request, approve, execute ceremony rather than a plain multisig.
  • Five compliance modules. Blacklist, freezing, a KYC activation service, and deposit and redemption whitelists. Each module is itself a proxy governed by its own control-authority contract.
  • 토큰. 업그레이드 가능한 프록시 뒤의 ControllableAHKD. mint, burn, pause, 강제 파괴(forced-destroy)와 모든 전송에 연결된 규정 준수 검사를 갖춘 제어형 ERC-20입니다.
  • 자체 개발한 M-of-N 거버넌스 엔진. 모든 특권 작업(업그레이드, mint, burn, pause, 블랙리스트, 동결, 규정 준수 모듈 변경)은 단순한 멀티시그가 아니라 요청(request), 승인(approve), 실행(execute) 절차를 거칩니다.
  • 다섯 개의 규정 준수 모듈. 블랙리스트, 동결, KYC 활성화 서비스, 입금 및 상환 화이트리스트. 각 모듈은 자체 통제 권한(control-authority) 계약이 관리하는 프록시입니다.

Structurally, this same "control authority plus proxy plus implementation" unit repeats six times (the token plus five modules), and all six control authorities resolve their roles at a single registry. All control in the system ultimately converges on this one registry, and who can rewrite the roles inside it comes down to a very small set of signer keys, as we show in detail below. 구조적으로, 이 동일한 "통제 권한 + 프록시 + 구현" 단위가 여섯 번 반복되며(토큰 + 5개 모듈), 여섯 개의 통제 권한은 모두 단일 레지스트리에서 역할을 확인합니다. 시스템의 모든 통제는 궁극적으로 이 하나의 레지스트리에 수렴하며, 그 안의 역할을 다시 쓸 수 있는 사람은 아래에서 자세히 보여주듯 매우 적은 수의 서명 키로 귀결됩니다.

Part 1: Security and bugs

파트 1: 보안 및 버그

The findings in this part are summarized below; each row is detailed in the section noted. 이 파트의 결과는 아래에 요약되어 있습니다. 각 행은 해당 섹션에서 자세히 설명합니다.

Area Finding Where Effect
1.1 KYC and revocation controls fail KYC revocation is dead code TokenHolderActivationServerLibrary.sol:221-235 isActive ignores provider de-registration (the loop never runs, and uses == not =); fail-open.
Verifier de-registration never sets INACTIVE TokenHolderActivationServer.sol:504-511 A "de-registered" verifier can still onboard and de-activate wallets; a second call reverts.
KYC proof never validated on-chain TokenHolderActivationServer.sol:575-578 The proof is passed in but discarded; any proof, including an empty string, passes.
Free-transfer exemption can be split around ControllableAHKD.sol:337-535 freeTransferLimit is checked per call, not cumulatively; if used as a cap on un-KYC'd activity, it can be bypassed by splitting into sub-limit transfers.
1.2 Governance is over-concentrated High-risk operations are single-signature authorizationMatrix; mint tx 0xa7e53c…b33d7 mint / burn / freeze / KYC-deactivate (role C), pause / destroy (role D), blacklist / unfreeze (role F) each execute with one key.
One pair of keys (A + B) upgrades everything authorizationMatrix upgradeTo for the token and all five modules is role A + B; two people can replace any implementation.
The same pair rewrites every role registry 0xa728… authorizationMatrix grantRole / revokeRole on the registry are also role A + B (ADMIN_ROLE is held only by the contract; DEFAULT_ADMIN_ROLE = address(0)); no external key can change roles directly.
One address holds six roles role registry 0xa728… 0x2f7f00… holds role C plus ADMIN_TOKEN_HOLDER and all four auditor roles; execution and audit overlap.
Revocation is not immediate; no timelock HybridControlEngine.sol:158-227 A counted signature is not re-validated after a role is revoked; execution is atomic with the final signature, with no delay.
The engine's audit trail is unreliable HybridControlEngine.sol:36-129, 177-196 evtApprove emits address(0) as the signer; the active-request list uses nonce 0 as both a real id and the empty marker, so reverse traversal misses the first request.
1.3 Inconsistent transfer checks transfer and transferFrom enforce different rules ControllableAHKD.sol:313-502 The whitelist early-return bypasses KYC only via transferFrom; checkingMode checks different parties; the same transfer is governed differently.
1.4 Signs of a pre-production build debug logs, wrong comment, name/hash mismatch, tests uploaded UpgradeableProxy.sol:115-119; ControllableAHKD.sol:25 console.log in the proxy fallback (permanent gas); proxy comment contradicts the code; role name/hash mismatch; 117 files incl. tests uploaded; optimizer runs = 0.
1.5 The one upgrade left defects in place tracing the single upgrade tx 0x742372…85136, 0xa7a400…630c Deploy Apr 28 → upgrade Jul 10; the A + B ceremony's two transactions were one block apart (~12s); every Part 1 defect is in the installed implementation.
영역 발견 사항 위치 영향
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; mint tx 0xa7e53c…b33d7 mint / burn / freeze / KYC 비활성화(역할 C), pause / destroy(역할 D), 블랙리스트 / unfreeze(역할 F)가 각각 하나의 키로 실행됨.
한 쌍의 키(A + B)가 모든 것을 업그레이드 authorizationMatrix 토큰 및 5개 모듈 모두에 대한 upgradeTo는 역할 A + B; 두 사람이 구현을 교체할 수 있음.
같은 쌍이 모든 역할을 다시 씀 레지스트리 0xa728… authorizationMatrix 레지스트리의 grantRole / revokeRole 또한 역할 A + B(ADMIN_ROLE은 계약만 보유, DEFAULT_ADMIN_ROLE = address(0)); 외부 키가 직접 역할을 변경할 수 없음.
하나의 주소가 여섯 개 역할 보유 역할 레지스트리 0xa728… 0x2f7f00…이 역할 C와 ADMIN_TOKEN_HOLDER 및 4개 감사 역할을 모두 보유; 실행과 감사가 중복됨.
철회가 즉시 이루어지지 않음; 타임락 없음 HybridControlEngine.sol:158-227 집계된 서명은 역할이 철회된 후 재검증되지 않음; 실행은 마지막 서명과 원자적으로 지연 없이 발생.
엔진의 감사 추적이 신뢰할 수 없음 HybridControlEngine.sol:36-129, 177-196 evtApprove가 실제 서명자가 아닌 address(0)를 서명자로 방출; 활성 요청 목록은 nonce 0을 실제 ID와 빈 마커로 동시에 사용하여 역방향 순회 시 첫 요청을 놓침.
1.3 불일치하는 전송 검사 transfertransferFrom이 서로 다른 규칙 적용 ControllableAHKD.sol:313-502 화이트리스트 조기 반환은 transferFrom을 통해서만 KYC를 우회; checkingMode가 서로 다른 당사자를 검사; 동일한 전송이 다르게 규율됨.
1.4 프로덕션 이전 빌드의 징후 디버그 로그, 잘못된 주석, 이름/해시 불일치, 테스트 업로드 UpgradeableProxy.sol:115-119; ControllableAHKD.sol:25 프록시 폴백에 console.log(영구적 가스); 프록시 주석이 코드와 모순; 역할 이름/해시 불일치; 테스트 포함 117개 파일 업로드; optimizer runs = 0.
1.5 단 한 번의 업그레이드에도 결함이 남음 단일 업그레이드 추적 tx 0x742372…85136, 0xa7a400…630c 4월 28일 배포 → 7월 10일 업그레이드; A + B 절차의 두 트랜잭션이 한 블록(약 12초) 간격; 파트 1의 모든 결함이 설치된 구현에 존재.

1.1 The KYC and revocation controls do not work as written

1.1 KYC 및 철회 통제가 작성된 대로 작동하지 않습니다

Under the regulation, the B-side (institutional) users HKDAP serves are required to pass KYC. In the contract, that means it has to do at least two things: enforce KYC on transfers, so that a wallet which has not passed KYC is blocked, and revoke access when an identity provider or a holder is removed. In HKDAP, that path is broken in three independent places. 규정에 따라 HKDAP가 서비스하는 B-side(기관) 사용자는 KYC를 통과해야 합니다. 계약에서 이는 최소한 두 가지를 수행해야 함을 의미합니다: KYC를 통과하지 않은 지갑이 차단되도록 전송에 KYC를 적용하고, ID 제공자 또는 보유자가 제거될 때 접근을 철회하는 것입니다. HKDAP에서 이 경로는 세 군데 독립적으로 끊어져 있습니다.

KYC revocation is dead code. The token gates transfers by calling isActive(address) on the KYC module. isActive is meant to do two things: first, set a base value from a per-wallet counter, active when that wallet has been KYC-activated more times than it has been de-activated; then narrow that value to false if any identity provider that vouched for the wallet has since been de-registered. The two cover two kinds of revocation, revoking one holder's KYC (the counter) and revoking a whole provider so that every wallet it onboarded falls with it (the loop). Only the first happens. KYC 철회는 죽은 코드입니다. 토큰은 KYC 모듈에서 isActive(address)를 호출하여 전송을 제어합니다. isActive는 두 가지를 수행하도록 되어 있습니다: 첫째, 지갑별 카운터에서 기본 값을 설정하는데, 해당 지갑이 비활성화된 횟수보다 KYC 활성화된 횟수가 많으면 활성 상태가 됩니다. 그런 다음 해당 지갑을 보증한 ID 제공자가 이후 등록 말소된 경우 그 값을 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    }

Line 221 is a real assignment (=), and it is the only one that takes effect: active becomes deactivationCount < activationCount. The loop (lines 226-235) is what should narrow that value, but it fails twice. Its condition on line 226, entryCount > chainedItemList.entryCount, is 0 > N, so the body never runs; and even if it did, line 231 uses ==, a comparison whose result is discarded, where it should assign with =. isActive therefore returns the counter comparison alone and ignores provider revocation entirely. This is fail-open: de-registering a compromised KYC provider does not stop the wallets it onboarded from transacting. And because unregisterVerifier does not touch those counters, a revoked provider's wallets keep a positive count and stay active. 221행은 실제 할당(=)이며 실제로 효과가 있는 유일한 줄입니다: activedeactivationCount < activationCount가 됩니다. 루프(226-235행)는 그 값을 좁혀야 하는 부분이지만 두 번 실패합니다. 226행의 조건 entryCount > chainedItemList.entryCount0 > N이므로 본문이 실행되지 않으며, 설령 실행되더라도 231행은 =로 할당해야 하는 곳에서 비교 결과가 폐기되는 ==를 사용합니다. 따라서 isActive는 카운터 비교만 반환하고 제공자 철회를 완전히 무시합니다. 이는 fail-open입니다: 손상된 KYC 제공자를 등록 말소해도 그 제공자가 온보딩한 지갑의 거래를 막지 못합니다. 그리고 unregisterVerifier는 해당 카운터를 건드리지 않기 때문에, 철회된 제공자의 지갑은 양수 카운터를 유지하며 활성 상태로 남습니다.

Provider revocation is broken on the other side too. unregisterVerifier only moves a linked-list node; it never sets the provider's directory status to INACTIVE: 제공자 철회도 반대쪽에서 끊어져 있습니다. 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    }

Because the status stays ACTIVE, a "de-registered" verifier can still register and de-activate holders, the branch meant to reactivate a provider is unreachable, and a second unregisterVerifier call underflows and reverts. 상태가 ACTIVE로 남아 있기 때문에 "등록 말소된" 검증자는 여전히 보유자를 등록하고 비활성화할 수 있고, 제공자를 재활성화하기 위한 분기는 도달할 수 없으며, 두 번째 unregisterVerifier 호출은 언더플로되어 revert됩니다.

The KYC proof is never validated on-chain. When an authorized verifier registers or renews a wallet through registerOrRenew (gated so that only a currently active verifier can call it), the flow reaches _checkKYCProof, which is meant to validate the submitted proof against the provider's scheme. Per the interface, that proof is "a URI for an Oracle, or a signed hash from a verifiable source." The function ignores it: KYC 증명은 온체인에서 절대 검증되지 않습니다. 인가된 검증자가 registerOrRenew를 통해 지갑을 등록하거나 갱신할 때(현재 활성 검증자만 호출할 수 있도록 게이트됨), 흐름은 제출된 증명을 제공자의 방식에 따라 검증하기 위한 _checkKYCProof에 도달합니다. 인터페이스에 따르면 이 증명은 "Oracle의 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    }

The proof does reach this function. registerOrRenew carries the submitted kycProof down through _registerOrRenew and passes it in as the second argument. But on line 575 that argument lands in a parameter with no name at all, which the doc comment above the function also omits, and the body never reads it. The function only confirms the provider is active and returns true on line 577, so the proof is discarded rather than checked. This is not an outsider bypass, since only an active verifier can reach it, but on-chain the contract performs no validation of the KYC evidence, so its integrity rests entirely on the off-chain verifier. A compromised or careless verifier can activate any wallet with any proof, including an empty string. 증명은 실제로 이 함수에 도달합니다. registerOrRenew는 제출된 kycProof_registerOrRenew를 통해 전달하여 두 번째 인자로 넘깁니다. 하지만 575행에서 그 인자는 이름조차 없는 매개변수에 들어가며, 함수 위의 문서 주석도 이를 생략하고, 본문은 결코 그것을 읽지 않습니다. 함수는 제공자가 활성 상태인지만 확인하고 577행에서 true를 반환하므로, 증명은 검사되는 것이 아니라 폐기됩니다. 활성 검증자만 도달할 수 있으므로 외부인이 우회할 수 있는 것은 아니지만, 온체인에서 계약은 KYC 증거를 전혀 검증하지 않으므로 그 무결성은 전적으로 오프체인 검증자에게 달려 있습니다. 손상되었거나 부주의한 검증자는 빈 문자열을 포함한 어떤 증명으로도 모든 지갑을 활성화할 수 있습니다.

Together, these three mean that a core compliance property, the ability to gate and to revoke access, does not work as written. 이 세 가지가 합쳐져, 접근을 제어하고 철회하는 능력이라는 핵심 규정 준수 속성이 작성된 대로 작동하지 않습니다.

Beyond these three, there is a related weakness: the free-transfer exemption can be split around. The token has a freeTransferLimit, and a transfer below it skips the isActive (KYC) check. But the limit is compared only against the amount of the current transfer; the contract keeps no cumulative total per address or period. So if the limit is used as a cap on un-KYC'd activity, a holder can move an arbitrary total by splitting it into repeated transfers each just under the limit, which makes the cap ineffective. 이 세 가지 외에 관련된 약점이 하나 더 있습니다: 무료 전송 면제를 분할하여 우회할 수 있습니다. 토큰에는 freeTransferLimit이 있으며, 그 미만의 전송은 isActive(KYC) 검사를 건너뜁니다. 그러나 한도는 현재 전송 금액에 대해서만 비교되며, 계약은 주소나 기간별 누계를 유지하지 않습니다. 따라서 이 한도를 KYC 미필 활동의 상한으로 사용하는 경우, 보유자는 각각 한도 바로 아래인 반복 전송으로 분할하여 임의의 총액을 이동할 수 있으므로 상한이 무의미해집니다.

1.2 Governance is over-concentrated, and high-risk operations are single-signature

1.2 거버넌스가 과도하게 집중되어 있고 고위험 작업이 단일 서명으로 수행됩니다

We enumerated every role and every role holder on-chain. Two things stand out before the specifics. 우리는 온체인에서 모든 역할과 역할 보유자를 열거했습니다. 세부 사항 전에 두 가지가 눈에 띕니다.

First, the roles that authorize the M-of-N ceremonies have no readable names. In the deployed configuration they appear only as 32-byte hashes, and none of them matches a named role constant in the verified source (the named roles such as SUPPLY_CONTROLLER_ROLE are held by the contracts themselves, not by the signers). We label the six signer roles A to F. That the most powerful roles in the system are opaque identifiers is itself a weakness: it makes the governance harder to review than named roles would. 첫째, M-of-N 절차를 승인하는 역할에는 읽을 수 있는 이름이 없습니다. 배포된 구성에서 그들은 32바이트 해시로만 나타나며, 검증된 소스의 명명된 역할 상수와 일치하는 것은 없습니다(SUPPLY_CONTROLLER_ROLE 같은 명명된 역할은 서명자가 아니라 계약 자체가 보유합니다). 우리는 여섯 개의 서명자 역할을 A부터 F까지 라벨링했습니다. 시스템에서 가장 강력한 역할이 불투명한 식별자라는 것 자체가 약점입니다: 명명된 역할보다 거버넌스를 검토하기 어렵게 만듭니다.

Second, the number of holders is small. The table below is read from the role registry on-chain; addresses are abbreviated. 둘째, 보유자 수가 적습니다. 아래 표는 온체인 역할 레지스트리에서 읽은 것이며 주소는 축약되었습니다.

Role (our label) On-chain hash Holder(s) What it authorizes
A 0x37f0f656… 0x747356d525bf47f495af7c6e42f3ab9b8ba87a4b first signature for upgradeTo / changeAdmin / setControlAuthority
B 0x95c27f81… 0x8117a657df7a1c399231bfe26a096eb8f8ad5973, 0x5d9d9b28e83382e694916b0feeaf7a9badbb659c, 0xebb73f78e253539acceb4e6cc287095788eee5d4 the second signature on nearly every two-signature action
C 0xfa2fe896… 0x2f7f00cc5334fe2861e485ff610f74890a0316ed mintToDeposit, burnFrom, freeze, KYC deactivate (single)
D 0x3dce3265… 0x5092af62a1625fa57404557d3ad417474f3f494c pause, destroyBlackFunds, register/unregister deposit & redemption addresses, registerVerifier (single)
E 0xfd21a76d… 0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2 change the compliance modules (setBlacklistServer, and so on)
F 0x510ac1ff… 0x7daabe5092d3feea5cc19ddbb5380a4eac38f89c addBlackList, unfreeze (single)
역할 (우리 라벨) 온체인 해시 보유자 승인하는 작업
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 (단일)

Several points follow from the table. 표에서 몇 가지가 따라 나옵니다.

Single-signer high-risk operations. Most high-risk operations require a single role at a quota of one. The table below is read on-chain from the authorizationMatrix of the token's governance contract and of the five module governance contracts (single signature unless a + B second signature is shown): 단일 서명 고위험 작업. 대부분의 고위험 작업은 쿼터 1의 단일 역할을 요구합니다. 아래 표는 토큰 거버넌스 계약과 5개 모듈 거버넌스 계약의 authorizationMatrix에서 온체인으로 읽은 것입니다(+ B 두 번째 서명이 표시되지 않으면 단일 서명):

Operation Governed by Required signatures
mintToDeposit, burnFrom token C x1
freeze, batchFreeze freezing module C x1
deactivate, adminDeactivate (KYC) KYC module C x1
pause, destroyBlackFunds token D x1
register, unregister (deposit / redemption) directory modules D x1
registerVerifier, unregisterVerifier KYC module D x1
addBlackList, batchBlackList blacklist module F x1
unfreeze freezing module F x1
removeBlackList blacklist module F x1 + B x1
setBlacklistServer, setFreezingServer, setCheckingMode token E x1 + B x1
directory-server and supply-limit setters token D x1 + B x1
upgradeTo / changeAdmin / setControlAuthority (token and every module), unpause token + modules A x1 + B x1
작업 관리 주체 필요한 서명
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

Issuance, freezing, and KYC de-activation are single-signature (all under role C); pause, destroy, directory changes, and verifier registration are single-signature under role D; blacklisting and unfreezing are single-signature under role F. Only upgrades and configuration changes take a second signature. Note the asymmetry: freeze and addBlackList need one signature while removeBlackList needs two, so restricting an account is easier than releasing it. 발행, 동결, KYC 비활성화는 단일 서명입니다(모두 역할 C). pause, destroy, 디렉터리 변경, 검증자 등록은 역할 D에서 단일 서명입니다. 블랙리스트 등록과 동결 해제는 역할 F에서 단일 서명입니다. 두 번째 서명이 필요한 것은 업그레이드와 구성 변경뿐입니다. 비대칭성에 주목하세요: freezeaddBlackList는 서명 하나가 필요한 반면 removeBlackList는 두 개가 필요하므로, 계정을 제한하는 것이 해제하는 것보다 쉽습니다.

This is not just a reading of the matrix; it is observable in a live mint. The most recent issuance at the time of writing, tx 0xa7e53c…b33d7, is a single transaction sent by the sole role-C account (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) to the token's governance contract. It calls request, and that same transaction reaches quorum and emits the mint Transfer from address(0), with no separate approval transaction and no second signer. Because issuance settles inside the requester's own transaction, one key both requested and executed the mint. 이는 단지 매트릭스를 읽은 것만이 아닙니다. 실제 mint에서 관찰할 수 있습니다. 이 글을 쓰는 시점의 가장 최근 발행인 tx 0xa7e53c…b33d7은 유일한 역할 C 계정(0x2f7f00cc5334fe2861e485ff610f74890a0316ed)이 토큰의 거버넌스 계약으로 보낸 단일 트랜잭션입니다. 이 트랜잭션은 request를 호출하고, 같은 트랜잭션에서 쿼럼에 도달하여 address(0)로부터의 mint Transfer를 방출하며, 별도의 승인 트랜잭션이나 두 번째 서명자가 없습니다. 발행이 요청자 자신의 트랜잭션 안에서 완결되므로 하나의 키가 mint를 요청하고 실행했습니다.

There is also no timelock anywhere in the engine. The moment the last required signature lands, the action executes in that same transaction, with no delay in which it could be reviewed, cancelled, or contested; the engine records an executedAt timestamp but never checks one. So even the two-signature operations settle instantly once the second key signs. 또한 엔진 어디에도 타임락이 없습니다. 마지막으로 필요한 서명이 도착하는 순간 해당 작업은 검토, 취소, 이의제기를 할 수 있는 지연 없이 같은 트랜잭션에서 실행됩니다. 엔진은 executedAt 타임스탬프를 기록하지만 그것을 확인하지는 않습니다. 따라서 두 서명 작업조차 두 번째 키가 서명하면 즉시 완결됩니다.

One pair of keys upgrades everything. Upgrading the token and upgrading all five compliance modules use the same requirement, A plus B. Since A is one account and B is three accounts sharing a role, two people can replace any implementation in the system. 한 쌍의 키가 모든 것을 업그레이드합니다. 토큰 업그레이드와 5개 규정 준수 모듈 업그레이드 모두 동일한 요구사항, A + B를 사용합니다. A는 하나의 계정이고 B는 역할을 공유하는 세 개의 계정이므로, 두 사람이 시스템의 어떤 구현이든 교체할 수 있습니다.

The same pair also controls the role table. Roles can only be granted and revoked on the registry (HybridControlledAuthority, 0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c), through its own ceremony; no external key can change them directly. And its authorizationMatrix, read on-chain, requires the same signatures for a role change as for an upgrade: role A plus role B. So the two people who can replace any implementation can also add a key to role C, remove an existing holder, and rewrite the entire role table. That two-signature gate is better than the single-signature operations above, but it is still a low bar for the root of the system, since role A is a single account with no redundancy. 같은 쌍이 역할 테이블도 통제합니다. 역할은 레지스트리(HybridControlledAuthority, 0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c)에서만 자체 절차를 통해 부여되고 철회될 수 있습니다. 외부 키가 직접 변경할 수 없습니다. 그리고 레지스트리의 authorizationMatrix는 온체인에서 읽으면 역할 변경에 업그레이드와 동일한 서명, 즉 역할 A와 역할 B를 요구합니다. 따라서 어떤 구현이든 교체할 수 있는 두 사람은 역할 C에 키를 추가하고, 기존 보유자를 제거하고, 전체 역할 테이블을 다시 쓸 수 있습니다. 그 두 서명 게이트는 위의 단일 서명 작업보다 낫지만, 역할 A가 중복 없이 단일 계정이므로 시스템의 루트로서는 여전히 낮은 기준입니다.

One address, six roles. The holder of role C (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) also holds the named ADMIN_TOKEN_HOLDER_ROLE (KYC verifier management) and is a member of all four auditor roles (BLACKLIST_AUDITOR_ROLE, FREEZING_AUDITOR_ROLE, WHITELIST_AUDITOR_ROLE, and AFL_TOKEN_AUDITOR_HOLDER_ROLE). Compromise of that one key is loss of issuance, freezing, and KYC administration at once. 하나의 주소, 여섯 개의 역할. 역할 C의 보유자(0x2f7f00cc5334fe2861e485ff610f74890a0316ed)는 명명된 ADMIN_TOKEN_HOLDER_ROLE(KYC 검증자 관리)도 보유하며 네 개의 감사 역할(BLACKLIST_AUDITOR_ROLE, FREEZING_AUDITOR_ROLE, WHITELIST_AUDITOR_ROLE, AFL_TOKEN_AUDITOR_HOLDER_ROLE) 모두의 구성원입니다. 이 키 하나가 손상되면 발행, 동결, KYC 관리가 동시에 상실됩니다.

The auditor roles have two problems of their own. First, they gate the read-only getters for the compliance lists, apparently to control who can read them, but on a public chain that is pointless: the underlying storage is readable by anyone (reading storage slots is how we mapped this system), so the lists are public either way, and the restriction shows the design did not account for being on a public chain. Second, concentration: all four auditor roles sit with the same 23 accounts, and the holders of execution roles A, C, D, and F are among them, so the same keys that mint, burn, freeze, and blacklist also sit in the group meant to review those actions. 감사 역할에는 두 가지 자체 문제가 있습니다. 첫째, 감사 역할은 규정 준수 목록에 대한 읽기 전용 getter를 게이트하여 읽을 수 있는 사람을 통제하는 것으로 보이지만, 공개 체인에서는 무의미합니다: 기본 스토리지는 누구나 읽을 수 있으므로(스토리지 슬롯을 읽는 것이 우리가 이 시스템을 매핑한 방법입니다) 목록은 어느 쪽이든 공개되어 있으며, 이러한 제한은 설계가 공개 체인에 있다는 점을 고려하지 않았음을 보여줍니다. 둘째, 집중: 네 개의 감사 역할 모두 동일한 23개 계정에 있으며, 실행 역할 A, C, D, F의 보유자도 그중에 포함되어 있으므로 mint, burn, freeze, 블랙리스트를 수행하는 키가 그러한 작업을 검토해야 하는 그룹에도 함께 있습니다.

Revocation is not immediate. In the ceremony engine, a signer's role is checked once and the quota is decremented; earlier signers are never re-validated, so revoking a role afterward does not retract a vote already counted: 철회는 즉시 이루어지지 않습니다. 절차 엔진에서 서명자의 역할은 한 번 확인되고 쿼터가 감소합니다. 이전 서명자는 결코 재검증되지 않으므로 이후에 역할을 철회해도 이미 집계된 투표는 취소되지 않습니다:

// 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        }

Finally, the engine's own audit trail is unreliable. On every approval, the evtApprove event emits address(0) as the signer rather than the real approver; the actual signer survives only in the transaction sender and an internal record, so event logs cannot attribute who approved a request. And the active-request list uses nonce 0 as both a real request id and its empty marker, so monitoring or approval tooling that walks the list backward misses the request at nonce 0. Neither is critical, but for a regulated system that needs a clean audit trail, both subtract from it. 마지막으로 엔진 자체의 감사 추적이 신뢰할 수 없습니다. 모든 승인에서 evtApprove 이벤트는 실제 승인자가 아닌 address(0)를 서명자로 방출합니다. 실제 서명자는 트랜잭션 발신자와 내부 기록에만 남으므로 이벤트 로그만으로는 누가 요청을 승인했는지 귀속시킬 수 없습니다. 그리고 활성 요청 목록은 nonce 0을 실제 요청 ID와 빈 마커로 동시에 사용하므로 목록을 역방향으로 순회하는 모니터링 또는 승인 도구는 nonce 0의 요청을 놓칩니다. 둘 다 치명적이지는 않지만, 깨끗한 감사 추적이 필요한 규제 시스템에서 둘 다 그 품질을 떨어뜨립니다.

1.3 Transfer controls are inconsistent between transfer and transferFrom

1.3 transfertransferFrom 간 전송 통제가 일관되지 않습니다

Part of the difference between the two is expected. In transferFrom the initiator, msg.sender, is an approved spender rather than the source of the funds, so the code checks the real source from explicitly in every mode; transfer does not need to, because there msg.sender is the source. That adaptation is reasonable. Two other differences are not explained by the initiator, and they leave the same economic action governed by different rules. 두 함수 간 차이의 일부는 예상할 수 있습니다. transferFrom에서 개시자 msg.sender는 자금의 소스가 아니라 승인된 스펜더이므로 코드는 모든 모드에서 실제 소스 from을 명시적으로 확인합니다. transfermsg.sender가 소스이므로 그럴 필요가 없습니다. 그 적응은 합리적입니다. 다른 두 가지 차이는 개시자로 설명되지 않으며, 동일한 경제적 행위가 서로 다른 규칙에 의해 규율되도록 만듭니다.

First, the deposit and redemption whitelists are consulted only in the transferFrom path, where membership triggers an early return that bypasses the isActive (KYC) check. transfer never consults them. Whether a recipient is whitelisted has nothing to do with who initiated the transfer, so the same recipient is subject to KYC through transfer but can skip it through transferFrom: 첫째, 입금 및 상환 화이트리스트는 transferFrom 경로에서만 조회되며, 멤버십이 조기 반환을 촉발하여 isActive(KYC) 검사를 우회합니다. transfer는 이를 전혀 조회하지 않습니다. 수신자가 화이트리스트에 있는지 여부는 누가 전송을 시작했는지와 무관하므로, 동일한 수신자가 transfer를 통해서는 KYC를 받지만 transferFrom을 통해서는 건너뛸 수 있습니다:

// contracts/hce/ControllableAHKD.sol : transfer(), "Source" mode gates only the sender
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" ) ) ;

Second, checkingMode means different things across the two functions. In transfer, "Source" mode checks the sender; in transferFrom, "Source" mode checks the spender (msg.sender), while from is checked in every mode. So a single configuration setting enforces two different policies depending on the entry point. 둘째, checkingMode는 두 함수에서 서로 다른 의미를 갖습니다. transfer에서 "Source" 모드는 발신자를 검사합니다. transferFrom에서 "Source" 모드는 스펜더(msg.sender)를 검사하고 from은 모든 모드에서 검사됩니다. 따라서 단일 구성 설정이 진입점에 따라 두 가지 다른 정책을 강제합니다.

The effect is that the transfer controls of a regulated token depend on which function is used, which makes them hard to reason about and, depending on configuration, avoidable. 결과적으로 규제 토큰의 전송 통제는 어떤 함수를 사용하는지에 따라 달라지며, 이는 추론하기 어렵게 만들고 구성에 따라 회피할 수 있게 만듭니다.

1.4 Signs of a pre-production build

1.4 프로덕션 이전 빌드의 징후

Beyond the specific logic bugs, several properties of the codebase indicate that a pre-production build was deployed to mainnet. 구체적인 로직 버그 외에도 코드베이스의 여러 속성은 프로덕션 이전 빌드가 메인넷에 배포되었음을 나타냅니다.

Debug logging in production. hardhat/console.log calls remain throughout, including inside the proxy's fallback, which runs on every user transaction. Because the proxy is not itself upgradeable, this overhead is permanent: 프로덕션의 디버그 로깅. hardhat/console.log 호출이 전체에 남아 있으며, 모든 사용자 트랜잭션에서 실행되는 프록시 폴백 내부에도 포함됩니다. 프록시 자체는 업그레이드할 수 없으므로 이 오버헤드는 영구적입니다:

// 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    }

A proxy header comment that describes the opposite of the code. The same file carries OpenZeppelin's TransparentUpgradeableProxy documentation, which states the admin can never fall through to the implementation. This contract deliberately does the reverse: the guard on line 117 is commented out, and the admin does fall through. A reviewer trusting the comment would mis-model the trust boundary. 코드와 반대되는 내용을 설명하는 프록시 헤더 주석. 같은 파일에는 관리자가 절대 구현으로 폴스루할 수 없다고 명시하는 OpenZeppelin의 TransparentUpgradeableProxy 문서가 들어 있습니다. 이 계약은 의도적으로 그 반대를 수행합니다: 117행의 가드가 주석 처리되어 있고 관리자는 실제로 폴스루합니다. 주석을 신뢰하는 검토자는 신뢰 경계를 잘못 모델링하게 됩니다.

Role names that do not match their hashes. The "elevated risk" role is declared with the same constant name but a different keccak string in the token and in the modules, which produces two different roles: 해시와 일치하지 않는 역할 이름. "고위험(elevated risk)" 역할이 토큰과 모듈에서 동일한 상수 이름으로 선언되었지만 keccak 문자열이 달라 두 개의 서로 다른 역할이 생성됩니다:

// contracts/hce/ControllableAHKD.sol  (token)    hash = 0xd2b9...
25    bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATED_RISK_OWNER_ROLE') ;
// contracts/hce/erc20/modules/BlackListServer.sol  (module)    hash = 0x4de4...
18    bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATEDRISK_OWNER_ROLE') ;

The deployment avoids trouble only because each module holds its own copy; a second role name (AFL_TOKEN_HOLDER_AUDITOR_ROLE) is transposed the same way. 배포는 각 모듈이 자체 복사본을 보유하기 때문에 문제를 피하지만, 두 번째 역할 이름(AFL_TOKEN_HOLDER_AUDITOR_ROLE)도 같은 방식으로 전치되어 있습니다.

Other signs. The proxy's verification bundle uploaded 117 files, including the project's test suite, to the public explorer, which hands a reader the internal tests and edge cases. The most recent upgrade changed only compiler-warning cleanup, with no third-party audit in the loop. And the optimizer is set to zero runs, which makes the hot paths of a heavily used token more expensive rather than less. 기타 징후. 프록시의 검증 번들은 프로젝트의 테스트 스위트를 포함한 117개 파일을 공개 익스플로러에 업로드하여, 독자에게 내부 테스트와 엣지 케이스를 제공합니다. 가장 최근 업그레이드는 컴파일러 경고 정리만 변경했으며, 제3자 감사는 수반되지 않았습니다. 또한 optimizer는 0 runs로 설정되어 있어 많이 사용되는 토큰의 핫 경로를 더 저렴하게 만드는 대신 더 비싸게 만듭니다.

None of these is individually severe. Together, they indicate the code did not go through the release discipline expected of a contract that holds value on mainnet. 이 중 어느 것도 개별적으로 심각하지 않습니다. 그러나 함께 볼 때, 이 코드가 메인넷에서 가치를 보유하는 계약에 기대되는 릴리스 규율을 거치지 않았음을 나타냅니다.

1.5 The contract's one upgrade, traced

1.5 계약의 단 한 번의 업그레이드 추적

The proxy has been upgraded once. It was deployed on April 28, 2026 with implementation 0x8ff12fe3bed22d9e40afb4b98ef4dee28d94699e, and on July 10, 2026 it was upgraded to the current 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc. Because an upgrade is an on-chain governance action, we can see exactly who approved it. 프록시는 한 번 업그레이드되었습니다. 2026년 4월 28일 구현 0x8ff12fe3bed22d9e40afb4b98ef4dee28d94699e로 배포되었고, 2026년 7월 10일 현재의 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc로 업그레이드되었습니다. 업그레이드는 온체인 거버넌스 작업이므로 정확히 누가 승인했는지 볼 수 있습니다.

upgradeTo requires role A plus role B. The upgrade was two transactions in adjacent blocks, about twelve seconds apart: upgradeTo는 역할 A와 역할 B를 요구합니다. 업그레이드는 인접한 블록의 두 트랜잭션이었고 약 12초 간격이었습니다:

  • the request, role A, from 0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2, in block 25500519 (tx 0x742372…85136);
  • the approval, role B, from 0x3795300b31429f9d37b0dc805528d9390ce87c50, in block 25500520 (tx 0xa7a400…630c), which reached quorum and performed the upgrade in the same transaction.
  • 요청, 역할 A, 0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2로부터, 블록 25500519 (tx 0x742372…85136);
  • 승인, 역할 B, 0x3795300b31429f9d37b0dc805528d9390ce87c50로부터, 블록 25500520 (tx 0xa7a400…630c), 이 트랜잭션은 쿼럼에 도달하여 같은 트랜잭션에서 업그레이드를 수행했습니다.

Two things stand out. First, the entire two-signature ceremony completed within a single block interval. From request to execution was one block; there was no window in which the second signer could review independently before the change went live. 두 가지가 눈에 띕니다. 첫째, 두 서명 절차 전체가 단일 블록 간격 안에 완료되었습니다. 요청부터 실행까지 한 블록이었으며, 두 번째 서명자가 변경이 적용되기 전에 독립적으로 검토할 수 있는 창이 없었습니다.

Second, neither signer is identifiable from the current role registry. The roles have since been rotated: the requester 0xa9315a… no longer holds role A (it holds role E today), and the second signer 0x3795300b… holds no role at all now. Reading the registry as it stands would not tell you who authorized the upgrade; only the transaction history does. This is the "revocation is not immediate" property seen from the other side: roles move, so a snapshot of who holds what is not a record of who did what. 둘째, 현재 역할 레지스트리에서는 두 서명자 중 누구도 식별할 수 없습니다. 역할은 이후 순환되었습니다: 요청자 0xa9315a…는 더 이상 역할 A를 보유하지 않으며(현재 역할 E 보유), 두 번째 서명자 0x3795300b…는 지금 아무 역할도 보유하지 않습니다. 현재 상태의 레지스트리를 읽는 것만으로는 누가 업그레이드를 승인했는지 알 수 없습니다. 오직 트랜잭션 기록만이 알려줍니다. 이것은 "철회가 즉시 이루어지지 않는다"는 속성을 반대쪽에서 본 것입니다: 역할은 이동하므로 누가 무엇을 보유하는지의 스냅샷은 누가 무엇을 했는지의 기록이 아닙니다.

And the upgrade did not fix any of the defects in this review. The implementation it installed, 0xe42d38b0…, is the one all of Part 1 describes. We cannot tell from the chain whether a third-party audit was performed before the upgrade; what we can say is that if one was, the defects in Part 1 survived it. 그리고 업그레이드는 이 검토의 어떤 결함도 고치지 못했습니다. 설치된 구현 0xe42d38b0…는 파트 1 전체가 설명하는 바로 그 구현입니다. 업그레이드 전에 제3자 감사가 수행되었는지 체인에서 알 수는 없습니다. 말할 수 있는 것은, 있었다면 파트 1의 결함이 그 감사를 통과했다는 것입니다.

Part 2: Does it match Hong Kong's stablecoin framework?

파트 2: 홍콩의 스테이블코인 프레임워크와 일치합니까?

Hong Kong's Stablecoins Ordinance took effect on August 1, 2025, and licensed issuers are supervised under the HKMA's Guideline on Supervision of Licensed Stablecoin Issuers. We compared only the clauses a smart contract can satisfy on its own; reserve backing, custody, and off-chain key ceremonies are out of scope for an on-chain review. For each clause below we state what the guideline requires, what the contract does, and where the two diverge. The comparison is summarized here and detailed in the sections that follow. 홍콩의 스테이블코인 조례는 2025년 8월 1일부터 시행되었으며, 허가된 발행자는 HKMA의 허가 스테이블코인 발행자 감독 지침에 따라 감독받습니다. 우리는 스마트 계약이 자체적으로 충족할 수 있는 조항만 비교했습니다. 준비금 담보, 보관, 오프체인 키 절차는 온체인 검토 범위를 벗어납니다. 아래 각 조항에 대해 지침이 요구하는 것, 계약이 하는 것, 그리고 둘이 어디서 갈라지는지를 서술합니다. 비교 결과는 여기에 요약되어 있으며 다음 섹션에서 자세히 설명합니다.

HKMA clause What it requires Where the contract diverges Verdict
6.5.3 High-risk operations must not be unilateral (multi-signature, and mitigants such as velocity limits or timelocks) mint, burn, pause, and freeze each execute with one key; no timelock (execution is atomic with the final signature) Diverges
6.5.4 Segregate duties across authorised persons; revoke authority immediately one account holds six roles, so execution and audit overlap; a counted approval survives a later revocation Diverges
6.5.5 Third-party audit for every code change; correct, consistent, free of vulnerabilities the Part 1 defects are live in the deployed implementation; isActive and provider revocation do not do what they are named to do Diverges
Effectiveness of compliance controls blacklist, freeze, whitelist, and KYC controls must be effective KYC gating and provider revocation are non-functional in the deployed code Diverges
2.2.3 Frozen or destroyed coins remain fully backed and reconcilable destroy emits Transfer to address(this) rather than address(0) and leaves the net-issuance counters untouched; supply reconstructed from events drifts Concern
HKMA 조항 요구 사항 계약이 어긋나는 지점 판정
6.5.3 고위험 작업이 일방적이지 않아야 함 (다중 서명, 속도 제한이나 타임락 같은 완화 장치) mint, burn, pause, freeze가 각각 하나의 키로 실행됨; 타임락 없음 (실행이 마지막 서명과 원자적으로 발생) 불일치
6.5.4 권한자 간 직무 분리; 권한을 즉시 철회 가능해야 함 하나의 계정이 여섯 개 역할을 보유하여 실행과 감사가 중복됨; 집계된 승인이 이후 철회에도 생존함 불일치
6.5.5 모든 코드 변경에 제3자 감사; 정확하고 일관되며 취약점이 없어야 함 파트 1의 결함이 배포된 구현에 존재; isActive와 제공자 철회가 이름이 의미하는 대로 작동하지 않음 불일치
규정 준수 통제의 효과성 블랙리스트, 동결, 화이트리스트, KYC 통제가 효과적이어야 함 KYC 게이팅과 제공자 철회가 배포된 코드에서 작동하지 않음 불일치
2.2.3 동결 또는 파괴된 코인이 완전히 담보되고 조정 가능해야 함 destroy가 Transferaddress(0)가 아닌 address(this)로 방출하고 순발행 카운터를 변경하지 않음; 이벤트에서 재구성된 공급이 어긋남 우려

Paragraph 6.5.3: high-risk operations must not be unilateral

6.5.3항: 고위험 작업은 일방적이어서는 안 됩니다

What it requires. High-risk operations should be designed so that no single party can perform them unilaterally, for example through a multi-signature protocol, and the guideline lists further mitigants such as velocity limits and time-delayed (timelock) controls. 요구 사항. 고위험 작업은 예를 들어 다중 서명 프로토콜을 통해 어떤 단일 당사자도 일방적으로 수행할 수 없도록 설계되어야 하며, 지침은 속도 제한(velocity limits) 및 시간 지연(타임락) 통제와 같은 추가 완화 장치를 나열합니다.

What the contract does. Read from the governance contract's authorizationMatrix (the full matrix is in the table in 1.2), the supply and emergency operations each require a single role, at a quota of one: 계약이 하는 일. 거버넌스 계약의 authorizationMatrix(전체 매트릭스는 1.2의 표에 있음)에서 읽으면, 공급 및 비상 작업은 각각 쿼터 1의 단일 역할을 요구합니다:

Operation Required signature
mintToDeposit ROLE_C x1
burnFrom ROLE_C x1
freeze ROLE_C x1
pause ROLE_D x1
destroyBlackFunds ROLE_D x1
작업 필요한 서명
mintToDeposit ROLE_C x1
burnFrom ROLE_C x1
freeze ROLE_C x1
pause ROLE_D x1
destroyBlackFunds ROLE_D x1

Where it diverges. Minting, burning, pausing, and freezing can each be executed by one key. This does not meet the "no single party unilaterally" requirement. Nor is there a timelock: as shown in 1.2, once quorum is reached the action executes in the same transaction, so a time delay, one of the mitigants the guideline names, is also absent. The contract does implement a supply velocity limit (whenWithinRiskThresholds), so that mitigant is present, but it does not substitute for multi-signature control on the operations themselves. 어긋나는 지점. mint, burn, pause, freeze는 각각 하나의 키로 실행될 수 있습니다. 이는 "단일 당사자의 일방적 행위 금지" 요구사항을 충족하지 못합니다. 타임락도 없습니다: 1.2에서 보았듯이 쿼럼에 도달하면 작업이 같은 트랜잭션에서 실행되므로 지침이 명명한 완화 장치 중 하나인 시간 지연도 없습니다. 계약은 공급 속도 제한(whenWithinRiskThresholds)을 구현하므로 그 완화 장치는 존재하지만, 작업 자체에 대한 다중 서명 통제를 대체하지는 않습니다.

Paragraph 6.5.4: segregation of duties and immediate revocation

6.5.4항: 직무 분리와 즉시 철회

What it requires. Different operations should be segregated across different authorised persons, and an authorised person's authority should be revocable immediately. 요구 사항. 서로 다른 작업은 서로 다른 권한자에게 분리되어야 하며, 권한자의 권한은 즉시 철회 가능해야 합니다.

What the contract does. One externally owned account holds six roles (issuance, freezing, KYC administration, and all four auditor roles), so the execution and audit roles overlap. Role changes themselves also require only role A plus role B (see 1.2), so the same small group decides who is authorized and can execute and upgrade. And a signature already counted is not re-validated if the signer's role is later revoked. 계약이 하는 일. 하나의 외부 소유 계정이 여섯 개 역할(발행, 동결, KYC 관리, 그리고 네 개의 감사 역할 모두)을 보유하므로 실행 역할과 감사 역할이 중복됩니다. 역할 변경 자체도 역할 A + 역할 B만 요구하므로(1.2 참조), 누가 권한을 부여받는지를 결정하고 실행하고 업그레이드할 수 있는 사람이 동일한 소규모 그룹입니다. 그리고 이미 집계된 서명은 서명자의 역할이 나중에 철회되어도 재검증되지 않습니다.

Where it diverges. Duties are concentrated rather than segregated, and revocation is not immediate: a revoked signer's earlier approval still counts toward a later execution. 어긋나는 지점. 직무가 분리되지 않고 집중되어 있으며, 철회가 즉시 이루어지지 않습니다: 철회된 서명자의 이전 승인은 이후 실행에 여전히 집계됩니다.

Paragraph 6.5.5: audit every code change; correct, consistent, no vulnerabilities

6.5.5항: 모든 코드 변경 감사; 정확하고 일관되며 취약점이 없어야 함

What it requires. A qualified third party should audit the smart contracts for every code change, and confirm they are (i) implemented correctly, (ii) consistent with the intended functionality, and (iii) free of vulnerabilities to a high level of confidence. 요구 사항. 자격을 갖춘 제3자가 모든 코드 변경에 대해 스마트 계약을 감사하고, (i) 올바르게 구현되었고, (ii) 의도된 기능과 일치하며, (iii) 높은 수준의 확신으로 취약점이 없음을 확인해야 합니다.

What the contract does. The current implementation is the one installed by the July 10 upgrade (traced in 1.5), and the defects in Part 1 are live in it. 계약이 하는 일. 현재 구현은 7월 10일 업그레이드로 설치된 것이며(1.5에서 추적), 파트 1의 결함이 그 안에 존재합니다.

Where it diverges. We cannot see off-chain whether an audit was performed, but the outcome does not meet the standard either way. Because isActive and provider revocation do not do what they are named to do, conditions (i) and (ii) are not met; and with the defects in Part 1 present in the deployed code, (iii) is not met either. 어긋나는 지점. 감사가 수행되었는지 오프체인에서는 알 수 없지만, 결과는 어느 쪽이든 기준을 충족하지 못합니다. isActive와 제공자 철회가 이름이 의미하는 대로 작동하지 않으므로 (i)과 (ii)를 충족하지 못하며, 파트 1의 결함이 배포된 코드에 존재하므로 (iii)도 충족하지 못합니다.

Effectiveness of the compliance controls

규정 준수 통제의 효과성

What it requires. The guideline's lifecycle model (blacklist, freeze, whitelist, KYC) presumes those controls are effective. 요구 사항. 지침의 수명주기 모델(블랙리스트, 동결, 화이트리스트, KYC)은 그러한 통제가 효과적이라고 전제합니다.

What the contract does. As shown in 1.1, KYC gating and provider revocation are not functional in the deployed code. 계약이 하는 일. 1.1에서 보았듯이 KYC 게이팅과 제공자 철회는 배포된 코드에서 작동하지 않습니다.

Where it diverges. A control that is required to work does not work. This is a substantive gap, not a formality. 어긋나는 지점. 작동해야 하는 통제가 작동하지 않습니다. 이는 형식적인 문제가 아니라 실질적인 공백입니다.

Paragraph 2.2.3: frozen or destroyed coins remain fully backed and reconcilable

2.2.3항: 동결 또는 파괴된 코인은 완전히 담보되고 조정 가능해야 함

What it requires. Stablecoins that are frozen or destroyed by enforcement action should remain fully backed, so that supply and reserves can be reconciled. 요구 사항. 집행 조치로 동결되거나 파괴된 스테이블코인은 완전히 담보로 유지되어야 하므로 공급과 준비금을 조정할 수 있어야 합니다.

What the contract does. The usual remediation flow for a regulated stablecoin is to burn the funds at a bad address and later re-issue an equal amount to the victim as a separate mint (this is how USDT's destroyBlackFunds plus issue works). HKDAP's destroy reduces _totalSupply, which is a burn, but it never credits balances[address(this)], it emits a Transfer to address(this) instead of address(0), and it leaves the net-issuance counters that the mint limit uses untouched: 계약이 하는 일. 규제 스테이블코인의 일반적인 구제 흐름은 불량 주소의 자금을 소각하고 나중에 피해자에게 동일한 금액을 별도의 mint로 재발행하는 것입니다(USDT의 destroyBlackFunds + issue가 이렇게 작동합니다). HKDAP의 destroy는 _totalSupply를 감소시키므로 소각이지만, balances[address(this)]를 증가시키지 않고, Transferaddress(0) 대신 address(this)로 방출하며, mint 한도가 사용하는 순발행 카운터를 건드리지 않습니다:

// 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    }

Where it diverges. The state change is a burn, but the event says the tokens moved to the contract, and nothing is ever held there (balances[address(this)] stays zero). An indexer would credit address(this) with tokens it does not hold, and the total supply reconstructed from events would not match the chain. It also confuses the remediation itself: because the tokens are burned rather than parked, a re-issue to the victim must be a fresh mint, yet the misleading Transfer(..., address(this), ...) suggests the contract now custodies them and could forward them, which it cannot. A clean burn to address(0) plus a separate re-issue would be both correct and reconcilable. As written, the on-chain accounting that a reserve reconciliation depends on drifts from the chain's true state. This is a concern rather than a clean pass. 어긋나는 지점. 상태 변경은 소각이지만 이벤트는 토큰이 계약으로 이동했다고 말하며, 그곳에는 아무것도 보유되지 않습니다(balances[address(this)]는 0으로 유지됩니다). 인덱서는 보유하지 않는 토큰을 address(this)에 적립하고, 이벤트에서 재구성된 총 공급은 체인과 일치하지 않게 됩니다. 또한 구제 절차 자체를 혼란스럽게 만듭니다: 토큰이 보관되지 않고 소각되므로 피해자에 대한 재발행은 새로운 mint여야 하는데, 오해를 부르는 Transfer(..., address(this), ...)는 계약이 지금 그 토큰을 보관하고 있으며 전달할 수 있다는 것을 암시하지만 실제로는 그럴 수 없습니다. address(0)로의 깔끔한 소각과 별도의 재발행이 정확하고 조정 가능합니다. 작성된 대로, 준비금 조정이 의존하는 온체인 회계가 체인의 실제 상태에서 어긋납니다. 이는 완전한 통과가 아니라 우려 사항입니다.

We limit these findings to what the chain shows. Whether reserves are fully backed, whether keys sit in an HSM or an air-gapped environment, and whether transactions are simulated off-chain before signing are not visible from the contract, and we make no claim about them. 우리는 이러한 발견을 체인이 보여주는 것으로 한정합니다. 준비금이 완전히 담보되어 있는지, 키가 HSM이나 에어갭 환경에 있는지, 서명 전에 트랜잭션이 오프체인에서 시뮬레이션되는지는 계약에서 볼 수 없으며, 이에 대해 어떤 주장도 하지 않습니다.

Conclusion

결론

The picture is consistent across both axes of the review. As software, HKDAP contains functional defects, including compliance controls that do not execute as written, and it shows several signs of a pre-production build. As a regulated stablecoin, several of its on-chain properties conflict with specific clauses of the HKMA guideline. On the on-chain evidence, and setting aside the off-chain matters we cannot see, the deployed contract does not yet meet the standard a commercial stablecoin should meet. 검토의 두 축에서 그림은 일관됩니다. 소프트웨어로서 HKDAP는 작성된 대로 실행되지 않는 규정 준수 통제를 포함한 기능적 결함이 있으며, 프로덕션 이전 빌드의 여러 징후를 보여줍니다. 규제 스테이블코인으로서 여러 온체인 속성이 HKMA 지침의 특정 조항과 충돌합니다. 온체인 증거에 근거하고, 우리가 볼 수 없는 오프체인 사항을 제쳐두면, 배포된 계약은 상업용 스테이블코인이 충족해야 하는 기준을 아직 충족하지 못합니다.

Two observations follow, and they are worth stating plainly. 두 가지 관찰이 뒤따르며, 이를 분명히 말할 가치가 있습니다.

First, issuing on a public chain changes where compliance is decided. A requirement such as "no single party should be able to act unilaterally" is satisfied or not by the role checks in the deployed code, and that code is public. The implementation, rather than the licence or the documentation, is where such a requirement is actually met or missed, and anyone can verify which. 첫째, 공개 체인에서 발행하는 것은 규정 준수가 결정되는 지점을 바꿉니다. "어떤 단일 당사자도 일방적으로 행동할 수 없어야 한다"는 요구사항은 배포된 코드의 역할 검사에 의해 충족되거나 충족되지 않으며, 그 코드는 공개되어 있습니다. 그러한 요구사항이 실제로 충족되거나 놓치는 곳은 라이선스나 문서가 아니라 구현이며, 누구나 어느 쪽인지 검증할 수 있습니다.

Second, the "Beta Access" label does not change the risk profile of the deployed code. The contract is live on Ethereum mainnet, administered by real keys, and represents a claim on Hong Kong dollars. It should therefore be held to production standards regardless of how it is labeled. 둘째, "Beta Access" 라벨은 배포된 코드의 위험 프로필을 바꾸지 않습니다. 계약은 이더리움 메인넷에서 실제 키에 의해 관리되며 홍콩 달러에 대한 청구권을 나타냅니다. 따라서 라벨과 관계없이 프로덕션 표준으로 취급되어야 합니다.

A common thread runs under the specific findings. The architecture reinvents, in bespoke form, primitives the ecosystem already provides and has audited at scale: a from-scratch approval engine and role layer where a Safe multisig with OpenZeppelin's AccessManager and TimelockController would do; a hand-written linked-list collection in place of EnumerableSet; a modified proxy in place of the standard TransparentUpgradeableProxy; and a hand-written ERC-20 in place of OpenZeppelin's. Most of the defects in this review live in that custom machinery, not in the parts that reuse standard components. It reads like a system abstracted the way general-purpose software is, rather than composed from the small, audited building blocks that on-chain development favors, where every layer of custom abstraction is also gas, attack surface, and upgrade risk. A design built from those standard components would be smaller, safer, and easier to review, and it would come with the things this system currently lacks: a deliberation window from a timelock, and named, legible roles. 구체적인 발견 사항 아래에는 공통된 맥락이 흐릅니다. 이 아키텍처는 생태계가 이미 제공하고 대규모로 감사한 기본 구성 요소를 맞춤형 형태로 재발명합니다: Safe 멀티시그와 OpenZeppelin의 AccessManager 및 TimelockController로 충분한 곳에 처음부터 작성된 승인 엔진과 역할 계층, EnumerableSet 대신 손으로 작성한 연결 리스트 컬렉션, 표준 TransparentUpgradeableProxy 대신 수정된 프록시, OpenZeppelin 대신 손으로 작성한 ERC-20. 이 검토의 대부분의 결함은 표준 구성 요소를 재사용하는 부분이 아니라 그 맞춤형 메커니즘에 있습니다. 이는 일반 목적 소프트웨어처럼 추상화된 시스템처럼 읽히며, 온체인 개발이 선호하는 작고 감사된 구성 요소로 조합된 것이 아닙니다. 온체인 개발에서 맞춤형 추상화의 모든 계층은 가스, 공격 표면, 업그레이드 위험이기도 합니다. 표준 구성 요소로 구축된 설계는 더 작고, 더 안전하고, 검토하기 쉬우며, 이 시스템이 현재 결여하고 있는 것들, 즉 타임락의 숙고 창과 명명되고 읽을 수 있는 역할을 갖추게 될 것입니다.

The issues described here are addressable. Restoring multi-signature on high-risk operations, separating execution from audit, fixing the KYC revocation logic, unifying the transfer checks, removing the debug code, and requiring a third-party audit before each upgrade would resolve most of them. Deploying on mainnet with verified source is also what made this review possible, and it is the right default. Continuous on-chain security and compliance review of this kind is what we do at BlockSec, and we are glad to help. 여기서 설명된 문제들은 해결 가능합니다. 고위험 작업에 다중 서명을 복원하고, 실행과 감사를 분리하고, KYC 철회 로직을 수정하고, 전송 검사를 통일하고, 디버그 코드를 제거하고, 각 업그레이드 전에 제3자 감사를 요구하면 대부분 해결될 것입니다. 검증된 소스와 함께 메인넷에 배포하는 것이 이 검토를 가능하게 만든 것이기도 하며, 올바른 기본값입니다. 이러한 종류의 지속적인 온체인 보안 및 규정 준수 검토는 BlockSec이 하는 일이며, 기꺼이 돕겠습니다.

Get Real-Time Protection with Phalcon Security

Audits alone are not enough. Phalcon Security detects attacks in real time and blocks threats mid-flight.

phalcon security