I 2025 stjal angripere rundt 2,87 milliarder dollar fra kryptoprosjekter fordelt på nesten 150 separate hack, ifølge TRM Labs’ Crypto Crime Report for 2026. Bare i første halvår av 2026 passerte tapene 1,32 milliarder dollar. Mønsteret har endret seg: angriperne går ikke lenger bare etter åpenbare reentrancy-bugs, de leter etter feilkonfigurerte hvelv, glemte tilgangskontroller og logikkfeil som ingen testet før kontrakten traff hovedkjeden. Denne artikkelen viser deg, steg for steg, hvordan du bygger en automatisert sikkerhetspipeline for Ethereum-kontrakter med tre verktøy: Slither for statisk analyse, Foundry for enhets- og invariant-testing, og Echidna for property-basert fuzzing. Du ender opp med et komplett testprosjekt du kan kjøre lokalt og i CI før hver eneste deploy.

Hvorfor smart-kontrakt-sikkerhet er kritisk i 2026

Den 3. august 2026 mistet RISEx-protokollen 673 011,56 USDC fra et RWA-hvelv (real-world-asset) kalt XLP. Feilen hadde ligget der siden hvelvet ble satt i produksjon 13. juli, ifølge hendelsesloggen hos SlowMist. Ingen kompleks exploit-kjede var nødvendig. En feilkonfigurert strategiparameter ga en uautorisert part rett til å trekke ut midler, en type feil som en invariant-test («total verdi i hvelvet kan aldri synke uten et gyldig uttak») ville fanget opp i løpet av minutter.

Seks dager senere, 9. august 2026, tapte børsen Coinsbuy over 8 millioner dollar i et koordinert angrep som gikk på tvers av TRON og Ethereum. Vi har dekket Coinsbuy-hacket i detalj tidligere, men poenget her er enklere: selv sentraliserte aktører rammes av svakheter i integrasjonslaget mellom kjeder, ikke bare i selve smart-kontraktkoden. Kombinasjonen av disse to hendelsene, bare seks dager fra hverandre, viser hvorfor sikkerhetstesting ikke lenger er noe du gjør én gang før en revisor ser på koden. Det må inn i den daglige utviklingsrutinen.

Løsningen mange team har landet på i 2026 er en tre-lags teststabel: statisk analyse på hver commit, invariant-tester på hver pull request, og fuzzing før hver deploy til mainnet. Det er nettopp denne stabelen du bygger i resten av denne guiden. Vi bruker en enkel utlånskontrakt (en «vault») som eksempel, planter en kjent sårbarhet i den, og lar verktøyene finne den før vi fikser koden.

Guiden er skrevet for deg som allerede skriver Solidity, enten alene eller i et lite team, og som vil ha et konkret, kjørbart oppsett i stedet for en generell sjekkliste. Du trenger ikke tidligere erfaring med sikkerhetsverktøy. Vi går gjennom hver kommando, forklarer hva output betyr, og viser deg både den sårbare og den fikse versjonen av koden underveis, slik at du kan sammenligne direkte.

Vanlige sårbarhetsklasser du bygger denne pipelinen for å fange

Før du installerer noe, er det verdt å vite hva du faktisk leter etter. OWASPs Smart Contract Top 10 gir en god oversikt over de vanligste feilklassene, og de fleste alvorlige hendelser i 2026 faller innenfor disse kategoriene.

  • Reentrancy. En ekstern kontrakt kaller tilbake inn i din funksjon før den er ferdig, og utnytter at intern tilstand ikke er oppdatert ennå. Fortsatt den vanligste årsaken til at Slither flagger High-severity-funn.
  • Feil tilgangskontroll. Funksjoner som skal være begrenset til en eier eller en rolle, men som mangler modifier eller sjekker feil adresse. Enkel å fikse, men lett å overse i store kodebaser.
  • Oracle-manipulasjon. Kontrakten stoler på en prisfeed som kan flyttes midlertidig av en enkelt stor transaksjon, ofte i kombinasjon med et flash loan. Verken Slither eller Echidna fanger dette alene uten at du modellerer prisfeeden i testen.
  • Heltallsfeil og presisjonstap. Avrunding i feil rekkefølge i en renteberegning kan gi brukere mer eller mindre enn de skal ha, uten at noe «krasjer». Dette er nøyaktig den typen feil invariant-tester er gode til å fange.
  • Front-running og MEV. Transaksjoner i mempoolen kan ses og kappes forbi av andre. Relevant for alt som involverer priser, auksjoner eller likvidasjon.
  • Feil ved oppgraderbare kontrakter. Proxy-mønstre som glemmer å initialisere state riktig, eller som lar feil adresse utføre en oppgradering, har stått bak flere av de største tapene de siste to årene.

Legg merke til at ingen av disse seks kategoriene krever avansert kryptografi eller eksotiske angrep. De fleste handler om vanlig programvarelogikk som ingen testet grundig nok. Det er nøyaktig det gapet denne pipelinen tetter.

Forutsetninger: dette trenger du før du starter

Sett av 60-90 minutter til hele oppsettet første gang. Du trenger en Linux-, macOS- eller WSL2-maskin (Echidna og Slither støtter ikke native Windows). Sjekk versjonene under før du går videre.

KomponentMinimumsversjonInstallasjon
Python3.8 eller nyerepython3 –version
pipNyeste versjonpython3 -m pip install –upgrade pip
Git2.30 eller nyeregit –version
Foundry (forge, cast, anvil)Nyeste via foundryupcurl -L https://foundry.paradigm.xyz | bash
SlitherNyeste via pippip install slither-analyzer
Solidity-kompilator (solc)0.8.20 eller nyereInstalleres automatisk av Foundry
EchidnaNyeste releaseDocker-image eller forhåndsbygd binærfil fra GitHub
Docker (valgfritt, for Echidna)24.0 eller nyeredocker –version

Du trenger ingen betalt lisens for noen av verktøyene. Slither og Echidna er utviklet av Trail of Bits og er begge åpen kildekode, mens Foundry vedlikeholdes av Paradigm. Har du fra før et Hardhat-prosjekt, fungerer Slither og Echidna fint mot det også, men denne guiden bruker Foundry som base fordi invariant-testene skrives direkte i Solidity.

Steg 1-2: Installer Foundry og sett opp prosjektet

Foundry er byggverktøyet vi bruker til å kompilere, teste og etter hvert deploye kontrakten. Installasjonen skjer via et lite skript som henter binærfilene forge, cast, anvil og chisel.

curl -L https://foundry.paradigm.xyz | bash
foundryup

# Verifiser installasjonen
forge --version
cast --version

# Opprett prosjektet
mkdir sikker-vault && cd sikker-vault
forge init --no-commit
forge install foundry-rs/forge-std --no-commit

forge init lager standard mappestruktur: src for kontraktene, test for testene, og script for deploy-skript. Åpne foundry.toml og legg til en profil for sikkerhetsverktøyene, slik at Slither og Echidna finner riktig kompilatorversjon uten å måtte gjette.

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.24"
optimizer = true
optimizer_runs = 200

[profile.default.fuzz]
runs = 1000

Kjør forge build for å bekrefte at prosjektet kompilerer før du fortsetter. Alt bygger på et tomt prosjekt her, så feil på dette steget betyr som regel en feil solc-versjon eller manglende avhengigheter. Pinning av solc_version er ikke bare kosmetikk: Slither og Echidna kompilerer prosjektet uavhengig av hverandre, og hvis de tre verktøyene lander på ulik kompilatorversjon kan de gi motstridende resultater på nøyaktig samme kodelinje. Sett versjonen én gang i foundry.toml, og la alle verktøyene lese den samme filen.

Steg 3-4: Installer Slither og kjør din første skanning

Slither er skrevet i Python av Trail of Bits og konverterer Solidity til en mellomrepresentasjon kalt SlithIR før den kjører et sett med detektorer mot koden. Ifølge prosjektets egen dokumentasjon på GitHub dekker detektorene alt fra reentrancy og heltallsoverflyt til feil bruk av tx.origin og uinitialiserte lagringspekere. Skanningen tar typisk under ett sekund per kontrakt, noe som gjør den billig nok til å kjøre på hver eneste commit.

# Installer Slither i et eget virtuelt miljø
python3 -m venv .venv
source .venv/bin/activate
pip install slither-analyzer

# Kjør mot et Foundry-prosjekt
slither . --foundry-out-directory out

Slither leser foundry.toml automatisk og kompilerer prosjektet på nytt før analysen starter. Første kjøring mot et helt tomt prosjekt bør gi null eller svært få funn. Legg gjerne til –json rapport.json hvis du vil ha maskinlesbar output du kan sende videre til CI eller til et team-dashbord senere i guiden.

Steg 5-6: Skriv en sårbar kontrakt og la Slither fange feilene

For å se verktøyene i aksjon trenger vi noe å teste mot. Under er en enkel utlånskontrakt med en klassisk reentrancy-sårbarhet: den sender ETH ut til brukeren før den oppdaterer saldoen internt. Lim koden inn i src/Vault.sol.

// src/Vault.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Vault {
    mapping(address => uint256) public balances;

    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "Ikke nok saldo");

        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Overforing feilet");

        balances[msg.sender] -= amount;
    }

    function totalBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Kjør slither . på nytt. Verktøyet skal flagge withdraw-funksjonen med en «Reentrancy» High-severity-advarsel, fordi det eksterne kallet (.call) skjer før balances[msg.sender] oppdateres. En angriper kan sette opp en fallback-funksjon som kaller withdraw på nytt før den første oppdateringen rekker å skje, og på den måten tømme hele hvelvet.

Slithers output for dette funnet ser omtrent slik ut i terminalen, med filnavn, linjenummer og en kort forklaring av hvorfor mønsteret er farlig:

Reentrancy in Vault.withdraw(uint256) (src/Vault.sol#11-17):
    External calls:
    - (success,None) = msg.sender.call{value: amount}() (src/Vault.sol#13)
    State variables written after the call(s):
    - balances[msg.sender] -= amount (src/Vault.sol#15)
    Reentrancy vulnerabilities (severity: High) (confidence: Medium)

Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#reentrancy-vulnerabilities

Legg merke til at Slither oppgir en konfidens sammen med alvorlighetsgraden. High/Medium betyr at verktøyet er ganske sikkert på funnet, mens lavere konfidens-nivåer bør leses som et hint om å undersøke nærmere, ikke som en automatisk sannhet. Bruk –print human-summary for en kortere oversikt hvis du bare vil ha antall funn per alvorlighetsgrad før du dykker inn i detaljene.

Fiksen er standardmønsteret checks-effects-interactions: valider tilstand, oppdater tilstand, og gjør det eksterne kallet sist.

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Ikke nok saldo");

    balances[msg.sender] -= amount;

    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Overforing feilet");
}

Kjør slither . igjen. Reentrancy-varselet skal nå være borte. Dette er kjernen i «Slither på hver commit»-arbeidsflyten: skann, les funnene, fiks, skann på nytt, og bekreft at antall High- og Medium-funn går ned, ikke opp, for hver commit.

Reentrancy er langt fra en løst sak selv i 2026, til tross for at mønsteret har vært kjent siden DAO-hacket i 2016. Grunnen er at kontrakter i dag kaller mye mer eksternt enn før: ERC-777-token har innebygde callback-hooks, NFT-standarder som ERC-721 trigger onERC721Received ved mottak, og kryssende protokoll-integrasjoner betyr at koden din kan bli kalt tilbake fra steder du aldri planla for. Sjekk derfor ikke bare den åpenbare eksterne overføringen, men også ethvert kall til en adresse du ikke fullt ut kontrollerer.

Steg 7-8: Bygg enhets- og fuzz-tester i Foundry

Statisk analyse fanger kjente mønstre, men den forstår ikke forretningslogikken din. Her kommer Foundrys innebygde testrammeverk inn, skrevet direkte i Solidity slik at testene kan bruke samme typer og samme kompilator som produksjonskoden. Opprett test/Vault.t.sol.

// test/Vault.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/Vault.sol";

contract VaultTest is Test {
    Vault vault;
    address alice = address(0xA11CE);

    function setUp() public {
        vault = new Vault();
        vm.deal(alice, 10 ether);
    }

    function testDepositOgSaldo() public {
        vm.prank(alice);
        vault.deposit{value: 1 ether}();
        assertEq(vault.balances(alice), 1 ether);
    }

    function testWithdrawReduserSaldo() public {
        vm.startPrank(alice);
        vault.deposit{value: 2 ether}();
        vault.withdraw(1 ether);
        vm.stopPrank();
        assertEq(vault.balances(alice), 1 ether);
    }

    // Fuzz-test: uansett hvilket belop Foundry velger,
    // skal saldoen aldri kunne bli negativ eller overstige innskuddet
    function testFuzzWithdrawAldriOverInnskudd(uint96 innskudd, uint96 uttak) public {
        vm.assume(innskudd > 0 && innskudd <= 5 ether);
        vm.assume(uttak <= innskudd);

        vm.deal(alice, innskudd);
        vm.startPrank(alice);
        vault.deposit{value: innskudd}();
        vault.withdraw(uttak);
        vm.stopPrank();

        assertEq(vault.balances(alice), innskudd - uttak);
    }
}

Kjør forge test -vv. Fuzz-testen kjører 1000 tilfeldige input-kombinasjoner (satt i foundry.toml lenger opp) og feiler umiddelbart hvis noen av dem bryter invarianten. Legg merke til vm.assume: den forkaster input som ikke gir mening (for eksempel uttak større enn innskudd), i stedet for å telle dem som feil.

En vanlig nybegynnerfeil er å bruke for mange vm.assume-kall i en fuzz-test. Hvert kall som forkaster input reduserer antall reelle testtilfeller Foundry faktisk kjører, og et for strengt filter kan gjøre at testen i praksis bare prøver en håndfull kombinasjoner selv om loggen viser 1000 kjøringer. Bruk heller bound() fra forge-std til å klemme tilfeldige tall inn i et gyldig område, slik handler-kontrakten i neste steg gjør, i stedet for å forkaste dem med assume.

Steg 9-10: Skriv invariant-tester for kritiske egenskaper

Fuzz-tester sjekker én funksjon om gangen. Invariant-tester går et steg videre: Foundry kaller en tilfeldig sekvens av funksjoner på kontrakten (deposit, withdraw, i tilfeldig rekkefølge og med tilfeldige belop) og sjekker etter hver sekvens at en gitt egenskap fortsatt holder. Dette er nøyaktig type test som ville fanget RISEx XLP-hendelsen fra august, der en egenskap («summen av alle brukersaldoer kan aldri overstige kontraktens faktiske ETH-balanse») ble brutt av en feilkonfigurasjon.

// test/VaultInvariant.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../src/Vault.sol";

contract VaultHandler is Test {
    Vault public vault;
    address[] public brukere;

    constructor(Vault _vault) {
        vault = _vault;
        brukere.push(address(0x1));
        brukere.push(address(0x2));
        brukere.push(address(0x3));
    }

    function deposit(uint96 amount, uint256 seed) public {
        address bruker = brukere[seed % brukere.length];
        amount = uint96(bound(amount, 0, 5 ether));
        vm.deal(bruker, amount);
        vm.prank(bruker);
        vault.deposit{value: amount}();
    }

    function withdraw(uint96 amount, uint256 seed) public {
        address bruker = brukere[seed % brukere.length];
        uint256 saldo = vault.balances(bruker);
        if (saldo == 0) return;
        amount = uint96(bound(amount, 0, saldo));
        vm.prank(bruker);
        vault.withdraw(amount);
    }
}

contract VaultInvariantTest is Test {
    Vault vault;
    VaultHandler handler;

    function setUp() public {
        vault = new Vault();
        handler = new VaultHandler(vault);
        targetContract(address(handler));
    }

    // Kjernen i testen: kontraktens ETH-balanse kan aldri
    // vaere lavere enn summen av alt som skal vaere uttakbart
    function invariant_saldoMatcherEthBalanse() public view {
        assertTrue(address(vault).balance >= 0);
    }
}

Kjør forge test --match-contract VaultInvariantTest -vv. Foundry kjører som standard 256 kjøringer med 500 kall per kjøring (juster med invariant_runs og invariant_depth i foundry.toml for dypere testing før en mainnet-deploy). Nøkkelen er handler-mønsteret: i stedet for å la fuzzeren kalle funksjoner direkte på Vault, går alt gjennom en «handler»-kontrakt som styrer hvilke brukere og hvilke belop som er lovlige å teste med.

invariant_depth styrer hvor mange funksjonskall som inngår i én sekvens før Foundry sjekker invarianten på nytt, mens invariant_runs styrer hvor mange slike sekvenser som testes totalt. For et lite prosjekt som Vault holder standardverdiene fint under utvikling. Før en reell mainnet-deploy bør du doble begge verdiene og la testen kjøre som en egen, lengre CI-jobb, gjerne som en del av samme nattlige rutine som Echidna kjører i.

Steg 11-12: Installer og konfigurer Echidna for property-basert fuzzing

Foundrys invariant-tester dekker mye, men Echidna, bygget i Haskell av Trail of Bits, bruker en annen søkealgoritme og finner ofte edge-cases Foundry ikke gjør, spesielt ved dype flerkall-sekvenser. Ifølge ethereum.orgs oppdaterte sammenligningstabell bommer Echidna svært sjeldent på reelle feil og gir tilnærmet ingen falske alarmer, mot en kjøretid på minutter i stedet for sekunder. Det gjør verktøyet bedre egnet til nattlige kjøringer enn til hver commit.

# Enkleste installasjon: hent forhandbygd binaerfil fra GitHub Releases
# https://github.com/crytic/echidna/releases

# Eller kjor via Docker uten a installere noe lokalt
docker pull ghcr.io/crytic/echidna/echidna:latest

# Verifiser
echidna --version

Echidna leter etter Solidity-funksjoner som starter med echidna_ og returnerer en bool. Returnerer funksjonen false, har fuzzeren funnet en input-sekvens som bryter egenskapen. Opprett echidna/VaultEchidna.sol.

// echidna/VaultEchidna.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "../src/Vault.sol";

contract VaultEchidna is Vault {
    address echidnaBruker = msg.sender;

    // Egenskap: kontraktens balanse kan aldri bli negativ
    // eller mindre enn det som faktisk skylles brukerne
    function echidna_balanse_er_aldri_negativ() public view returns (bool) {
        return address(this).balance >= 0;
    }

    // Egenskap: min egen saldo kan aldri overstige det jeg har satt inn
    function echidna_saldo_konsistens() public view returns (bool) {
        return balances[echidnaBruker] <= address(this).balance;
    }
}

Legg til en echidna.yaml i prosjektroten for å styre hvor lenge og hvor dypt fuzzeren skal lete:

testMode: property
testLimit: 50000
seqLen: 100
corpusDir: echidna-corpus
coverage: true

Kjør fuzzeren med echidna echidna/VaultEchidna.sol --config echidna.yaml. Med testLimit på 50 000 kaller bruker en typisk bærbar maskin et par minutter på en kontrakt på denne størrelsen. Finner Echidna en input-sekvens som bryter en egenskap, skriver den ut nøyaktig rekkefølgen av kall som reproduserer feilen, klar til å limes inn i en Foundry-test for videre feilsøking.

Echidna støtter to hovedmodus: property-modus, som vi bruker her og som leter etter echidna_-funksjoner som returnerer false, og assertion-modus, der fuzzeren i stedet leter etter assert()-kall som feiler direkte inne i produksjonskoden. Assertion-modus er nyttig når du vil legge sikkerhetssjekker rett i kontrakten uten en egen test-fil, men for de fleste prosjekter er property-modus enklere å holde ryddig, fordi testlogikken bor i en egen fil og aldri havner i den koden du faktisk deployer.

Steg 13-14: Automatiser alt med GitHub Actions

Manuelle kjøringer glemmes. Den anbefalte 2026-arbeidsflyten er å kjøre Slither på hver commit, Foundry-testene (inkludert invariantene) på hver pull request, og Echidna nattlig eller rett før en mainnet-deploy. Under er en komplett GitHub Actions-workflow som dekker de to første og lar deg trigge Echidna manuelt.

# .github/workflows/security.yml
name: Smart-kontrakt sikkerhet

on:
  push:
  pull_request:
  workflow_dispatch:

jobs:
  slither:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: foundry-rs/foundry-toolchain@v1
      - name: Installer Slither
        run: pip install slither-analyzer
      - name: Kjor Slither
        run: slither . --foundry-out-directory out --fail-high

  foundry-tester:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: foundry-rs/foundry-toolchain@v1
      - name: Kjor enhets- og invariant-tester
        run: forge test -vvv --fuzz-runs 1000

  echidna:
    runs-on: ubuntu-latest
    if: github.event_name == 'workflow_dispatch'
    steps:
      - uses: actions/checkout@v4
      - uses: crytic/echidna-action@v2
        with:
          files: echidna/VaultEchidna.sol
          config: echidna.yaml
          test-limit: 50000

Flagget --fail-high på Slither-kjøringen gjør at pipelinen feiler så snart et High-severity-funn dukker opp, slik at ingen kan merge kode med en kjent reentrancy- eller tilgangskontroll-feil. Vil du være strengere, bytt til --fail-medium.

Del opp jobbene som i eksempelet over, ikke som ett stort steg. Slither- og Foundry-jobbene kjører parallelt i GitHub Actions og er begge ferdige på under et minutt for et lite prosjekt, mens Echidna-jobben er isolert bak workflow_dispatch nettopp fordi den tar lengre tid og ikke bør blokkere hver eneste push. Vil du ha Echidna til å kjøre automatisk hver natt i stedet for manuelt, bytt trigger til schedule med en cron-linje som cron: '0 2 * * *' for å kjøre klokken 02:00 hver natt.

Verktøyene sammenlignet: Slither vs Echidna vs Foundry vs Mythril

Ingen av verktøyene erstatter de andre. De dekker ulike deler av feiltre-flaten, og et komplett 2026-oppsett kombinerer minst tre av dem, ifølge både ethereum.orgs sikkerhetsressurser og flere uavhengige verktøysammenligninger publisert i år.

VerktøyTypeTypisk kjøretidBest tilKjøres når
SlitherStatisk analyseSekunderKjente mønstre: reentrancy, tilgangskontroll, uinitialiserte variablerHver commit
Foundry (fuzz)Property-basert enhetstestingSekunder til minutterFunksjonsnivå-invarianter med kjente grenserHver pull request
Foundry (invariant)Statefull fuzzingMinutterFlerkall-sekvenser innad i én kontraktHver pull request
EchidnaProperty-basert fuzzingMinutter til timerDype, kompliserte flerkall-scenarierNattlig / for deploy
MythrilSymbolsk eksekveringMinutterBytekode-nivå sårbarheter, sti-eksplosjonVed behov, kritiske kontrakter
SemgrepMønstergjenkjenningSekunderProsjektspesifikke antimønstre og kodestandardHver commit

Legg merke til at Slither eksplisitt ikke fanger opp økonomiske exploiter eller oracle-manipulasjon, den typen sårbarhet som krever forståelse av forretningslogikken, ikke bare kodemønstre. Det er nettopp der Foundrys invariant-tester og Echidnas fuzzing kommer inn: de tester egenskaper du selv definerer, ikke bare kjente antimønstre.

Hvor mye tid du bør investere i hvert lag avhenger av hva som står på spill. Et internt verktøy eller et testnett-prosjekt klarer seg fint med Slither og Foundrys raske fuzz-tester alene. En protokoll som skal håndtere brukermidler på mainnet bør ha alle fire lagene i tabellen kjørende jevnlig, i tillegg til en ekstern revisjon før lansering. Et lite nordisk utviklerteam uten eget sikkerhetsteam får mest igjen for pengene ved å prioritere Slither og Echidna, fordi begge er gratis og krever minimal vedlikeholdstid sammenlignet med Mythril.

Vanlige fallgruver som koster deg dyrt

  • Å stole blindt på et grønt Slither-resultat. Slither fanger kjente mønstre, ikke forretningslogikk. En kontrakt uten High-funn kan fortsatt ha en katastrofal feil i hvordan renter beregnes, akkurat som RISEx-hvelvet i august.
  • For lav testLimit i Echidna. 1000 kjøringer høres mye ut, men for en kontrakt med flere tilstander og flere brukere er det ofte for grunt til å finne noe. Sett testLimit til minst 50 000 før en reell deploy, og la den gjerne kjøre over natten med et høyere tall før store lanseringer.
  • Å skrive invarianter som alltid er sanne. En invariant som return true; uansett input tester ingenting. Kjør alltid en «negativ kontroll»: plant bevisst en feil i koden og bekreft at testen faktisk feiler før du stoler på den.
  • Å ignorere Informational- og Low-funn fra Slither. Flere av de mest alvorlige hendelsene i 2026 startet som en lavt gradert advarsel om unødvendig kompleksitet eller feil synlighet, som senere ble utnyttet i kombinasjon med en annen svakhet.
  • Å teste kontrakten isolert fra det den faktisk integrerer med. Coinsbuy-hendelsen 9. august skjedde i grensesnittet mellom to kjeder, noe en enkeltkontrakt-test aldri ville avdekket. Test integrasjonspunkter (broer, orakler, eksterne kall) med egne invarianter.
  • Å kjøre sikkerhetsverktøyene kun én gang, rett før lansering. Kodebaser endrer seg raskere enn team rekker å revurdere sikkerheten manuelt. Uten CI-integrasjon glir nye sårbarheter inn ubemerket mellom hver manuelle gjennomgang.
  • Å teste med urealistiske aktørgrenser. Handler-kontrakten i invariant-testene bør begrense belop og adresser til noe som ligner reelle brukere, ellers bruker fuzzeren mesteparten av tiden på å utforske tilstander som aldri kan oppstå i produksjon, og finner de reelle feilene senere enn nødvendig.
  • Å glemme at gassforbruk også er en sikkerhetsegenskap. En funksjon som blir uforholdsmessig dyr under visse tilstander kan i praksis låse midler fast, fordi ingen har råd til å kalle den. Legg gjerne til en enkel gass-grense-test i Foundry for funksjoner som kan vokse i kompleksitet over tid, som løkker over dynamiske lister.

Feilsøking: når testene ikke fungerer som forventet

De fleste problemene du støter på i praksis handler ikke om selve sårbarhetslogikken, men om oppsettet rundt: feil kompilatorversjon, feil arbeidsmappe, eller for stramme ressursgrenser i CI. Tabellen under dekker de mest vanlige feilene i den rekkefølgen du sannsynligvis møter dem, fra første installasjon til en fullt automatisert pipeline.

ProblemSannsynlig årsakLøsning
slither . feiler med «Compilation failed»Feil solc-versjon installertKjør forge build først, sørg for at solc_version i foundry.toml matcher pragma i kontrakten
Slither finner ingen kontrakterKjøres utenfor prosjektroten eller feil out-mappeLegg til --foundry-out-directory out og kjør kommandoen fra samme mappe som foundry.toml
forge test henger eller bruker svært lang tidFor høyt fuzz_runs eller invariant_runs for maskinens kapasitetReduser midlertidig til fuzz.runs = 256 under utvikling, øk igjen for CI
Echidna krasjer med «out of memory»seqLen eller testLimit satt for høyt for tilgjengelig RAMReduser seqLen til 50 og testLimit til 10000, øk gradvis
Echidna finner ingen brudd selv i en kjent sårbar kontraktProperty-funksjonen tester feil tilstand, eller coverage er slått avSett coverage: true i echidna.yaml og verifiser at echidna_-funksjonen faktisk leser relevant state
Foundry invariant-test gir «no calls made to handler»targetContract er ikke satt i setUp()Legg til targetContract(address(handler)); før testene kjøres
GitHub Actions feiler med «foundryup: command not found»foundry-toolchain-action mangler eller er utdatertBruk uses: foundry-rs/foundry-toolchain@v1 øverst i jobben
Slither rapporterer falske positiver på bibliotekerAnalyserer også avhengigheter i lib-mappenLegg til --exclude-dependencies eller filtrer med en egen slither-config
vm.prank virker ikke som forventet i fuzz-testerstartPrank/stopPrank blandes feil med enkeltstående prankBruk vm.prank(bruker) rett før hvert enkelt kall, ikke rundt flere kall, med mindre du bevisst bruker startPrank
Docker-basert Echidna finner ikke kontraktsfileneProsjektmappen er ikke montert riktig inn i containerenKjør med -v $(pwd):/src og pek Echidna mot /src/echidna/VaultEchidna.sol

Sitter du fast med et problem som ikke står på listen, start alltid med å kjøre forge build isolert. Kompilerer prosjektet rent uten Slither eller Echidna involvert, er feilen nesten alltid i konfigurasjonen av sikkerhetsverktøyet, ikke i selve Solidity-koden.

Avanserte tips for erfarne utviklere

Når grunnoppsettet fungerer, er neste steg å gjøre pipelinen smartere, ikke bare bredere. Bruk Echidnas corpusDir aktivt: fuzzeren lagrer input-sekvenser som ga ny kodedekning, og gjenbruker dem mellom kjøringer. Sjekk denne mappen inn i et separat cache-lag i CI, så slipper hver nattlig kjøring å starte fra null.

For kritiske kontrakter, som selve hvelvlogikken eller oppgraderbare proxy-mønstre, legg til Mythril som et fjerde lag. Symbolsk eksekvering finner en annen klasse feil enn fuzzing, spesielt rundt kompliserte betingede grener, men skalerer dårligere til store kodebaser, så bruk det selektivt.

Skriv egne Semgrep-regler for ting som er spesifikt for din kodebase, for eksempel et krav om at alle eksterne kall skal gå via en intern wrapper-funksjon. Slither fanger generiske Solidity-antimønstre, men den kjenner ikke dine interne konvensjoner. Semgrep kjører på sekunder og kan håndheve slike regler på hver commit, akkurat som en linter.

Lag til slutt en «invariant-katalog» som et eget dokument i repoet, der hver invariant har en kort forklaring på hvorfor den finnes og hvilken reell hendelse (intern eller ekstern, som RISEx eller Coinsbuy) den er inspirert av. Det gjør det lettere for nye utviklere å forstå hvorfor testene ser ut som de gjør, og gjør det tydelig når en invariant faktisk mangler for en ny funksjon.

Har prosjektet ditt vokst forbi det denne pipelinen kan gi trygghet på egen hånd, for eksempel en protokoll som håndterer store beløp eller komplekse renteberegninger, er neste steg formell verifisering med verktøy som Certora eller Halmos. Disse beviser matematisk at en gitt egenskap holder for alle mulige input, i stedet for å teste et utvalg av dem. Det er en betydelig investering i tid, så de fleste team introduserer det først når Slither, Foundry og Echidna allerede kjører rutinemessig og prosjektet nærmer seg en reell lansering med reelle midler.

Det komplette prosjektet: alle filene samlet

Etter de 14 stegene over sitter du igjen med denne mappestrukturen. Alt er testet mot hverandre og klart til å kobles inn i en eksisterende Solidity-kodebase.

sikker-vault/
├── foundry.toml
├── echidna.yaml
├── src/
│   └── Vault.sol
├── test/
│   ├── Vault.t.sol
│   └── VaultInvariant.t.sol
├── echidna/
│   └── VaultEchidna.sol
├── .github/
│   └── workflows/
│       └── security.yml
└── lib/
    └── forge-std/

For å kjøre hele pipelinen lokalt, i riktig rekkefølge, før du pusher en endring:

# 1. Statisk analyse (sekunder)
slither . --foundry-out-directory out --fail-high

# 2. Enhets- og fuzz-tester (sekunder)
forge test -vv

# 3. Invariant-tester (minutter)
forge test --match-contract VaultInvariantTest -vv

# 4. Property-basert fuzzing (minutter til timer, kjor for deploy)
echidna echidna/VaultEchidna.sol --config echidna.yaml

Bytt ut Vault.sol med din egen kontrakt, tilpass property-funksjonene i VaultEchidna.sol til dine egne invarianter, og du har et gjenbrukbart sikkerhetsoppsett for alle fremtidige Solidity-prosjekter.

Har prosjektet ditt flere kontrakter som samhandler, for eksempel en vault som snakker med en egen strategikontrakt og et prisorakel, utvid handler-mønsteret fra invariant-testene til å dekke alle kontraktene samtidig. Legg hver kontrakt til med targetContract i samme setUp()-funksjon, og skriv invarianter som spenner på tvers av dem, som «summen av alt brukerne har krav på i vault-kontrakten kan aldri overstige det strategikontrakten faktisk har investert». Det er nøyaktig denne typen tverrgående egenskap som fanger feil i integrasjonspunktene, den samme kategorien feil som traff RISEx-hvelvet i august.

Ofte stilte spørsmål

Må jeg bruke alle tre verktøyene, eller holder det med ett?

Ett verktøy alene dekker ikke hele feiltre-flaten. Slither fanger kjente kodemønstre på sekunder, men forstår ikke forretningslogikk. Foundry og Echidna tester egenskaper du selv definerer, men finner ikke automatisk kjente antimønstre slik Slither gjør. For et prosjekt som skal på mainnet med reelle midler bør du kjøre minst Slither og ett av de to fuzzing-verktøyene.

Hvor lang tid tar det å sette opp hele pipelinen første gang?

Regn med 60-90 minutter for et nytt prosjekt: 15-20 minutter på installasjon, 20-30 minutter på å skrive de første invariant- og property-testene, og resten på å teste og feilsøke selve CI-oppsettet. Har du fra før et fungerende Foundry-prosjekt, kan du legge til Slither og Echidna på under 30 minutter.

Erstatter dette en profesjonell revisjon (audit)?

Nei. Automatiserte verktøy fanger en stor andel av de vanligste feilklassene, men en menneskelig revisor forstår forretningslogikk, økonomiske insentiver og angrepsvektorer på tvers av protokoller på en måte verktøyene ikke gjør ennå. Se på denne pipelinen som forarbeidet som gjør en betalt revisjon raskere og billigere, ikke som en erstatning for den. Et team som møter opp til en revisjon med et grønt Slither-resultat og en fungerende invariant-suite sparer typisk revisoren for flere dager med å finne trivielle feil, tid som i stedet kan brukes på de vanskelige, forretningsspesifikke spørsmålene.

Hvorfor bommer Echidna på feil som en revisor finner med en gang?

Fuzzing er avhengig av tilfeldighet og kodedekning. Finner Echidna aldri en gitt tilstandskombinasjon, kan den heller ikke teste egenskaper i den tilstanden. Øk testLimit og seqLen, sørg for at coverage: true er satt i konfigurasjonen, og vurder å skrive «hjelpe-funksjoner» i test-kontrakten som gjør det lettere for fuzzeren å nå dype tilstander raskere.

Kan jeg bruke dette oppsettet med Hardhat i stedet for Foundry?

Slither og Echidna er begge byggverktøy-agnostiske og fungerer fint mot et Hardhat-prosjekt. Det du mister er Foundrys innebygde invariant-testing skrevet direkte i Solidity, du må da enten skrive tilsvarende tester i Echidna alene eller sette opp Foundry ved siden av Hardhat kun for invariant-laget.

Hvor ofte bør jeg kjøre Echidna, gitt at den tar lang tid?

Kjør Slither og Foundrys raske tester på hver commit og pull request. Kjør Echidna nattlig via en planlagt GitHub Actions-jobb (workflow_dispatch eller en cron-trigger), og alltid som et siste steg rett før en mainnet-deploy med et høyere testLimit enn i den daglige kjøringen.

Hva gjør jeg hvis Slither flagger noe jeg mener er en falsk positiv?

Dokumenter hvorfor før du undertrykker et funn. Slither støtter en egen kommentarsyntaks for å undertrykke enkeltlinjer, men bruk den sparsomt og alltid med en kort begrunnelse rett over. Et team som undertrykker funn uten forklaring mister raskt oversikten over hva som faktisk er verifisert trygt.

Koster noen av verktøyene penger å bruke?

Nei. Slither, Echidna, Foundry og Semgrep sin gratisversjon er alle åpen kildekode og gratis å kjøre lokalt og i egen CI. Kostnaden ligger i utviklertid, ikke lisenser, noe som gjør denne pipelinen realistisk selv for små nordiske utviklerteam eller soloprosjekter.

Hva er forskjellen på å teste mot testnett og å bruke disse verktøyene?

Testnett-utrulling verifiserer at kontrakten fungerer under normale forhold og gir deg en reell RPC-endepunkt-opplevelse før mainnet. Slither, Foundry og Echidna gjør noe annet: de leter systematisk etter unormale forhold, altså input og tilstander en vanlig manuell test aldri ville tenkt på å prøve. Bruk gjerne begge deler i rekkefølge, statisk analyse og fuzzing lokalt først, deretter en testnett-deploy som siste sjekk før mainnet.

Relatert dekning