Eine BlockSec-Sicherheits- und Compliance-Prüfung von HKDAP (Anchorpoint), Stand 13. August 2026.
Kurzfassung. Wir haben den bereitgestellten Contract von HKDAP geprüft, dem ersten regulierten Stablecoin, der in Hongkong ausgegeben wurde und live auf dem Ethereum-Mainnet läuft, und er ist nicht produktionsreif. Seine KYC- und Widerrufskontrollen funktionieren nicht wie beschrieben, seine Governance ist so stark konzentriert, dass ein einzelner Schlüssel prägen, verbrennen oder einfrieren kann, und mehrere seiner On-Chain-Eigenschaften stehen im Widerspruch zur eigenen Richtlinie der HKMA. Ein gemeinsamer roter Faden zieht sich darunter hindurch: der Contract erfindet von Grund auf die Primitiven neu, die das Ökosystem bereits bereitstellt und in großem Maßstab prüft (ein Multisig, eine Zugriffskontrollschicht, ein Timelock, das ERC-20 selbst), und die meisten Mängel finden sich in dieser maßgeschneiderten Mechanik statt in den Teilen, die Standardkomponenten wiederverwenden. Die Kennzeichnung „Beta Access“ schließt diese Lücke nicht.
Am 12. August 2026 startete Anchorpoint die erste Phase von HKDAP, einen an den Hongkong-Dollar gekoppelten Stablecoin. Dies ist ein bedeutender Start. Anchorpoint, ein Joint Venture unter Führung der Standard Chartered Bank (Hong Kong) mit HKT und Animoca Brands, hält eine von nur zwei Stablecoin-Emittentenlizenzen, die die HKMA vergeben hat, von 36 Antragstellern, und HKDAP zählt zu den ersten Stablecoins, die unter Hongkongs Stablecoins Ordinance ausgegeben wurden.
Im Gegensatz zu den meisten Produkten einer regulierten Bank ist HKDAP direkt einsehbar. Er läuft auf dem Ethereum-Mainnet, und sein Contract-Quellcode ist auf Etherscan verifiziert. Das ist eine nützliche Eigenschaft: Bei einem auf diese Weise ausgegebenen Stablecoin werden die Regeln, die Ausgabe, Übertragung und Einfrieren steuern, nicht in einem Dokument beschrieben, sondern in Code implementiert, den jeder lesen kann und der exakt so ausgeführt wird, wie er geschrieben ist. Eine Lizenz ist eine Behauptung; der bereitgestellte Contract ist die Umsetzung dieser Behauptung, und sie ist öffentlich.
Das macht eine konkrete Überprüfung möglich, und genau das haben wir getan. Wir haben den bereitgestellten Contract entlang zweier Achsen untersucht. Erstens als Software: Ist er korrekt und auf Produktionsniveau? Zweitens als regulierter Stablecoin: Entspricht sein On-Chain-Verhalten der Guideline on Supervision of Licensed Stablecoin Issuers der HKMA?
Die Befunde sind entlang beider Achsen konsistent. Der Contract enthält mehrere funktionale Mängel, darunter Compliance-Kontrollen, die nicht wie beschrieben funktionieren. Seine Governance ist stark konzentriert, wobei mehrere risikoreiche Vorgänge von einem einzigen Schlüssel ausgeführt werden können. Und eine Reihe seiner On-Chain-Eigenschaften stehen im Widerspruch zu bestimmten Klauseln der HKMA-Richtlinie. Unsere Einschätzung ist, dass der Contract selbst als Beta nicht das Qualitätsniveau erreicht, das ein kommerzieller Stablecoin erfordert.
Der Rest dieses Artikels stellt diese Überprüfung vor, aktuell zum Stand 13. August 2026. Unsere Überprüfung basiert auf öffentlich bereitgestelltem Code und beobachtbaren On-Chain-Fakten. Wir stellen keine Behauptungen zu Sachverhalten auf, die die Blockchain nicht belegen kann, wie etwa Reservendeckung oder Off-Chain-Schlüsselverwahrung.
Wie wir den Contract gefunden haben
Wir sind vom Emittenten ausgegangen, nicht von einer Token-Liste. Anchorpoints Unternehmenspräsenz verlinkt auf seine Website unter anchorpoint.hk. Die Beta-Access-Seite nennt die Bereitstellung: Ethereum-Mainnet, Proxy 0x87622385F960fcCB3121d6D0A9513bd1D9Bed6cA, dessen Quellcode auf Etherscan verifiziert ist.
Von dort aus haben wir das gesamte System On-Chain kartiert: den Token-Proxy und seine Implementierung (ControllableAHKD, 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc), den Governance-Contract, der ihn verwaltet (0x47dd47a776902bee03abdd5aeff43f41b0ffbc2b), das übergeordnete Rollenregister (0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c), sowie fünf Compliance-Module, jedes mit eigenem Governance-Contract. Jede unten aufgeführte Beziehung wurde durch das Lesen von Storage-Slots und das Aufrufen von View-Funktionen im Mainnet verifiziert, nicht allein aus dem Quellcode abgeleitet.
Wie der Contract aussieht
Die drei Schichten von HKDAP: eine Governance-Kontrollebene verwaltet den Token, und der Token fragt bei jeder Übertragung fünf Compliance-Module ab.

Der Token von HKDAP verwendet nicht die Standard-ERC-20-Referenzimplementierung von OpenZeppelin; seine ERC-20-Logik wurde von Grund auf neu geschrieben (hier haben mehrere der unten genannten Fehler ihren Ursprung). Das System hat drei Schichten:
- Der Token.
ControllableAHKD, hinter einem aufrüstbaren Proxy. Ein kontrolliertes ERC-20 mit Prägung, Verbrennung, Pausierung und erzwungener Vernichtung, plus Compliance-Prüfungen, die in jede Übertragung eingebunden sind. - Eine selbstgebaute M-von-N-Governance-Engine. Jede privilegierte Aktion (Upgrade, Prägung, Verbrennung, Pausierung, Blacklist, Einfrieren, Ändern eines Compliance-Moduls) durchläuft eine Anfrage-, Genehmigungs- und Ausführungszeremonie statt eines einfachen Multisigs.
- Fünf Compliance-Module. Blacklist, Einfrieren, ein KYC-Aktivierungsdienst sowie Whitelists für Einzahlung und Rücknahme. Jedes Modul ist selbst ein Proxy, der von seinem eigenen Kontrollbehörden-Contract verwaltet wird.
Strukturell wiederholt sich diese Einheit aus „Kontrollbehörde plus Proxy plus Implementierung“ sechsmal (der Token plus fünf Module), und alle sechs Kontrollbehörden lösen ihre Rollen bei ein und demselben Register auf. Alle Kontrolle im System läuft letztlich bei diesem einen Register zusammen, und wer die Rollen darin umschreiben kann, hängt von einer sehr kleinen Menge an Signaturschlüsseln ab, wie wir im Detail unten zeigen.
Teil 1: Sicherheit und Fehler
Die Befunde in diesem Teil sind unten zusammengefasst; jede Zeile wird im angegebenen Abschnitt detailliert behandelt.
| Bereich | Befund | Wo | Auswirkung |
|---|---|---|---|
| 1.1 KYC- und Widerrufskontrollen funktionieren nicht | KYC-Widerruf ist toter Code | TokenHolderActivationServerLibrary.sol:221-235 |
isActive ignoriert die Abmeldung von Anbietern (die Schleife läuft nie, und es wird == statt = verwendet); fail-open. |
| Verifizierer-Abmeldung setzt niemals INACTIVE | TokenHolderActivationServer.sol:504-511 |
Ein „abgemeldeter“ Verifizierer kann Wallets weiterhin onboarden und deaktivieren; ein zweiter Aufruf revertiert. | |
| KYC-Nachweis wird On-Chain nie validiert | TokenHolderActivationServer.sol:575-578 |
Der Nachweis wird übergeben, aber verworfen; jeder Nachweis, einschließlich eines leeren Strings, wird akzeptiert. | |
| Ausnahmelimit für freie Übertragungen kann umgangen werden | ControllableAHKD.sol:337-535 |
freeTransferLimit wird pro Aufruf geprüft, nicht kumulativ; wenn als Obergrenze für nicht-KYC-geprüfte Aktivität genutzt, kann dies durch Aufteilung in Teilbeträge unter dem Limit umgangen werden. |
|
| 1.2 Governance ist übermäßig konzentriert | Risikoreiche Vorgänge sind Einzelsignatur | authorizationMatrix; Prägungstransaktion 0xa7e53c…b33d7 |
Prägen / Verbrennen / Einfrieren / KYC-Deaktivierung (Rolle C), Pausieren / Vernichten (Rolle D), Blacklist / Einfrieren aufheben (Rolle F) werden jeweils mit einem einzigen Schlüssel ausgeführt. |
| Ein Schlüsselpaar (A + B) aktualisiert alles | authorizationMatrix |
upgradeTo für den Token und alle fünf Module erfordert Rolle A + B; zwei Personen können jede Implementierung ersetzen. |
|
| Dasselbe Paar schreibt jede Rolle um | Register 0xa728… authorizationMatrix |
grantRole / revokeRole im Register erfordern ebenfalls Rolle A + B (ADMIN_ROLE wird nur vom Contract selbst gehalten; DEFAULT_ADMIN_ROLE = address(0)); kein externer Schlüssel kann Rollen direkt ändern. |
|
| Eine Adresse hält sechs Rollen | Rollenregister 0xa728… |
0x2f7f00… hält Rolle C sowie ADMIN_TOKEN_HOLDER und alle vier Auditor-Rollen; Ausführung und Prüfung überschneiden sich. |
|
| Widerruf erfolgt nicht sofort; kein Timelock | HybridControlEngine.sol:158-227 |
Eine gezählte Signatur wird nach Widerruf einer Rolle nicht erneut validiert; die Ausführung erfolgt atomar mit der letzten Signatur, ohne Verzögerung. | |
| Der Audit-Trail der Engine ist unzuverlässig | HybridControlEngine.sol:36-129, 177-196 |
evtApprove gibt address(0) als Unterzeichner aus; die Liste aktiver Anfragen verwendet Nonce 0 sowohl als reale ID als auch als Leer-Markierung, sodass beim Rückwärtsdurchlauf die erste Anfrage übersehen wird. |
|
| 1.3 Inkonsistente Übertragungsprüfungen | transfer und transferFrom erzwingen unterschiedliche Regeln |
ControllableAHKD.sol:313-502 |
Die frühzeitige Rückgabe bei Whitelist umgeht KYC nur über transferFrom; checkingMode prüft unterschiedliche Parteien; dieselbe Übertragung wird unterschiedlich geregelt. |
| 1.4 Anzeichen für einen Pre-Production-Build | Debug-Logs, falscher Kommentar, Name/Hash-Nichtübereinstimmung, hochgeladene Tests | UpgradeableProxy.sol:115-119; ControllableAHKD.sol:25 |
console.log im Proxy-Fallback (permanenter Gasverbrauch); Proxy-Kommentar widerspricht dem Code; Rollenname/Hash stimmen nicht überein; 117 hochgeladene Dateien inkl. Tests; Optimizer-Runs = 0. |
| 1.5 Das einzige Upgrade beließ die Mängel im System | Rückverfolgung des einzigen Upgrades | Tx 0x742372…85136, 0xa7a400…630c |
Bereitstellung 28. April → Upgrade 10. Juli; die beiden Transaktionen der A+B-Zeremonie lagen einen Block auseinander (~12 s); jeder Mangel aus Teil 1 findet sich in der installierten Implementierung. |
1.1 Die KYC- und Widerrufskontrollen funktionieren nicht wie beschrieben
Gemäß der Regulierung müssen die B-seitigen (institutionellen) Nutzer, die HKDAP bedient, eine KYC-Prüfung durchlaufen. Im Contract bedeutet das, dass er mindestens zwei Dinge tun muss: KYC bei Übertragungen erzwingen, sodass eine Wallet, die keine KYC-Prüfung bestanden hat, blockiert wird, und den Zugriff widerrufen, wenn ein Identitätsanbieter oder ein Inhaber entfernt wird. Bei HKDAP ist dieser Pfad an drei unabhängigen Stellen defekt.
KYC-Widerruf ist toter Code. Der Token steuert Übertragungen, indem er isActive(address) beim KYC-Modul aufruft. isActive soll zwei Dinge tun: erstens einen Basiswert aus einem Zähler pro Wallet setzen, der aktiv ist, wenn eine Wallet öfter KYC-aktiviert als deaktiviert wurde; zweitens diesen Wert auf falsch verengen, wenn ein Identitätsanbieter, der für die Wallet gebürgt hat, inzwischen abgemeldet wurde. Die beiden decken zwei Arten des Widerrufs ab, den Widerruf der KYC eines einzelnen Inhabers (der Zähler) und den Widerruf eines ganzen Anbieters, sodass jede von ihm onboardete Wallet mit ihm fällt (die Schleife). Nur das Erste passiert tatsächlich.
// 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 }
Zeile 221 ist eine echte Zuweisung (=), und sie ist die einzige, die wirksam wird: active wird zu deactivationCount < activationCount. Die Schleife (Zeilen 226-235) soll diesen Wert eigentlich verengen, scheitert aber zweifach. Ihre Bedingung in Zeile 226, entryCount > chainedItemList.entryCount, lautet 0 > N, sodass der Rumpf nie ausgeführt wird; und selbst wenn doch, verwendet Zeile 231 ==, einen Vergleich, dessen Ergebnis verworfen wird, wo eigentlich mit = zugewiesen werden sollte. isActive liefert daher nur den Zählervergleich und ignoriert den Widerruf des Anbieters vollständig. Das ist fail-open: Das Abmelden eines kompromittierten KYC-Anbieters hindert die von ihm onboardeten Wallets nicht daran, weiter Transaktionen durchzuführen. Und da unregisterVerifier diese Zähler nicht berührt, behalten die Wallets eines widerrufenen Anbieters einen positiven Zähler und bleiben aktiv.
Die Abmeldung von Anbietern ist auch auf der anderen Seite defekt. unregisterVerifier verschiebt nur einen Knoten in einer verketteten Liste; es setzt den Verzeichnisstatus des Anbieters nie auf 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 }
Da der Status ACTIVE bleibt, kann ein „abgemeldeter“ Verifizierer weiterhin Inhaber registrieren und deaktivieren, der Zweig, der einen Anbieter reaktivieren soll, ist unerreichbar, und ein zweiter Aufruf von unregisterVerifier führt zu einem Unterlauf und revertiert.
Der KYC-Nachweis wird On-Chain nie validiert. Wenn ein autorisierter Verifizierer eine Wallet über registerOrRenew registriert oder erneuert (abgesichert, sodass nur ein aktuell aktiver Verifizierer diese Funktion aufrufen kann), erreicht der Ablauf _checkKYCProof, das den eingereichten Nachweis gegen das Schema des Anbieters validieren soll. Laut Schnittstelle ist dieser Nachweis „ein URI für ein Orakel oder ein signierter Hash aus einer verifizierbaren Quelle“. Die Funktion ignoriert ihn:
// 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 }
Der Nachweis erreicht diese Funktion tatsächlich. registerOrRenew leitet den eingereichten kycProof über _registerOrRenew weiter und übergibt ihn als zweites Argument. Aber in Zeile 575 landet dieses Argument in einem Parameter ohne jeglichen Namen, den auch der darüberstehende Doc-Kommentar der Funktion auslässt, und der Rumpf liest ihn nie aus. Die Funktion bestätigt lediglich, dass der Anbieter aktiv ist, und gibt in Zeile 577 „true“ zurück, sodass der Nachweis verworfen statt geprüft wird. Dies ist keine Umgehung durch Außenstehende, da nur ein aktiver Verifizierer diese Funktion erreichen kann, aber On-Chain führt der Contract keine Validierung des KYC-Nachweises durch, sodass dessen Integrität vollständig auf dem Off-Chain-Verifizierer beruht. Ein kompromittierter oder nachlässiger Verifizierer kann jede Wallet mit jedem Nachweis aktivieren, einschließlich eines leeren Strings.
Zusammen bedeuten diese drei Punkte, dass eine Kerneigenschaft der Compliance, die Fähigkeit, Zugriff zu gewähren und zu widerrufen, nicht wie beschrieben funktioniert.
Über diese drei hinaus gibt es eine verwandte Schwäche: die Ausnahme für freie Übertragungen kann umgangen werden. Der Token hat ein freeTransferLimit, und eine Übertragung darunter überspringt die isActive-Prüfung (KYC). Aber das Limit wird nur mit dem Betrag der aktuellen Übertragung verglichen; der Contract führt keine kumulative Summe pro Adresse oder Zeitraum. Wenn das Limit also als Obergrenze für nicht-KYC-geprüfte Aktivität dient, kann ein Inhaber einen beliebigen Gesamtbetrag bewegen, indem er ihn in wiederholte Übertragungen aufteilt, die jeweils knapp unter dem Limit liegen, was die Obergrenze wirkungslos macht.
1.2 Governance ist übermäßig konzentriert, und risikoreiche Vorgänge sind Einzelsignaturen
Wir haben jede Rolle und jeden Rolleninhaber On-Chain erfasst. Zwei Dinge fallen vorab auf.
Erstens haben die Rollen, die die M-von-N-Zeremonien autorisieren, keine lesbaren Namen. In der bereitgestellten Konfiguration erscheinen sie nur als 32-Byte-Hashes, und keiner davon entspricht einer benannten Rollenkonstante im verifizierten Quellcode (die benannten Rollen wie SUPPLY_CONTROLLER_ROLE werden von den Contracts selbst gehalten, nicht von den Unterzeichnern). Wir bezeichnen die sechs Signatur-Rollen mit A bis F. Dass die mächtigsten Rollen im System undurchsichtige Bezeichner sind, ist bereits an sich eine Schwäche: Es erschwert die Überprüfung der Governance im Vergleich zu benannten Rollen.
Zweitens ist die Anzahl der Inhaber gering. Die folgende Tabelle wurde On-Chain aus dem Rollenregister ausgelesen; Adressen sind gekürzt.
| Rolle (unsere Bezeichnung) | On-Chain-Hash | Inhaber | Was sie autorisiert |
|---|---|---|---|
| A | 0x37f0f656… |
0x747356d525bf47f495af7c6e42f3ab9b8ba87a4b |
erste Signatur für upgradeTo / changeAdmin / setControlAuthority |
| B | 0x95c27f81… |
0x8117a657df7a1c399231bfe26a096eb8f8ad5973, 0x5d9d9b28e83382e694916b0feeaf7a9badbb659c, 0xebb73f78e253539acceb4e6cc287095788eee5d4 |
die zweite Signatur bei fast jeder Zwei-Signatur-Aktion |
| C | 0xfa2fe896… |
0x2f7f00cc5334fe2861e485ff610f74890a0316ed |
mintToDeposit, burnFrom, freeze, KYC-Deaktivierung (einzeln) |
| D | 0x3dce3265… |
0x5092af62a1625fa57404557d3ad417474f3f494c |
pause, destroyBlackFunds, Registrierung/Abmeldung von Einzahlungs- & Rücknahmeadressen, registerVerifier (einzeln) |
| E | 0xfd21a76d… |
0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2 |
Änderung der Compliance-Module (setBlacklistServer usw.) |
| F | 0x510ac1ff… |
0x7daabe5092d3feea5cc19ddbb5380a4eac38f89c |
addBlackList, unfreeze (einzeln) |
Aus der Tabelle ergeben sich mehrere Punkte.
Risikoreiche Vorgänge mit Einzelsignatur. Die meisten risikoreichen Vorgänge erfordern eine einzelne Rolle bei einem Quorum von eins. Die folgende Tabelle wurde On-Chain aus der authorizationMatrix des Governance-Contracts des Tokens sowie der fünf Modul-Governance-Contracts ausgelesen (Einzelsignatur, sofern nicht eine zweite Signatur „+ B“ angegeben ist):
| Vorgang | Geregelt von | Erforderliche Signaturen |
|---|---|---|
mintToDeposit, burnFrom |
Token | C x1 |
freeze, batchFreeze |
Freezing-Modul | C x1 |
deactivate, adminDeactivate (KYC) |
KYC-Modul | C x1 |
pause, destroyBlackFunds |
Token | D x1 |
register, unregister (Einzahlung / Rücknahme) |
Verzeichnismodule | D x1 |
registerVerifier, unregisterVerifier |
KYC-Modul | D x1 |
addBlackList, batchBlackList |
Blacklist-Modul | F x1 |
unfreeze |
Freezing-Modul | F x1 |
removeBlackList |
Blacklist-Modul | F x1 + B x1 |
setBlacklistServer, setFreezingServer, setCheckingMode |
Token | E x1 + B x1 |
| Verzeichnisserver- und Versorgungslimit-Setter | Token | D x1 + B x1 |
upgradeTo / changeAdmin / setControlAuthority (Token und jedes Modul), unpause |
Token + Module | A x1 + B x1 |
Ausgabe, Einfrieren und KYC-Deaktivierung sind Einzelsignaturen (alle unter Rolle C); Pausieren, Vernichten, Verzeichnisänderungen und Verifizierer-Registrierung sind Einzelsignaturen unter Rolle D; Blacklisting und Aufheben von Einfrierungen sind Einzelsignaturen unter Rolle F. Nur Upgrades und Konfigurationsänderungen benötigen eine zweite Signatur. Man beachte die Asymmetrie: freeze und addBlackList benötigen eine Signatur, während removeBlackList zwei benötigt, sodass die Einschränkung eines Kontos leichter ist als seine Freigabe.
Dies ist nicht nur eine Lesart der Matrix; es ist bei einer live durchgeführten Prägung beobachtbar. Die jüngste Ausgabe zum Zeitpunkt der Erstellung, Tx 0xa7e53c…b33d7, ist eine einzelne Transaktion, die vom einzigen Rolle-C-Konto (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) an den Governance-Contract des Tokens gesendet wurde. Sie ruft request auf, und dieselbe Transaktion erreicht das Quorum und gibt das Prägungsereignis Transfer von address(0) aus, ohne separate Genehmigungstransaktion und ohne zweiten Unterzeichner. Da die Ausgabe innerhalb der eigenen Transaktion des Antragstellers abgeschlossen wird, hat ein einziger Schlüssel die Prägung sowohl angefordert als auch ausgeführt.
Es gibt auch nirgendwo in der Engine ein Timelock. Sobald die letzte erforderliche Signatur eintrifft, wird die Aktion in derselben Transaktion ausgeführt, ohne Verzögerung, in der sie überprüft, storniert oder angefochten werden könnte; die Engine speichert einen executedAt-Zeitstempel, prüft ihn aber nie. Selbst die Zwei-Signatur-Vorgänge werden also sofort abgeschlossen, sobald der zweite Schlüssel unterzeichnet.
Ein Schlüsselpaar aktualisiert alles. Das Upgrade des Tokens und aller fünf Compliance-Module erfordert dieselbe Anforderung, A plus B. Da A ein einzelnes Konto ist und B drei Konten sind, die sich eine Rolle teilen, können zwei Personen jede Implementierung im System ersetzen.
Dasselbe Paar kontrolliert auch die Rollentabelle. Rollen können nur im Register (HybridControlledAuthority, 0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c) über dessen eigene Zeremonie vergeben und entzogen werden; kein externer Schlüssel kann sie direkt ändern. Und dessen authorizationMatrix, On-Chain ausgelesen, erfordert für eine Rollenänderung dieselben Signaturen wie für ein Upgrade: Rolle A plus Rolle B. Die zwei Personen, die jede Implementierung ersetzen können, können also auch einen Schlüssel zur Rolle C hinzufügen, einen vorhandenen Inhaber entfernen und die gesamte Rollentabelle umschreiben. Diese Zwei-Signatur-Hürde ist besser als die oben genannten Einzelsignatur-Vorgänge, aber sie ist immer noch eine niedrige Hürde für die Wurzel des Systems, da Rolle A ein einzelnes Konto ohne Redundanz ist.
Eine Adresse, sechs Rollen. Der Inhaber der Rolle C (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) hält zudem die benannte ADMIN_TOKEN_HOLDER_ROLE (Verwaltung der KYC-Verifizierer) und ist Mitglied aller vier Auditor-Rollen (BLACKLIST_AUDITOR_ROLE, FREEZING_AUDITOR_ROLE, WHITELIST_AUDITOR_ROLE und AFL_TOKEN_AUDITOR_HOLDER_ROLE). Die Kompromittierung dieses einen Schlüssels bedeutet den Verlust der Ausgabe-, Einfrier- und KYC-Verwaltung auf einmal.
Die Auditor-Rollen haben zwei eigene Probleme. Erstens steuern sie die schreibgeschützten Getter für die Compliance-Listen, offenbar um zu kontrollieren, wer sie lesen darf, aber auf einer öffentlichen Chain ist das sinnlos: Der zugrunde liegende Speicher ist für jeden lesbar (das Auslesen von Storage-Slots war unsere Methode, um dieses System zu kartieren), sodass die Listen so oder so öffentlich sind, und die Einschränkung zeigt, dass das Design nicht berücksichtigte, dass es auf einer öffentlichen Chain läuft. Zweitens die Konzentration: Alle vier Auditor-Rollen liegen bei denselben 23 Konten, und die Inhaber der Ausführungsrollen A, C, D und F sind darunter, sodass dieselben Schlüssel, die prägen, verbrennen, einfrieren und blacklisten, auch in der Gruppe sitzen, die diese Aktionen überprüfen soll.
Widerruf erfolgt nicht sofort. In der Zeremonie-Engine wird die Rolle eines Unterzeichners einmal geprüft und das Quorum verringert; frühere Unterzeichner werden nie erneut validiert, sodass der spätere Widerruf einer Rolle eine bereits gezählte Stimme nicht zurückzieht:
// 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 }
Schließlich ist auch der eigene Audit-Trail der Engine unzuverlässig. Bei jeder Genehmigung gibt das evtApprove-Ereignis address(0) als Unterzeichner aus statt des tatsächlichen Genehmigers; der tatsächliche Unterzeichner überlebt nur im Absender der Transaktion und einem internen Datensatz, sodass Event-Logs nicht zuordnen können, wer eine Anfrage genehmigt hat. Und die Liste aktiver Anfragen verwendet Nonce 0 sowohl als reale Anfrage-ID als auch als deren Leer-Markierung, sodass Überwachungs- oder Genehmigungstools, die die Liste rückwärts durchlaufen, die Anfrage bei Nonce 0 übersehen. Keines der beiden ist kritisch, aber für ein reguliertes System, das einen sauberen Audit-Trail benötigt, mindern beide diesen.
1.3 Übertragungskontrollen sind zwischen transfer und transferFrom inkonsistent
Ein Teil des Unterschieds zwischen den beiden ist zu erwarten. Bei transferFrom ist der Initiator, msg.sender, ein genehmigter Ausgabeberechtigter statt der Quelle der Mittel, sodass der Code die tatsächliche Quelle from in jedem Modus explizit prüft; transfer muss dies nicht tun, weil dort msg.sender die Quelle ist. Diese Anpassung ist sinnvoll. Zwei weitere Unterschiede lassen sich nicht durch den Initiator erklären, und sie führen dazu, dass dieselbe wirtschaftliche Aktion nach unterschiedlichen Regeln geregelt wird.
Erstens werden die Einzahlungs- und Rücknahme-Whitelists nur im transferFrom-Pfad konsultiert, wo eine Mitgliedschaft eine frühzeitige Rückgabe auslöst, die die isActive-Prüfung (KYC) umgeht. transfer konsultiert sie nie. Ob ein Empfänger auf der Whitelist steht, hat nichts damit zu tun, wer die Übertragung initiiert hat, sodass derselbe Empfänger via transfer KYC unterliegt, dies aber via transferFrom umgehen kann:
// 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" ) ) ;
Zweitens bedeutet checkingMode in den beiden Funktionen etwas anderes. Bei transfer prüft der Modus „Source“ den Sender; bei transferFrom prüft der Modus „Source“ den Ausgabeberechtigten (msg.sender), während from in jedem Modus geprüft wird. Eine einzige Konfigurationseinstellung erzwingt also je nach Einstiegspunkt zwei unterschiedliche Richtlinien.
Die Auswirkung ist, dass die Übertragungskontrollen eines regulierten Tokens davon abhängen, welche Funktion verwendet wird, was sie schwer nachvollziehbar und je nach Konfiguration umgehbar macht.
1.4 Anzeichen für einen Pre-Production-Build
Über die spezifischen Logikfehler hinaus deuten mehrere Eigenschaften der Codebasis darauf hin, dass ein Pre-Production-Build im Mainnet bereitgestellt wurde.
Debug-Protokollierung in der Produktion. hardhat/console.log-Aufrufe finden sich durchgängig, auch im Fallback des Proxys, der bei jeder Nutzer-Transaktion ausgeführt wird. Da der Proxy selbst nicht aufrüstbar ist, ist dieser Overhead dauerhaft:
// 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 }
Ein Proxy-Header-Kommentar, der das Gegenteil des Codes beschreibt. Dieselbe Datei enthält die Dokumentation von OpenZeppelins TransparentUpgradeableProxy, die besagt, dass der Admin niemals zur Implementierung durchfallen kann. Dieser Contract macht bewusst das Gegenteil: Die Schutzmaßnahme in Zeile 117 ist auskommentiert, und der Admin fällt tatsächlich durch. Ein Prüfer, der dem Kommentar vertraut, würde die Vertrauensgrenze falsch modellieren.
Rollennamen, die nicht zu ihren Hashes passen. Die Rolle „elevated risk“ ist mit demselben Konstantennamen, aber unterschiedlichem keccak-String im Token und in den Modulen deklariert, was zwei unterschiedliche Rollen erzeugt:
// 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') ;
Die Bereitstellung vermeidet Probleme nur, weil jedes Modul eine eigene Kopie hält; ein zweiter Rollenname (AFL_TOKEN_HOLDER_AUDITOR_ROLE) ist auf dieselbe Weise vertauscht.
Weitere Anzeichen. Das Verifizierungspaket des Proxys hat 117 Dateien hochgeladen, einschließlich der Testsuite des Projekts, auf den öffentlichen Explorer, wodurch einem Leser die internen Tests und Grenzfälle bereitgestellt werden. Das jüngste Upgrade änderte nur die Bereinigung von Compiler-Warnungen, ohne Beteiligung eines Drittprüfers. Und der Optimizer ist auf null Durchläufe eingestellt, was die stark genutzten Pfade eines vielgenutzten Tokens teurer statt günstiger macht.
Keiner dieser Punkte ist für sich genommen schwerwiegend. Zusammen deuten sie darauf hin, dass der Code nicht die Release-Disziplin durchlaufen hat, die man von einem Contract erwartet, der im Mainnet Werte hält.
1.5 Das eine Upgrade des Contracts, nachverfolgt
Der Proxy wurde einmal aktualisiert. Er wurde am 28. April 2026 mit der Implementierung 0x8ff12fe3bed22d9e40afb4b98ef4dee28d94699e bereitgestellt, und am 10. Juli 2026 auf die aktuelle 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc aktualisiert. Da ein Upgrade eine On-Chain-Governance-Aktion ist, können wir genau sehen, wer es genehmigt hat.
upgradeTo erfordert Rolle A plus Rolle B. Das Upgrade bestand aus zwei Transaktionen in aufeinanderfolgenden Blöcken, etwa zwölf Sekunden auseinander:
- die Anfrage, Rolle A, von
0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2, in Block 25500519 (Tx0x742372…85136); - die Genehmigung, Rolle B, von
0x3795300b31429f9d37b0dc805528d9390ce87c50, in Block 25500520 (Tx0xa7a400…630c), die das Quorum erreichte und das Upgrade in derselben Transaktion durchführte.
Zwei Dinge fallen dabei auf. Erstens wurde die gesamte Zwei-Signatur-Zeremonie innerhalb eines einzigen Blockintervalls abgeschlossen. Von der Anfrage bis zur Ausführung verging ein Block; es gab kein Zeitfenster, in dem der zweite Unterzeichner die Änderung unabhängig hätte prüfen können, bevor sie live ging.
Zweitens ist keiner der beiden Unterzeichner aus dem aktuellen Rollenregister identifizierbar. Die Rollen wurden inzwischen rotiert: Der Antragsteller 0xa9315a… hält Rolle A nicht mehr (er hält heute Rolle E), und der zweite Unterzeichner 0x3795300b… hält heute keine Rolle mehr. Das heutige Register zu lesen, würde nicht verraten, wer das Upgrade autorisiert hat; nur die Transaktionshistorie tut das. Dies ist die Eigenschaft „Widerruf erfolgt nicht sofort“, von der anderen Seite betrachtet: Rollen bewegen sich, sodass eine Momentaufnahme, wer was hält, kein Protokoll darüber ist, wer was getan hat.
Und das Upgrade hat keinen der Mängel in diesem Bericht behoben. Die dabei installierte Implementierung, 0xe42d38b0…, ist diejenige, die der gesamte Teil 1 beschreibt. Wir können anhand der Chain nicht feststellen, ob vor dem Upgrade eine Prüfung durch Dritte durchgeführt wurde; was wir sagen können, ist: Falls doch, haben die in Teil 1 beschriebenen Mängel sie überstanden.
Teil 2: Entspricht der Contract Hongkongs Stablecoin-Rahmenwerk?
Hongkongs Stablecoins Ordinance trat am 1. August 2025 in Kraft, und lizenzierte Emittenten werden gemäß der Guideline on Supervision of Licensed Stablecoin Issuers der HKMA beaufsichtigt. Wir haben nur die Klauseln verglichen, die ein Smart Contract selbst erfüllen kann; Reservendeckung, Verwahrung und Off-Chain-Schlüsselzeremonien liegen außerhalb des Umfangs einer On-Chain-Prüfung. Für jede Klausel unten geben wir an, was die Richtlinie verlangt, was der Contract tut und wo die beiden voneinander abweichen. Der Vergleich wird hier zusammengefasst und in den folgenden Abschnitten detailliert.
| HKMA-Klausel | Was sie verlangt | Wo der Contract abweicht | Urteil |
|---|---|---|---|
| 6.5.3 | Risikoreiche Vorgänge dürfen nicht unilateral sein (Mehrfachsignatur, sowie Abhilfemaßnahmen wie Geschwindigkeitslimits oder Timelocks) | Prägung, Verbrennung, Pausierung und Einfrieren werden jeweils mit einem einzigen Schlüssel ausgeführt; kein Timelock (Ausführung ist atomar mit der letzten Signatur) | Abweichend |
| 6.5.4 | Aufgabentrennung unter autorisierten Personen; sofortiger Widerruf der Befugnis | ein Konto hält sechs Rollen, sodass sich Ausführung und Prüfung überschneiden; eine gezählte Genehmigung übersteht einen späteren Widerruf | Abweichend |
| 6.5.5 | Prüfung durch Dritte bei jeder Codeänderung; korrekt, konsistent, frei von Schwachstellen | die Mängel aus Teil 1 sind in der bereitgestellten Implementierung aktiv; isActive und der Widerruf von Anbietern tun nicht, was ihr Name verspricht |
Abweichend |
| Wirksamkeit der Compliance-Kontrollen | Blacklist-, Einfrier-, Whitelist- und KYC-Kontrollen müssen wirksam sein | KYC-Gating und Anbieter-Widerruf sind im bereitgestellten Code funktionsuntüchtig | Abweichend |
| 2.2.3 | Eingefrorene oder vernichtete Coins bleiben vollständig gedeckt und abstimmbar | die Vernichtung gibt Transfer an address(this) statt an address(0) aus und lässt die Nettoausgabezähler unberührt; die aus Events rekonstruierte Gesamtmenge weicht ab |
Bedenklich |
Paragraph 6.5.3: Risikoreiche Vorgänge dürfen nicht unilateral sein
Was verlangt wird. Risikoreiche Vorgänge sollten so gestaltet sein, dass keine einzelne Partei sie unilateral durchführen kann, etwa durch ein Mehrfachsignatur-Protokoll, und die Richtlinie nennt weitere Abhilfemaßnahmen wie Geschwindigkeitslimits und zeitverzögerte (Timelock-)Kontrollen.
Was der Contract tut. Aus der authorizationMatrix des Governance-Contracts ausgelesen (die vollständige Matrix findet sich in der Tabelle in 1.2), erfordern die Versorgungs- und Notfallvorgänge jeweils eine einzelne Rolle bei einem Quorum von eins:
| Vorgang | Erforderliche Signatur |
|---|---|
mintToDeposit |
ROLLE_C x1 |
burnFrom |
ROLLE_C x1 |
freeze |
ROLLE_C x1 |
pause |
ROLLE_D x1 |
destroyBlackFunds |
ROLLE_D x1 |
Wo dies abweicht. Prägen, Verbrennen, Pausieren und Einfrieren können jeweils von einem einzigen Schlüssel ausgeführt werden. Dies erfüllt nicht die Anforderung „keine einzelne Partei unilateral“. Auch gibt es kein Timelock: wie in 1.2 gezeigt, wird die Aktion, sobald das Quorum erreicht ist, in derselben Transaktion ausgeführt, sodass auch eine zeitliche Verzögerung, eine der von der Richtlinie genannten Abhilfemaßnahmen, fehlt. Der Contract implementiert tatsächlich ein Geschwindigkeitslimit für die Versorgung (whenWithinRiskThresholds), sodass diese Abhilfemaßnahme vorhanden ist, aber sie ersetzt keine Mehrfachsignatur-Kontrolle bei den Vorgängen selbst.
Paragraph 6.5.4: Aufgabentrennung und sofortiger Widerruf
Was verlangt wird. Unterschiedliche Vorgänge sollten auf unterschiedliche autorisierte Personen aufgeteilt werden, und die Befugnis einer autorisierten Person sollte sofort widerrufbar sein.
Was der Contract tut. Ein extern gehaltenes Konto hält sechs Rollen (Ausgabe, Einfrieren, KYC-Verwaltung und alle vier Auditor-Rollen), sodass sich Ausführungs- und Prüfrollen überschneiden. Auch Rollenänderungen selbst erfordern nur Rolle A plus Rolle B (siehe 1.2), sodass dieselbe kleine Gruppe entscheidet, wer autorisiert ist und ausführen und aktualisieren kann. Und eine bereits gezählte Signatur wird nicht erneut validiert, wenn die Rolle des Unterzeichners später widerrufen wird.
Wo dies abweicht. Aufgaben sind konzentriert statt getrennt, und der Widerruf erfolgt nicht sofort: Die frühere Genehmigung eines widerrufenen Unterzeichners zählt weiterhin zu einer späteren Ausführung.
Paragraph 6.5.5: Prüfung jeder Codeänderung; korrekt, konsistent, keine Schwachstellen
Was verlangt wird. Ein qualifizierter Dritter sollte die Smart Contracts bei jeder Codeänderung prüfen und bestätigen, dass sie (i) korrekt implementiert sind, (ii) mit der beabsichtigten Funktionalität konsistent sind und (iii) mit hoher Zuverlässigkeit frei von Schwachstellen sind.
Was der Contract tut. Die aktuelle Implementierung ist diejenige, die durch das Upgrade vom 10. Juli installiert wurde (nachverfolgt in 1.5), und die in Teil 1 beschriebenen Mängel sind darin aktiv.
Wo dies abweicht. Wir können Off-Chain nicht feststellen, ob eine Prüfung durchgeführt wurde, aber das Ergebnis erfüllt den Standard so oder so nicht. Da isActive und der Anbieter-Widerruf nicht das tun, was ihr Name verspricht, sind die Bedingungen (i) und (ii) nicht erfüllt; und da die Mängel aus Teil 1 im bereitgestellten Code vorhanden sind, ist auch (iii) nicht erfüllt.
Wirksamkeit der Compliance-Kontrollen
Was verlangt wird. Das Lebenszyklusmodell der Richtlinie (Blacklist, Einfrieren, Whitelist, KYC) setzt voraus, dass diese Kontrollen wirksam sind.
Was der Contract tut. Wie in 1.1 gezeigt, sind KYC-Gating und Anbieter-Widerruf im bereitgestellten Code nicht funktionsfähig.
Wo dies abweicht. Eine Kontrolle, die funktionieren muss, funktioniert nicht. Dies ist eine inhaltliche Lücke, keine Formalie.
Paragraph 2.2.3: Eingefrorene oder vernichtete Coins bleiben vollständig gedeckt und abstimmbar
Was verlangt wird. Stablecoins, die durch eine Vollzugsmaßnahme eingefroren oder vernichtet werden, sollten vollständig gedeckt bleiben, sodass Bestand und Reserven abgestimmt werden können.
Was der Contract tut. Der übliche Abhilfeablauf für einen regulierten Stablecoin besteht darin, die Mittel an einer bösartigen Adresse zu verbrennen und später einen gleich hohen Betrag als separate Prägung an das Opfer neu auszugeben (so funktioniert destroyBlackFunds plus issue bei USDT). Die Vernichtungsfunktion von HKDAP verringert _totalSupply, was einer Verbrennung entspricht, kreditiert aber nie balances[address(this)], gibt einen Transfer an address(this) statt an address(0) aus und lässt die Nettoausgabezähler, die das Prägungslimit nutzt, unberührt:
// 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 }
Wo dies abweicht. Die Zustandsänderung ist eine Verbrennung, aber das Ereignis besagt, dass die Token an den Contract übergegangen sind, und dort wird nie etwas gehalten (balances[address(this)] bleibt bei null). Ein Indexer würde address(this) Token gutschreiben, die er nicht besitzt, und die aus Events rekonstruierte Gesamtmenge würde nicht mit der Chain übereinstimmen. Es verwirrt auch die Abhilfemaßnahme selbst: Da die Token verbrannt statt geparkt werden, muss eine Neuausgabe an das Opfer eine frische Prägung sein, doch der irreführende Transfer(..., address(this), ...) legt nahe, der Contract verwahre sie jetzt und könne sie weiterleiten, was er nicht kann. Eine saubere Verbrennung an address(0) plus eine separate Neuausgabe wären sowohl korrekt als auch abstimmbar. So wie es geschrieben ist, weicht die On-Chain-Buchführung, auf die eine Reservenabstimmung angewiesen ist, vom tatsächlichen Zustand der Chain ab. Dies ist eher ein Bedenken als ein klarer Verstoß.
Wir beschränken diese Befunde auf das, was die Chain zeigt. Ob Reserven vollständig gedeckt sind, ob Schlüssel in einem HSM oder einer abgeschotteten Umgebung liegen und ob Transaktionen vor der Signatur Off-Chain simuliert werden, ist aus dem Contract nicht ersichtlich, und wir stellen dazu keine Behauptung auf.
Fazit
Das Bild ist über beide Achsen der Überprüfung hinweg konsistent. Als Software enthält HKDAP funktionale Mängel, einschließlich Compliance-Kontrollen, die nicht wie beschrieben ausgeführt werden, und zeigt mehrere Anzeichen für einen Pre-Production-Build. Als regulierter Stablecoin stehen mehrere seiner On-Chain-Eigenschaften im Widerspruch zu bestimmten Klauseln der HKMA-Richtlinie. Auf Basis der On-Chain-Beweise, und unter Ausklammerung der Off-Chain-Sachverhalte, die wir nicht einsehen können, erfüllt der bereitgestellte Contract noch nicht den Standard, den ein kommerzieller Stablecoin erfüllen sollte.
Daraus ergeben sich zwei Beobachtungen, die es klar auszusprechen gilt.
Erstens verändert die Ausgabe auf einer öffentlichen Chain, wo Compliance entschieden wird. Eine Anforderung wie „keine einzelne Partei sollte unilateral handeln können“ wird durch die Rollenprüfungen im bereitgestellten Code erfüllt oder nicht erfüllt, und dieser Code ist öffentlich. Die Implementierung, nicht die Lizenz oder die Dokumentation, ist der Ort, an dem eine solche Anforderung tatsächlich erfüllt oder verfehlt wird, und jeder kann überprüfen, was zutrifft.
Zweitens ändert die Kennzeichnung „Beta Access“ nichts am Risikoprofil des bereitgestellten Codes. Der Contract läuft live auf dem Ethereum-Mainnet, wird von echten Schlüsseln verwaltet und stellt einen Anspruch auf Hongkong-Dollar dar. Er sollte daher unabhängig von seiner Kennzeichnung an Produktionsstandards gemessen werden.
Ein gemeinsamer roter Faden zieht sich unter den spezifischen Befunden hindurch. Die Architektur erfindet in maßgeschneiderter Form Primitiven neu, die das Ökosystem bereits bereitstellt und in großem Maßstab geprüft hat: eine von Grund auf neu geschriebene Genehmigungs-Engine und Rollenschicht, wo ein Safe-Multisig mit OpenZeppelins AccessManager und TimelockController genügen würde; eine handgeschriebene verkettete Liste anstelle von EnumerableSet; ein modifizierter Proxy anstelle des Standard-TransparentUpgradeableProxy; und ein handgeschriebenes ERC-20 anstelle desjenigen von OpenZeppelin. Die meisten Mängel in diesem Bericht finden sich in dieser maßgeschneiderten Mechanik, nicht in den Teilen, die Standardkomponenten wiederverwenden. Es liest sich, als sei das System so abstrahiert worden, wie es bei allgemeiner Software üblich ist, statt aus den kleinen, geprüften Bausteinen zusammengesetzt zu werden, die die On-Chain-Entwicklung bevorzugt, wo jede Schicht maßgeschneiderter Abstraktion zugleich Gas, Angriffsfläche und Upgrade-Risiko bedeutet. Ein Design, das aus diesen Standardkomponenten aufgebaut ist, wäre kleiner, sicherer und leichter zu prüfen, und es würde mit den Dingen einhergehen, die diesem System derzeit fehlen: ein Bedenkzeitfenster durch ein Timelock und benannte, nachvollziehbare Rollen.
Die hier beschriebenen Probleme sind behebbar. Die Wiederherstellung der Mehrfachsignatur bei risikoreichen Vorgängen, die Trennung von Ausführung und Prüfung, die Behebung der KYC-Widerrufslogik, die Vereinheitlichung der Übertragungsprüfungen, das Entfernen des Debug-Codes und die Forderung nach einer Prüfung durch Dritte vor jedem Upgrade würden die meisten davon lösen. Die Bereitstellung im Mainnet mit verifiziertem Quellcode ist auch das, was diese Überprüfung erst ermöglicht hat, und das ist der richtige Standardansatz. Kontinuierliche On-Chain-Sicherheits- und Compliance-Überprüfung dieser Art ist das, was wir bei BlockSec tun, und wir helfen gerne weiter.



