Zum Hauptinhalt springen
BlockchainMar 28, 2026

Deep EVM #19: Eigenschaftsbasiertes Testen fuer Smart Contracts — Fuzzing mit Foundry

OS
Open Soft Team

Engineering Team

Ueber Unit-Tests hinaus

Unit-Tests verifizieren spezifische Szenarien, aber Smart Contracts sind adversarialen Eingaben ausgesetzt — von jeder Adresse, mit jedem Wert, in jeder Reihenfolge. Eigenschaftsbasiertes Testen dreht das Paradigma um: Statt erwartete Ausgaben fuer spezifische Eingaben festzulegen, definieren Sie Eigenschaften, die fuer ALLE Eingaben gelten muessen.

Fuzz-Testing mit Foundry

Foundry generiert automatisch zufaellige Eingaben fuer Testfunktionen:

// Foundry erkennt Fuzz-Parameter automatisch
function testFuzz_transfer(
    address to,
    uint256 amount
) public {
    vm.assume(to != address(0));
    vm.assume(amount <= totalSupply);
    
    deal(token, address(this), amount);
    
    uint256 balBefore = token.balanceOf(address(this));
    token.transfer(to, amount);
    uint256 balAfter = token.balanceOf(address(this));
    
    // Eigenschaft: Balance muss um genau amount sinken
    assertEq(balBefore - balAfter, amount);
}

Foundry fuehrt diesen Test standardmaessig 256 Mal mit unterschiedlichen Zufallswerten aus.

Invarianten-Tests

Invarianten sind Bedingungen, die IMMER gelten muessen, unabhaengig davon, welche Funktionen in welcher Reihenfolge aufgerufen werden:

contract InvariantTest is Test {
    TokenHandler handler;
    
    function setUp() public {
        handler = new TokenHandler(token);
        targetContract(address(handler));
    }
    
    // Diese Funktion wird nach jeder zufaelligen Aufrufsequenz geprueft
    function invariant_totalSupplyConstant() public {
        assertEq(token.totalSupply(), INITIAL_SUPPLY);
    }
    
    function invariant_balanceSumEqualsTotal() public {
        uint256 sum = 0;
        for (uint i = 0; i < actors.length; i++) {
            sum += token.balanceOf(actors[i]);
        }
        assertEq(sum, token.totalSupply());
    }
}

Differentielles Fuzzing: Huff vs. Solidity

function testFuzz_differential_balanceOf(
    address account,
    uint256 initialBalance
) public {
    vm.assume(initialBalance <= type(uint128).max);
    
    // Gleiche Anfangsbedingungen setzen
    deal(huffToken, account, initialBalance);
    deal(solidityToken, account, initialBalance);
    
    // Beide abfragen
    (bool s1, bytes memory r1) = huffToken.staticcall(
        abi.encodeWithSignature("balanceOf(address)", account)
    );
    (bool s2, bytes memory r2) = solidityToken.staticcall(
        abi.encodeWithSignature("balanceOf(address)", account)
    );
    
    // Ergebnisse muessen identisch sein
    assertEq(s1, s2);
    assertEq(r1, r2);
}

Best Practices

  1. vm.assume() sparsam verwenden — Zu viele Annahmen reduzieren die Testabdeckung
  2. Eigene Handler schreiben — Handler steuern, welche Funktionen der Fuzzer aufruft
  3. Ghost-Variablen — Eigene Buchhaltung neben dem Contract fuehren
  4. Genug Durchlaeufe konfigurieren — foundry.toml: [fuzz] runs = 10000
  5. Fehlschlaege reproduzieren — Foundry speichert Seeds fuer reproduzierbare Fehler

Fazit

Eigenschaftsbasiertes Testen und Fuzzing finden Fehler, die Unit-Tests uebersehen. Fuer Huff-Contracts, die kein Sicherheitsnetz haben, ist differentielles Fuzzing gegen eine Solidity-Referenz die staerkste Waffe in Ihrem Testarsenal.