Arithmetic-Fehler in Solidity Contracts vermeiden: So klappt's

Verfasst von

Arithmetische Fehler in Solidity-Smart Contracts treten auf, wenn Token-, Anteils- oder Preiskalkulationen die falsche Formel, Dezimalstellenbasis, Rundungsrichtung oder numerische Annahme verwenden. Dies ermöglicht es Nutzern, übermäßige Ansprüche zu prägen, mehr Wert abzuheben, als sie eingezahlt haben, oder buchhalterische Invarianten zu verletzen. Die Abhilfe besteht darin, Einheiten explizit zu definieren, eine vollpräzise begrenzte Arithmetik zu verwenden und die wirtschaftlichen Invarianten zu testen, die die Formel wahren muss.

Die Folgen sind erheblich. Cetus CLMM verlor am 22.05.2025 auf Sui 223,0 Mio. USD bei einem Vorfall, der als Arithmetischer Fehler (Arithmetic Error) im Zusammenhang mit Token- und Anteilsbuchhaltung klassifiziert wurde. Arithmetische Sicherheit beschränkt sich daher nicht nur darauf, Solidity-Overflow-Reverts zu verhindern. Sie stellt sicher, dass jeder geprägte Anteil einen berechtigten Anspruch auf Vermögenswerte darstellt.

Was sind arithmetische Fehler in Solidity und wie funktionieren Angriffe?

Arithmetische Fehler in Solidity sind Fehler bei Berechnungen, die Einzahlungen, Abhebungen, Salden, Preise, Gebühren oder Liquidität in wirtschaftliche Ansprüche übersetzen. Ein Angreifer nutzt die Diskrepanz zwischen dem beabsichtigten Buchhaltungsmodell und der implementierten Formel aus, indem er oft die Reihenfolge von Transaktionen, Vermögensmengen oder Poolzustände so wählt, dass eine falsche Umrechnung zu seinen Gunsten ausfällt.

In modernem Solidity führen arithmetische Overflow- und Underflow-Fehler standardmäßig zum Revert. Diese Schutzmaßnahme ist nützlich, beweist aber nicht, dass eine Formel wirtschaftlich korrekt ist. Eine Berechnung kann innerhalb der Grenzen von uint256 bleiben und trotzdem zu viele Tresor-Anteile prägen, zu wenig Sicherheiten verlangen oder eine Abhebung zulasten des Nutzers aufrunden.

Ein typischer Angriff mit übermäßigen Anteilen folgt diesem Ablauf:

  1. Ein Tresor hat bestehende Vermögenswerte und Anteile, sodass jeder Anteil einen Nicht-1-Preis hat.
  2. Der Tresor erhält Erträge, Gebühren, Spenden oder einen Rebase-bedingten Saldoanstieg.
  3. Der Anteilspreis steigt, weil die Gesamtvermögen zugenommen haben, die Gesamtanteile jedoch nicht.
  4. Ein Angreifer zahlt Vermögenswerte durch eine Formel ein, die annimmt, dass ein Anteil immer genau einem Vermögenswert entspricht.
  5. Der Tresor prägt mehr Anteile, als die Einzahlung bei aktuellem Preis eigentlich erlaubt.
  6. Der Angreifer löst diese Anteile gegen einen unverhältnismäßigen Anteil des Poolvermögens ein.

Die Kerninvariante ist einfach: Nach einer Einzahlung sollte der Eigentumsanteil eines neuen Nutzers dem von ihm beigetragenen Wert entsprechen. Erzeugt eine Einzahlung im Wert von 1 % der Poolvermögen mehr als etwa 1 % der ausstehenden Anteile, so werden bestehende Inhaber verwässert.

Welche Solidity-Patterns verursachen Token- und Anteilsbuchhaltungsfehler?

Token- und Anteilsbuchhaltungsfehler entstehen üblicherweise durch das Vermischen von Einheiten, das Abschneiden von Zwischenwerten oder die Behandlung eines sich ändernden Anteilpreises als feststehend. Die risikoreichsten Funktionen sind deposit, mint, withdraw, redeem, Kreditlimits, Gebührenabrechnung, Liquiditätsberechnungen und Belohnungsverteilung.

Ein gefährliches Muster ist das direkte Prägen von Anteilen aus hinterlegten Vermögenseinheiten:

shares = assets * 1e18 / assetScale;

Diese Umrechnung ist möglicherweise nur für die erste Einzahlung geeignet, wenn ein Anteil absichtlich als eine normalisierte Vermögenseinheit definiert wird. Sobald ein Tresor Erträge erzielt oder Verluste erleidet, müssen Einzahlungen zum aktuellen Wechselkurs erfolgen:

shares = assets * totalShares / totalAssets;

Auch dieser Ausdruck benötigt sorgfältige Behandlung. Erstes Multiplizieren kann zu Overflow führen, wenn es innerhalb von unchecked steht, während erste Division Präzision kosten kann. Die Dezimalnormalisierung ist eine weitere Fehlerquelle. Vermögenswerte im USDC-Stil verwenden häufig weniger Dezimalstellen als ein 18-Dezimal-Anteilstoken, weshalb ein Protokoll rohe Token-Einheiten konsistent von normalisierten Buchhaltungseinheiten unterscheiden muss.

Häufige Ursachen sind:

  • Festes Kodieren von 1e18 für Vermögenswerte mit anderer Dezimalpräzision.
  • Anteile zum fixen Eins-zu-eins-Kurs prägen, obwohl sich der Anteilspreis geändert hat.
  • Dividieren vor Multiplizieren, mit stillschweigendem Verlust bedeutender Präzision.
  • Aufrunden von Einzahlungen oder Abhebungen, was kumulativen Werteverlust erzeugt.
  • Einsatz von unchecked um benutzerkontrollierte Werte oder Skalierungsfaktoren.
  • Die Annahme, Token-Salden können sich nicht außerhalb normaler Einzahlungs- und Abhebungsvorgänge ändern.
  • Vergessen, dass Gebühren-bei-Transfer, Rebase- und ertragsbringende Tokens die zwischengespeicherte Buchhaltung invalidieren können.

Wie behebt man arithmetische Fehler bei Solidity-Anteilsberechnungen?

Um arithmetische Fehler zu beheben, normalisiert man Vermögenseinheiten einmal, berechnet Anteile anhand des aktuellen Poolverhältnisses, verwendet vollpräzise Multiplikation und Division und wählt eine Rundung, die keinen Wert von bestehenden Inhabern auf neue Einzahler überträgt. Der Vertrag muss außerdem sein Vermögensguthaben und die Anteilsmenge als verknüpften Buchhaltungszustand behandeln.

Der verwundbare Tresor unten akzeptiert einen Token mit konfigurierbaren Dezimalstellen. Er prägt normalisierte Anteile zu einem fixen Kurs, selbst nachdem reportYield die Vermögenswerte erhöht hat. Ein Einzahler kann nach Ertragsansammlung zu viele Anteile erhalten und diese dann gegen den größeren Pool einlösen.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
}

contract VulnerableShareVault {
    IERC20 public immutable asset;
    uint8 public immutable assetDecimals;
    uint256 public totalAssets;
    uint256 public totalShares;
    mapping(address => uint256) public shares;

    constructor(address asset_, uint8 decimals_) {
        asset = IERC20(asset_);
        assetDecimals = decimals_;
    }

    function deposit(uint256 assets) external {
        require(asset.transferFrom(msg.sender, address(this), assets), "transfer failed");

        uint256 scale = 10 ** assetDecimals;
        // VULNERABLE: ignoriert den aktuellen Preis bestehender Anteile.
        uint256 minted = (assets * 1e18) / scale;

        totalAssets += assets;
        totalShares += minted;
        shares[msg.sender] += minted;
    }

    function reportYield(uint256 assets) external {
        require(asset.transferFrom(msg.sender, address(this), assets), "transfer failed");
        totalAssets += assets;
    }

    function withdraw(uint256 shareAmount) external {
        uint256 assets = (shareAmount * totalAssets) / totalShares;
        shares[msg.sender] -= shareAmount;
        totalShares -= shareAmount;
        totalAssets -= assets;
        require(asset.transfer(msg.sender, assets), "transfer failed");
    }
}

Die korrigierte Version wandelt rohe Vermögenseinheiten in 18-Dezimal-Buchhaltungseinheiten um und prägt Anteile nach dem aktuellen Wechselkurs. Sie verwendet OpenZeppelins vollpräzises Math.mulDiv, rundet Ein- und Auszahlungen ab und begrenzt unterstützte Dezimalwerte.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";

interface IERC20 {
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function transfer(address to, uint256 amount) external returns (bool);
}

contract FixedShareVault {
    IERC20 public immutable asset;
    uint256 public immutable assetScale;
    uint256 public totalAssets;
    uint256 public totalShares;
    mapping(address => uint256) public shares;

    constructor(address asset_, uint8 decimals_) {
        require(decimals_ <= 18, "unsupported decimals");
        asset = IERC20(asset_);
        assetScale = 10 ** decimals_;
    }

    function deposit(uint256 assets) external {
        require(assets > 0, "zero assets");
        require(asset.transferFrom(msg.sender, address(this), assets), "transfer failed");

        uint256 normalizedAssets = Math.mulDiv(assets, 1e18, assetScale);
        uint256 normalizedPool = Math.mulDiv(totalAssets, 1e18, assetScale);

        // FIX: Preise Anteilsprägung nach dem Pool-Verhältnis vor der Einzahlung.
        uint256 minted = totalShares == 0
            ? normalizedAssets
            : Math.mulDiv(normalizedAssets, totalShares, normalizedPool);

        require(minted > 0, "deposit too small");
        totalAssets += assets;
        totalShares += minted;
        shares[msg.sender] += minted;
    }

    function withdraw(uint256 shareAmount) external {
        uint256 assets = Math.mulDiv(shareAmount, totalAssets, totalShares);
        shares[msg.sender] -= shareAmount;
        totalShares -= shareAmount;
        totalAssets -= assets;
        require(asset.transfer(msg.sender, assets), "transfer failed");
    }
}

Wenn Ihr Protokoll Einzahlungen, Abhebungen, Sicherheiten oder Liquidität mit benutzerdefinierter Arithmetik bewertet, kann ein Smart-Contract-Audit und eine Sicherheitsbewertung die Einheitenskalierungen und wirtschaftlichen Invarianten über den gesamten Transaktionsverlauf hinweg nachvollziehen.

Wie können Entwickler arithmetische Fehler vermeiden?

Entwickler vermeiden arithmetische Fehler, indem sie Buchhaltungseinheiten vor der Implementierung von Formeln spezifizieren, konservative Rundung anwenden und jede Umwandlung als Sicherheitsgrenze behandeln. Eine Formel sollte angeben, was jeder Eingangswert repräsentiert, seine Dezimalpräzision, seinen zulässigen Bereich und wer vom Rundungsergebnis profitiert.

Technik Was verhindert es Gas- oder Komplexitätskosten Einschränkung
Explizite Einheitennormalisierung Vermischen roher Token-Einheiten mit 18-Dezimal-Anteils-Einheiten Zusätzliche Multiplikation oder Division Benötigt eine dokumentierte Dezimalpolitik
Math.mulDiv Overflow und Präzisionsverlust in a * b / c Mehr Gas als einfache Operatoren Validiert nicht die wirtschaftliche Formel
Abrunden von Ein- und Auszahlungen Übermäßiges Prägen und Über-Einlösung aufgrund von Rundung Kann Staubsalden erzeugen Staub benötigt eine Rückgewinnungsstrategie
Mindestschwelle für Anteilsprägung Einzahlungen, die auf null Anteile abrunden Ein Vergleich Darf legitime kleine Nutzer nicht blockieren
Begrenzte Eingaben und Dezimalstellen Unsichere Exponentiation und extreme Benutzereingaben Validierungsprüfungen Grenzen müssen unterstützte Vermögenswerte widerspiegeln
Invariant-Tests Zustandsübergänge, die Poolbuchhaltung verletzen Aufwand für das Schreiben von Tests Invarianten müssen gegnerische Sequenzen abdecken

Nutzen Sie Abrundung, wo die Alternative dazu führen würde, dass Wert ausgegeben wird, den der Pool nicht tragen kann. Ein Tresor rundet üblicherweise eingelegte Anteile nach unten und eingelöste Vermögenswerte nach unten. Wenn ein Produkt eine nutzerfreundliche Rundung benötigt, sollte es die dadurch entstehende Subvention explizit finanzieren, statt sie stillschweigend anderen Einzahlern aufzubürden.

Sicherheits-Insight: Nach der Soken-Methodik beginnt die arithmetische Prüfung mit Einheiten und Erhaltungsgesetzen, nicht mit Operatoren. Eine geprüfte Multiplikation kann trotzdem angreifbar sein, wenn das Ergebnis einen falschen Token, Anteil, Preis oder Eigentumsanspruch repräsentiert.

Welche realen Vorfälle zeigen die Auswirkung von arithmetischen Fehlern?

Arithmetische Fehler demonstrieren, dass Token- und Anteilsbuchhaltung eine protokollweite Sicherheitseigenschaft ist und kein rein lokales Codequalitätsproblem. Die betroffene Logik kann in AMM-Reserven, Kreditbelohnungen, Tresoranteilen, Liquiditätsberechnungen oder Asset-Umrechnungsroutinen liegen.

Protokoll Datum Verlustbetrag Chain
Cetus CLMM 2025-05-22 $223.0M Sui
Compound V2 2021-09-29 $147.0M Ethereum
Uranium Finance 2021-04-28 $57.2M BSC
ValueDefi 2021-05-07 $11.0M BSC
KiiChain 2026-08-22 $9.7M KiiChain
Truebit 2026-01-08 $26.4M Ethereum
Notional V2 2026-09-04 $1.7M Ethereum
USM 2026-08-09 $136K Ethereum

Diese Vorfälle haben eine kritische Gemeinsamkeit: Eine Berechnung steuerte die Verteilung oder Bewertung von On-Chain-Werten. Der Fehler war nicht zwingend eine ungeprüfte uint256-Addition. In Token- und Anteilsbuchhaltung ist häufig das fehlerhafte Verhältnis zwischen Reserven, Angebot, Preis und Nutzeransprüchen die Schwachstelle.

DefiLlama berichtet von 35 Vorfällen mit arithmetischen Fehlern in Smart Contracts von 2020 bis 2026 mit Gesamtschäden von 561,0 Mio. USD. Für Protokollteams empfiehlt sich ein gleichrangiges Überprüfen von Buchhaltungspfaden wie von Zugriffskontrolle und externen Aufrufbehandlungen.

Wie erkennt man arithmetische Fehler vor der Bereitstellung?

Arithmetische Fehler erkennt man am besten durch das Überprüfen von Zustandsübergängen und das Testen wirtschaftlicher Eigenschaften über viele Sequenzen — nicht durch manuelle Einzelformelprüfung. Statische Analyse identifiziert riskante Operatoren und unchecked-Abschnitte, Fuzzing und Invariantentests prüft, ob das Buchhaltungsmodell bei Einzahlungen, Abhebungen, Erträgen, Verlusten und ungewöhnlichen Dezimalwerten hält.

Verwenden Sie diese fokussierte Prüfliste:

  1. Listen Sie jede Vermögens-, Anteil-, Preis-, Satz- und Gebührenvariable mit Einheit und Dezimalstellen auf.
  2. Verifizieren Sie, dass jede Einzahlung zum Kurs vor der Einzahlung erfolgt.
  3. Verifizieren Sie, dass jede Abhebung mit einem mit den verbrannten Anteilen konsistenten Kurs gerechnet wird.
  4. Suchen Sie nach rohen *, /, unchecked und hartkodierten Skalierungs-Konstanten in Buchhaltungspfaden.
  5. Testen Sie Vermögenswerte mit unterschiedlichen Dezimalpräzisionen und Einzahlungen nahe Mindestgrößen.
  6. Bestätigen Sie, dass Rundung weder unzulässige Anteile prägt noch unzulässige Werte ausgibt.
  7. Modellieren Sie Bilanzänderungen durch Rebase, Spenden, Gebühren und Erträge vor jeder Nutzeraktion.
  8. Prüfen Sie, dass die Gesamtansprüche der Nutzer die verfügbare Einlösesumme nicht übersteigen.

Slither kann gefährliche arithmetische Muster, ungeprüfte Blöcke und verdächtige Präzisionsoperationen anzeigen, ist aber nicht in der Lage, die ökonomische Richtigkeit einer Anteilspreisformel zu bewerten. Foundry-Fuzzing ist wirksamer in Kombination mit Assertions wie totalAssets, die alle eingelösten Ansprüche abdecken, und der Annahme, dass ein Einzahler keinen Gewinn aus einem sofortigen Einzahlungs-Abhebungs-Zyklus ohne legitimen Ertrag ziehen kann.

Invariantentests sollten Multi-Nutzer-Sequenzen erzeugen: Einzahlung, Ertragsmeldung, Abhebung, Teilabhebung, Spende und wiederholte kleine Einzahlungen. Sokens Prüfpraxis vergleicht dabei auch das beabsichtigte mathematische Modell mit jedem Zustandsupdate, weil eine korrekte Formel vor einem Zustandswechsel angewendet dennoch falsche Preise ergeben kann.

Arithmetische Sicherheit ist die Disziplin, wirtschaftliche Wahrheit bei jeder Umwandlung zu bewahren. Ein Tresor kann Solidity 0.8 geprüfte Arithmetik verwenden und trotzdem angreifbar sein, wenn er Ansprüche zum falschen Preis prägt, falsche Dezimalstellen annimmt oder Werte nach außen rundet.

Beginnen Sie damit, für jede Buchhaltungsvariable Einheit und Invariante zu formulieren und codieren Sie diese Invarianten in Foundry-Tests, bevor Sie neue Vermögenswerte oder Preisbildungspfade hinzufügen. Für tiefere Forschung zu Solidity-Sicherheitsmustern besuchen Sie den Soken Hub.

Verfasst von

Häufig gestellte Fragen

Was sind Arithmetic-Fehler in Solidity Smart Contracts?

Arithmetic-Fehler in Solidity sind Fehler in Berechnungen bei Einzahlungen, Abhebungen, Balances, Preisen, Gebühren, Liquidität, Tokens oder Anteilen. Falsche Formeln, Dezimalskalen, Rundungen oder Annahmen können zu überhöhten Ansprüchen oder Kontenfehlern führen.

Wie können Arithmetic-Fehler zu zu hohen Abhebungen führen?

Fehler in Berechnungen der Token-, Anteils- oder Preiskalkulation können zu übermäßigen Gutschriften oder Abhebungen führen. Falsche Formeln, Skalierungen oder Rundungsrichtungen erlauben Nutzern, mehr Wert abzuheben, als sie eingezahlt haben.

Wie kann ich Arithmetic-Fehler in Solidity verhindern?

Arithmetic-Fehler vermeidet man, indem sämtliche Einheiten explizit definiert, präzise und begrenzte Mathematik genutzt und wirtschaftliche Invarianten getestet werden. So werden Fehler in Formeln, Dezimalskalen, Rundungen und Annahmen ausgeschlossen.

Reichen Overflow-Reverts aus, um Arithmetic-Fehler in Solidity zu stoppen?

Overflow-Reverts allein eliminieren keine Arithmetic-Fehler. Es bedarf auch korrekter Formeln, expliziter Einheiten, passender Dezimalskalen, richtiger Rundungen und plausibler Annahmen, da sonst ungültige Ansprüche trotz Overflow-Schutz entstehen können.

Was lehrt der Cetus CLMM-Verlust über Arithmetic-Fehler?

Der $223 Mio Verlust von Cetus CLMM auf Sui zeigt, dass Arithmetic-Fehler mehr als Overflow-Vermeidung umfassen. Jede ausgegebene Aktie muss einen legitimen Anspruch auf Assets darstellen, sonst drohen hohe finanzielle Schäden.

Chat