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.