Weiter zum Hauptinhalt

Einige Tricks, die von Betrugs-Tokens verwendet werden, und wie man sie erkennt

scam
solidity
erc-20
javascript
typescript
Fortgeschritten
Ori Pomerantz
15. September 2023
15 Minuten Lesedauer

In diesem Tutorial analysieren wir einen Betrugs-Tokenopens in a new tab, um einige der Tricks zu sehen, die Betrüger anwenden und wie sie sie implementieren. Am Ende des Tutorials werden Sie einen umfassenderen Überblick über ERC-20-Token-Verträge, ihre Fähigkeiten und warum Skepsis notwendig ist, haben. Dann schauen wir uns die von diesem Betrugs-Token ausgegebenen Ereignisse an und sehen, wie wir automatisch erkennen können, dass er nicht legitim ist.

Betrugs-Tokens – was sind sie, warum machen Leute sie und wie kann man sie vermeiden

Eine der häufigsten Anwendungen von Ethereum ist die Schaffung eines handelbaren Tokens durch eine Gruppe, der gewissermaßen ihre eigene Währung darstellt. Jedoch gibt es überall, wo es legitime wertschöpfende Anwendungsmöglichkeiten gibt, auch Kriminelle, die diese Werte stehlen möchten.

Sie können mehr über dieses Thema an anderer Stelle auf ethereum.org aus der Perspektive eines Benutzers lesen. Dieses Tutorial konzentriert sich auf die Analyse eines Betrugs-Tokens, um zu sehen, wie es gemacht wird und wie es erkannt werden kann.

Woher weiß ich, dass wARB ein Betrug ist?

Der Token, den wir analysieren, ist wARBopens in a new tab, der vorgibt, dem legitimen ARB-Tokenopens in a new tab gleichwertig zu sein.

Der einfachste Weg, um zu wissen, welcher der legitime Token ist, ist ein Blick auf die Herkunftsorganisation, Arbitrumopens in a new tab. Die legitimen Adressen sind in ihrer Dokumentationopens in a new tab angegeben.

Warum ist der Quellcode verfügbar?

Normalerweise würden wir erwarten, dass Leute, die versuchen, andere zu betrügen, geheimnisvoll sind, und tatsächlich haben viele Betrugs-Tokens ihren Code nicht verfügbar (zum Beispiel dieseropens in a new tab und dieseropens in a new tab).

Allerdings veröffentlichen legitime Tokens normalerweise ihren Quellcode, sodass die Autoren von Betrugs-Tokens manchmal dasselbe tun, um legitim zu erscheinen. wARBopens in a new tab ist einer dieser Tokens mit verfügbarem Quellcode, was das Verständnis erleichtert.

Während Vertrags-Deployer wählen können, ob sie den Quellcode veröffentlichen oder nicht, können sie nicht den falschen Quellcode veröffentlichen. Der Block-Explorer kompiliert den bereitgestellten Quellcode unabhängig, und wenn er nicht genau denselben Bytecode erhält, lehnt er diesen Quellcode ab. Sie können mehr darüber auf der Etherscan-Seite lesenopens in a new tab.

Vergleich mit legitimen ERC-20-Tokens

Wir werden diesen Token mit legitimen ERC-20-Tokens vergleichen. Wenn Sie nicht damit vertraut sind, wie legitime ERC-20-Tokens typischerweise geschrieben werden, sehen Sie sich dieses Tutorial an.

Konstanten für privilegierte Adressen

Verträge benötigen manchmal privilegierte Adressen. Verträge, die für den langfristigen Gebrauch konzipiert sind, erlauben es einigen privilegierten Adressen, diese Adressen zu ändern, zum Beispiel um die Verwendung eines neuen Multisig-Vertrags zu ermöglichen. Es gibt mehrere Möglichkeiten, dies zu tun.

Der HOP-Token-Vertragopens in a new tab verwendet das Ownableopens in a new tab-Muster. Die privilegierte Adresse wird im Speicher in einem Feld namens _owner gehalten (siehe die dritte Datei, Ownable.sol).

1abstract contract Ownable is Context {
2 address private _owner;
3 .
4 .
5 .
6}

Der ARB-Token-Vertragopens in a new tab hat nicht direkt eine privilegierte Adresse. Er braucht jedoch auch keine. Er befindet sich hinter einem Proxyopens in a new tab an der Adresse 0xb50721bcf8d664c30412cfbc6cf7a15145234ad1opens in a new tab. Dieser Vertrag hat eine privilegierte Adresse (siehe die vierte Datei, ERC1967Upgrade.sol), die für Upgrades verwendet werden kann.

1 /**
2 * @dev Speichert eine neue Adresse im EIP1967-Admin-Slot.
3 */
4 function _setAdmin(address newAdmin) private {
5 require(newAdmin != address(0), "ERC1967: new admin is the zero address");
6 StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
7 }

Im Gegensatz dazu hat der wARB-Vertrag einen fest codierten contract_owner.

1contract WrappedArbitrum is Context, IERC20 {
2 .
3 .
4 .
5 address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1;
6 address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33;
7 .
8 .
9 .
10}
Alles anzeigen

Dieser Vertragsinhaberopens in a new tab ist kein Vertrag, der zu verschiedenen Zeiten von verschiedenen Konten kontrolliert werden könnte, sondern ein externes Konto. Das bedeutet, dass es wahrscheinlich für den kurzfristigen Gebrauch durch eine Einzelperson konzipiert ist, anstatt als langfristige Lösung zur Kontrolle eines ERC-20, das wertvoll bleiben wird.

Und tatsächlich, wenn wir in Etherscan nachsehen, sehen wir, dass der Betrüger diesen Vertrag nur für 12 Stunden (von der ersten Transaktionopens in a new tab bis zur letzten Transaktionopens in a new tab) am 19. Mai 2023 verwendet hat.

Die gefälschte _transfer-Funktion

Es ist Standard, dass tatsächliche Transfers über eine interne _transfer-Funktion stattfinden.

In wARB sieht diese Funktion fast legitim aus:

1 function _transfer(address sender, address recipient, uint256 amount) internal virtual{
2 require(sender != address(0), "ERC20: transfer from the zero address");
3 require(recipient != address(0), "ERC20: transfer to the zero address");
4
5 _beforeTokenTransfer(sender, recipient, amount);
6
7 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
8 _balances[recipient] = _balances[recipient].add(amount);
9 if (sender == contract_owner){
10 sender = deployer;
11 }
12 emit Transfer(sender, recipient, amount);
13 }
Alles anzeigen

Der verdächtige Teil ist:

1 if (sender == contract_owner){
2 sender = deployer;
3 }
4 emit Transfer(sender, recipient, amount);

Wenn der Vertragsinhaber Tokens sendet, warum zeigt das Transfer-Ereignis, dass sie von deployer kommen?

Es gibt jedoch ein wichtigeres Problem. Wer ruft diese _transfer-Funktion auf? Sie kann nicht von außen aufgerufen werden, sie ist als internal markiert. Und der Code, den wir haben, enthält keine Aufrufe an _transfer. Offensichtlich ist sie hier als Köder.

1 function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
2 _f_(_msgSender(), recipient, amount);
3 return true;
4 }
5
6 function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
7 _f_(sender, recipient, amount);
8 _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
9 return true;
10 }
Alles anzeigen

Wenn wir uns die Funktionen ansehen, die zum Übertragen von Tokens aufgerufen werden, transfer und transferFrom, sehen wir, dass sie eine völlig andere Funktion aufrufen, _f_.

Die echte _f_-Funktion

1 function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual {
2 require(sender != address(0), "ERC20: transfer from the zero address");
3 require(recipient != address(0), "ERC20: transfer to the zero address");
4
5 _beforeTokenTransfer(sender, recipient, amount);
6
7 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
8 _balances[recipient] = _balances[recipient].add(amount);
9 if (sender == contract_owner){
10
11 sender = deployer;
12 }
13 emit Transfer(sender, recipient, amount);
14 }
Alles anzeigen

Es gibt zwei potenzielle Warnsignale in dieser Funktion.

  • Die Verwendung des Funktionsmodifikatorsopens in a new tab _mod_. Wenn wir uns jedoch den Quellcode ansehen, sehen wir, dass _mod_ tatsächlich harmlos ist.

    1modifier _mod_(address sender, address recipient, uint256 amount){
    2 _;
    3}
  • Das gleiche Problem, das wir bei _transfer gesehen haben: Wenn contract_owner Tokens sendet, scheinen sie von deployer zu kommen.

Die gefälschte Ereignisfunktion dropNewTokens

Jetzt kommen wir zu etwas, das wie ein tatsächlicher Betrug aussieht. Ich habe die Funktion zur besseren Lesbarkeit etwas bearbeitet, aber sie ist funktional gleichwertig.

1function dropNewTokens(address uPool,
2 address[] memory eReceiver,
3 uint256[] memory eAmounts) public auth()

Diese Funktion hat den auth()-Modifikator, was bedeutet, dass sie nur vom Vertragsinhaber aufgerufen werden kann.

1modifier auth() {
2 require(msg.sender == contract_owner, "Not allowed to interact");
3 _;
4}

Diese Einschränkung ist vollkommen sinnvoll, da wir nicht wollen würden, dass zufällige Konten Tokens verteilen. Der Rest der Funktion ist jedoch verdächtig.

1{
2 for (uint256 i = 0; i < eReceiver.length; i++) {
3 emit Transfer(uPool, eReceiver[i], eAmounts[i]);
4 }
5}

Eine Funktion zum Übertragen von einem Pool-Konto an ein Array von Empfängern mit einem Array von Beträgen ist vollkommen sinnvoll. Es gibt viele Anwendungsfälle, in denen Sie Tokens von einer einzigen Quelle an mehrere Ziele verteilen möchten, wie z. B. Gehaltsabrechnungen, Airdrops usw. Es ist günstiger (in Gas), dies in einer einzigen Transaktion zu tun, anstatt mehrere Transaktionen auszugeben oder sogar den ERC-20-Vertrag mehrmals von einem anderen Vertrag als Teil derselben Transaktion aufzurufen.

dropNewTokens tut das jedoch nicht. Es gibt Transfer-Ereignisseopens in a new tab aus, aber es werden keine Tokens tatsächlich übertragen. Es gibt keinen legitimen Grund, Off-Chain-Anwendungen zu verwirren, indem man ihnen von einer Übertragung berichtet, die nicht wirklich stattgefunden hat.

Die „brennende“ Approve-Funktion

ERC-20-Verträge sollen eine approve-Funktion für Freigaben haben, und tatsächlich hat unser Betrugs-Token eine solche Funktion, und sie ist sogar korrekt. Da Solidity jedoch von C abstammt, ist die Groß- und Kleinschreibung von Bedeutung. Approve und approve sind unterschiedliche Zeichenketten.

Außerdem steht die Funktionalität nicht im Zusammenhang mit approve.

1 function Approve(
2 address[] memory holders)

Diese Funktion wird mit einem Array von Adressen für Inhaber des Tokens aufgerufen.

1 public approver() {

Der approver()-Modifikator stellt sicher, dass nur contract_owner diese Funktion aufrufen darf (siehe unten).

1 for (uint256 i = 0; i < holders.length; i++) {
2 uint256 amount = _balances[holders[i]];
3 _beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount);
4 _balances[holders[i]] = _balances[holders[i]].sub(amount,
5 "ERC20: burn amount exceeds balance");
6 _balances[0x0000000000000000000000000000000000000001] =
7 _balances[0x0000000000000000000000000000000000000001].add(amount);
8 }
9 }
10
Alles anzeigen

Für jede Inhaberadresse verschiebt die Funktion das gesamte Guthaben des Inhabers an die Adresse 0x00...01 und „verbrennt“ es damit effektiv (der eigentliche burn-Vorgang im Standard ändert auch die Gesamtversorgung und überträgt die Tokens an 0x00...00). Das bedeutet, dass contract_owner die Vermögenswerte jedes Benutzers entfernen kann. Das scheint keine Funktion zu sein, die man in einem Governance-Token haben möchte.

Probleme mit der Codequalität

Diese Probleme mit der Codequalität beweisen nicht, dass dieser Code ein Betrug ist, aber sie lassen ihn verdächtig erscheinen. Organisierte Unternehmen wie Arbitrum veröffentlichen normalerweise keinen so schlechten Code.

Die mount-Funktion

Obwohl es nicht im Standardopens in a new tab spezifiziert ist, wird die Funktion, die neue Tokens erstellt, allgemein mint genannt.

Wenn wir uns den wARB-Konstruktor ansehen, sehen wir, dass die Mint-Funktion aus irgendeinem Grund in mount umbenannt wurde und fünfmal mit einem Fünftel der anfänglichen Versorgung aufgerufen wird, anstatt einmal für den gesamten Betrag aus Effizienzgründen.

1 constructor () public {
2
3 _name = "Wrapped Arbitrum";
4 _symbol = "wARB";
5 _decimals = 18;
6 uint256 initialSupply = 1000000000000;
7
8 mount(deployer, initialSupply*(10**18)/5);
9 mount(deployer, initialSupply*(10**18)/5);
10 mount(deployer, initialSupply*(10**18)/5);
11 mount(deployer, initialSupply*(10**18)/5);
12 mount(deployer, initialSupply*(10**18)/5);
13 }
Alles anzeigen

Die mount-Funktion selbst ist ebenfalls verdächtig.

1 function mount(address account, uint256 amount) public {
2 require(msg.sender == contract_owner, "ERC20: mint to the zero address");

Wenn wir uns das require ansehen, sehen wir, dass nur der Vertragsinhaber prägen darf. Das ist legitim. Aber die Fehlermeldung sollte only owner is allowed to mint oder so etwas Ähnliches sein. Stattdessen lautet sie irrelevant ERC20: mint to the zero address. Der korrekte Test für das Prägen an die Nulladresse ist require(account != address(0), "<Fehlermeldung>"), was der Vertrag nie überprüft.

1 _totalSupply = _totalSupply.add(amount);
2 _balances[contract_owner] = _balances[contract_owner].add(amount);
3 emit Transfer(address(0), account, amount);
4 }

Es gibt zwei weitere verdächtige Fakten, die direkt mit dem Prägen zusammenhängen:

  • Es gibt einen account-Parameter, der vermutlich das Konto ist, das den geprägten Betrag erhalten sollte. Aber das Guthaben, das sich erhöht, ist tatsächlich das von contract_owner.

  • Während das erhöhte Guthaben zu contract_owner gehört, zeigt das ausgegebene Ereignis eine Übertragung an account.

Warum sowohl auth als auch approver? Warum das mod, das nichts tut?

Dieser Vertrag enthält drei Modifikatoren: _mod_, auth und approver.

1 modifier _mod_(address sender, address recipient, uint256 amount){
2 _;
3 }

_mod_ nimmt drei Parameter und macht nichts damit. Warum gibt es ihn?

1 modifier auth() {
2 require(msg.sender == contract_owner, "Not allowed to interact");
3 _;
4 }
5
6 modifier approver() {
7 require(msg.sender == contract_owner, "Not allowed to interact");
8 _;
9 }
Alles anzeigen

auth und approver machen mehr Sinn, weil sie prüfen, ob der Vertrag von contract_owner aufgerufen wurde. Wir würden erwarten, dass bestimmte privilegierte Aktionen, wie das Prägen, auf dieses Konto beschränkt sind. Was ist jedoch der Sinn, zwei separate Funktionen zu haben, die genau dasselbe tun?

Was können wir automatisch erkennen?

Wir können sehen, dass wARB ein Betrugs-Token ist, indem wir uns Etherscan ansehen. Das ist jedoch eine zentralisierte Lösung. Theoretisch könnte Etherscan unterwandert oder gehackt werden. Es ist besser, unabhängig herausfinden zu können, ob ein Token legitim ist oder nicht.

Es gibt einige Tricks, mit denen wir einen verdächtigen ERC-20-Token identifizieren können (entweder ein Betrug oder sehr schlecht geschrieben), indem wir uns die von ihnen ausgegebenen Ereignisse ansehen.

Verdächtige Approval-Ereignisse

Approval-Ereignisseopens in a new tab sollten nur auf eine direkte Anfrage hin geschehen (im Gegensatz zu Transfer-Ereignissenopens in a new tab, die als Ergebnis einer Freigabe stattfinden können). Siehe die Solidity-Dokumentation für eine detaillierte Erklärung dieses Problems und warum die Anfragen direkt sein müssen, anstatt durch einen Vertrag vermittelt zu werden.

Das bedeutet, dass Approval-Ereignisse, die Ausgaben von einem externen Konto genehmigen, von Transaktionen stammen müssen, die in diesem Konto ihren Ursprung haben und deren Ziel der ERC-20-Vertrag ist. Jede andere Art der Genehmigung von einem externen Konto ist verdächtig.

Hier ist ein Programm, das diese Art von Ereignis identifiziertopens in a new tab, das viemopens in a new tab und TypeScriptopens in a new tab, eine JavaScript-Variante mit Typsicherheit, verwendet. So führen Sie es aus:

  1. Kopieren Sie .env.example nach .env.
  2. Bearbeiten Sie .env, um die URL zu einem Ethereum-Mainnet-Node bereitzustellen.
  3. Führen Sie pnpm install aus, um die notwendigen Pakete zu installieren.
  4. Führen Sie pnpm susApproval aus, um nach verdächtigen Genehmigungen zu suchen.

Hier ist eine zeilenweise Erklärung:

1import {
2 Address,
3 TransactionReceipt,
4 createPublicClient,
5 http,
6 parseAbiItem,
7} from "viem"
8import { mainnet } from "viem/chains"

Importieren Sie Typdefinitionen, Funktionen und die Chain-Definition aus viem.

1import { config } from "dotenv"
2config()

Lesen Sie .env, um die URL zu erhalten.

1const client = createPublicClient({
2 chain: mainnet,
3 transport: http(process.env.URL),
4})

Erstellen Sie einen Viem-Client. Wir müssen nur aus der Blockchain lesen, daher benötigt dieser Client keinen privaten Schlüssel.

1const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82"
2const fromBlock = 16859812n
3const toBlock = 16873372n

Die Adresse des verdächtigen ERC-20-Vertrags und die Blöcke, in denen wir nach Ereignissen suchen werden. Node-Anbieter beschränken typischerweise unsere Fähigkeit, Ereignisse zu lesen, da die Bandbreite teuer werden kann. Glücklicherweise wurde wARB für einen Zeitraum von achtzehn Stunden nicht verwendet, sodass wir nach allen Ereignissen suchen können (es waren insgesamt nur 13).

1const approvalEvents = await client.getLogs({
2 address: testedAddress,
3 fromBlock,
4 toBlock,
5 event: parseAbiItem(
6 "event Approval(address indexed _owner, address indexed _spender, uint256 _value)"
7 ),
8})

So fragen Sie Viem nach Ereignisinformationen. Wenn wir ihm die genaue Ereignissignatur, einschließlich Feldnamen, zur Verfügung stellen, analysiert es das Ereignis für uns.

1const isContract = async (addr: Address): boolean =>
2 await client.getBytecode({ address: addr })

Unser Algorithmus ist nur auf externe Konten anwendbar. Wenn von client.getBytecode ein Bytecode zurückgegeben wird, bedeutet dies, dass es sich um einen Vertrag handelt und wir ihn einfach überspringen sollten.

Wenn Sie TypeScript noch nicht verwendet haben, könnte die Funktionsdefinition etwas seltsam aussehen. Wir sagen ihm nicht nur, dass der erste (und einzige) Parameter addr heißt, sondern auch, dass er vom Typ Address ist. Ähnlich teilt der : boolean-Teil TypeScript mit, dass der Rückgabewert der Funktion ein boolescher Wert ist.

1const getEventTxn = async (ev: Event): TransactionReceipt =>
2 await client.getTransactionReceipt({ hash: ev.transactionHash })

Diese Funktion ruft den Transaktionsbeleg aus einem Ereignis ab. Wir benötigen den Beleg, um sicherzustellen, dass wir das Transaktionsziel kennen.

1const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => {

Dies ist die wichtigste Funktion, die tatsächlich entscheidet, ob ein Ereignis verdächtig ist oder nicht. Der Rückgabetyp (Event | null) teilt TypeScript mit, dass diese Funktion entweder ein Event oder null zurückgeben kann. Wir geben null zurück, wenn das Ereignis nicht verdächtig ist.

1const owner = ev.args._owner

Viem kennt die Feldnamen, also hat es das Ereignis für uns analysiert. _owner ist der Eigentümer der auszugebenden Tokens.

1// Genehmigungen durch Verträge sind nicht verdächtig
2if (await isContract(owner)) return null

Wenn der Eigentümer ein Vertrag ist, gehen Sie davon aus, dass diese Genehmigung nicht verdächtig ist. Um zu überprüfen, ob die Genehmigung eines Vertrags verdächtig ist oder nicht, müssen wir die vollständige Ausführung der Transaktion verfolgen, um zu sehen, ob sie jemals zum Eigentümervertrag gelangt ist und ob dieser Vertrag den ERC-20-Vertrag direkt aufgerufen hat. Das ist viel ressourcenintensiver, als wir es gerne hätten.

1const txn = await getEventTxn(ev)

Wenn die Genehmigung von einem externen Konto kommt, holen Sie sich die Transaktion, die sie verursacht hat.

1// Die Genehmigung ist verdächtig, wenn sie von einem EOA-Besitzer stammt, der nicht das `from` der Transaktion ist
2if (owner.toLowerCase() != txn.from.toLowerCase()) return ev

Wir können nicht einfach auf Zeichenketten-Gleichheit prüfen, da Adressen hexadezimal sind und daher Buchstaben enthalten. Manchmal, zum Beispiel in txn.from, sind diese Buchstaben alle in Kleinbuchstaben. In anderen Fällen, wie bei ev.args._owner, ist die Adresse in gemischter Groß- und Kleinschreibung zur Fehlererkennungopens in a new tab.

Aber wenn die Transaktion nicht vom Eigentümer stammt und dieser Eigentümer ein externes Konto ist, dann haben wir eine verdächtige Transaktion.

1// Es ist auch verdächtig, wenn das Transaktionsziel nicht der ERC-20-Vertrag ist, den wir
2// untersuchen
3if (txn.to.toLowerCase() != testedAddress) return ev

Ähnlich verhält es sich, wenn die to-Adresse der Transaktion, also der erste aufgerufene Vertrag, nicht der zu untersuchende ERC-20-Vertrag ist, dann ist dies verdächtig.

1 // Wenn es keinen Grund gibt, misstrauisch zu sein, gib null zurück.
2 return null
3}

Wenn keine der beiden Bedingungen zutrifft, ist das Approval-Ereignis nicht verdächtig.

1const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev))
2const testResults = (await Promise.all(testPromises)).filter((x) => x != null)
3
4console.log(testResults)

Eine async-Funktionopens in a new tab gibt ein Promise-Objekt zurück. Mit der gängigen Syntax await x() warten wir darauf, dass dieses Promise erfüllt wird, bevor wir mit der Verarbeitung fortfahren. Dies ist einfach zu programmieren und zu verfolgen, aber es ist auch ineffizient. Während wir darauf warten, dass das Promise für ein bestimmtes Ereignis erfüllt wird, können wir bereits mit dem nächsten Ereignis beginnen.

Hier verwenden wir mapopens in a new tab, um ein Array von Promise-Objekten zu erstellen. Dann verwenden wir Promise.allopens in a new tab, um darauf zu warten, dass alle diese Promises aufgelöst werden. Wir filtern dann diese Ergebnisse mit filteropens in a new tab, um die nicht verdächtigen Ereignisse zu entfernen.

Verdächtige Transfer-Ereignisse

Eine weitere Möglichkeit, Betrugs-Tokens zu identifizieren, besteht darin, zu prüfen, ob sie verdächtige Übertragungen aufweisen. Zum Beispiel Übertragungen von Konten, die nicht so viele Tokens haben. Sie können sehen, wie dieser Test implementiert wirdopens in a new tab, aber wARB hat dieses Problem nicht.

Fazit

Die automatisierte Erkennung von ERC-20-Betrugsfällen leidet unter falsch-negativen Ergebnissenopens in a new tab, da ein Betrug einen vollkommen normalen ERC-20-Token-Vertrag verwenden kann, der einfach nichts Reales darstellt. Sie sollten also immer versuchen, die Token-Adresse aus einer vertrauenswürdigen Quelle zu beziehen.

Die automatisierte Erkennung kann in bestimmten Fällen helfen, wie z.B. bei DeFi-Komponenten, wo es viele Tokens gibt und diese automatisch gehandhabt werden müssen. Aber wie immer gilt caveat emptoropens in a new tab, führen Sie Ihre eigenen Recherchen durch und ermutigen Sie Ihre Benutzer, es Ihnen gleichzutun.

Hier finden Sie mehr von meiner Arbeitopens in a new tab.

Seite zuletzt aktualisiert: 25. Februar 2026

War dieses Tutorial hilfreich?