Zum Inhalt

Beispielarbeiten · Code & Fehler

Smart Contracts: Token und Treuhandvertrag mit Reentrancy-Prüfung

Ein Token und ein Treuhandvertrag in Solidity, dazu ein verwundbarer Vertrag und seine Korrektur. Übersetzt ohne Warnung, die Reentrancy-Lücke wird gemeldet und in der Korrektur nicht mehr.

Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.

Was gebaut wurde

  • Token und Treuhandvertrag mit Auszahlung erst nach Zustandsänderung und Sperre gegen Wiedereintritt
  • Übersetzt mit dem Solidity-Compiler ohne Warnung, Vertragsgröße unter dem Limit
  • Verwundbarer Vertrag wird als Reentrancy gemeldet, die Korrektur nicht
  • Gegenprobe mit einem Überlauf-Fehler wird ebenfalls gemeldet

smart-contracts.zip herunterladen

Nur übersetzt und statisch geprüft, nicht auf einer EVM ausgeführt und auf kein Netz geladen. Der formale Prüfer (SMTChecker) lief nicht; die Reentrancy-Prüfung ist textbasiert. Das ist kein Sicherheitsaudit mit Haftung: vor echtem Geld braucht ein Vertrag ein unabhängiges Audit. Keine Verträge für Betrug, Pump-and-Dump oder Rug-Pull.

Wie geprüft wurde

Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):

  • solc 0.8.38: 6 Dateien/6 Vertraege ohne Warnung, max 2155 B (<24576)
  • SMTChecker NICHT verfuegbar (kein z3), ersetzt durch Quelltextpruefung
  • Reentrancy: verwundbar gemeldet (1), 2 Fixe frei
  • Ueberlauf-Gegenprobe gemeldet

Grenzen

Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:

  • nur kompiliert und statisch geprueft, NICHT auf einer EVM ausgefuehrt und auf kein Netz (auch kein Testnetz) deployt
  • SMTChecker (z3) ist hier nicht verfuegbar: Ueberlauf/Invarianten werden durch die eigene Quelltextpruefung gesichert, nicht formal bewiesen
  • eigene Reentrancy-Pruefung ist textbasiert (verfolgt keine Zeiger auf Speicher, keine Aufrufe ueber mehrere Funktionen)
  • kein professionelles Sicherheitsaudit mit Haftung; vor echtem Geld braucht ein Vertrag ein unabhaengiges Audit
  • keine Vertraege zu Betrug, Pump-and-Dump, Rug-Pull oder Umgehung von Regeln
  • keine Gasgebuehren und keine Wallet auf unsere Kosten; Bereitstellung nur mit Freigabe des Kunden/Owners

Treuhandvertrag

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

/// Demo-Treuhandvertrag des ERFUNDENEN Betriebs Kornblume: Kaeufer zahlt ein, gibt frei (Zahlung an Verkaeufer)
/// oder holt nach Ablauf der Frist sein Geld zurueck. Zustand wird VOR der Auszahlung geaendert
/// (Checks-Effects-Interactions) und zusaetzlich durch eine Sperre gegen Wiedereintritt gesichert.
contract KornblumeEscrow {
    enum Status { Leer, Eingezahlt, Freigegeben, Erstattet }

    struct Geschaeft {
        address kaeufer;
        address verkaeufer;
        uint256 betrag;
        uint256 frist;
        Status status;
    }

    mapping(uint256 => Geschaeft) public geschaefte;
    uint256 public naechsteId;
    uint256 public gesamtEingezahlt;
    uint256 public gesamtAusgezahlt;
    uint256 private _sperre = 1;

    event Eingezahlt(uint256 indexed id, address indexed kaeufer, address indexed verkaeufer, uint256 betrag, uint256 frist);
    event Freigegeben(uint256 indexed id, address indexed verkaeufer, uint256 betrag);
    event Erstattet(uint256 indexed id, address indexed kaeufer, uint256 betrag);

    error FalscherStatus();
    error NurKaeufer();
    error FristNichtAbgelaufen();
    error KeinBetrag();
    error FalscheAdresse();
    error AuszahlungFehlgeschlagen();
    error Wiedereintritt();

    modifier keinWiedereintritt() {
        if (_sperre != 1) revert Wiedereintritt();
        _sperre = 2;
        _;
        _sperre = 1;
    }

    function einzahlen(address verkaeufer, uint256 dauerSekunden) external payable returns (uint256 id) {
        if (msg.value == 0) revert KeinBetrag();
        if (verkaeufer == address(0) || verkaeufer == msg.sender) revert FalscheAdresse();
        id = naechsteId;
        naechsteId = id + 1;
        geschaefte[id] = Geschaeft(msg.sender, verkaeufer, msg.value, block.timestamp + dauerSekunden, Status.Eingezahlt);
        gesamtEingezahlt += msg.value;
        // Invariante: ausgezahlt wird nie mehr als eingezahlt wurde.
        assert(gesamtAusgezahlt <= gesamtEingezahlt);
        emit Eingezahlt(id, msg.sender, verkaeufer, msg.value, block.timestamp + dauerSekunden);
    }

    function freigeben(uint256 id) external keinWiedereintritt {
        Geschaeft storage g = geschaefte[id];
        if (g.status != Status.Eingezahlt) revert FalscherStatus();
        if (msg.sender != g.kaeufer) revert NurKaeufer();
        // Effekte zuerst ...
        g.status = Status.Freigegeben;
        uint256 betrag = g.betrag;
        address an = g.verkaeufer;
        gesamtAusgezahlt += betrag;
        assert(gesamtAusgezahlt <= gesamtEingezahlt);
        // ... Interaktion zuletzt.
        (bool ok, ) = payable(an).call{value: betrag}("");
        if (!ok) revert AuszahlungFehlgeschlagen();
        emit Freigegeben(id, an, betrag);
    }

    function erstatten(uint256 id) external keinWiedereintritt {
        Geschaeft storage g = geschaefte[id];
        if (g.status != Status.Eingezahlt) revert FalscherStatus();
        if (msg.sender != g.kaeufer) revert NurKaeufer();
        if (block.timestamp < g.frist) revert FristNichtAbgelaufen();
        g.status = Status.Erstattet;
        uint256 betrag = g.betrag;
        address an = g.kaeufer;
        gesamtAusgezahlt += betrag;
        assert(gesamtAusgezahlt <= gesamtEingezahlt);
        (bool ok, ) = payable(an).call{value: betrag}("");
        if (!ok) revert AuszahlungFehlgeschlagen();
        emit Erstattet(id, an, betrag);
    }
}

So etwas für deinen Betrieb?

Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.

Zur Werkstatt