Mellom 2020 og 2025 forsvant over 3,8 milliarder dollar fra Ethereum-baserte prosjekter gjennom sårbare smart kontrakter, og 3,1 milliarder dollar av dette skjedde i løpet av bare første halvår 2025, ifølge en sikkerhetsgjennomgang fra Metana. Pengene ble ikke stjålet fordi angriperne knakk kryptografien. De ble stjålet fordi utviklere skrev kode med hull som reentrancy, manglende tilgangskontroll og prisorakler som var lette å manipulere. Denne guiden viser deg, steg for steg, hvordan du bygger en Solidity-kontrakt som lukker de vanligste hullene, tester den med Foundry, og kjører den gjennom statisk analyse med Slither før den noensinne treffer et testnett.
Du trenger ingen forkunnskaper om avanserte auditeringsteknikker. Det du trenger er grunnleggende Solidity-erfaring, litt tid, og verktøyene vi installerer i steg 1. Målet er et komplett, kjørbart prosjekt du kan bruke som mal for dine egne kontrakter i 2026, uansett om du bygger et nordisk DeFi-produkt eller bare vil forstå hvorfor kontrakten din trenger mer enn en require-setning her og der.
Guiden er bygget rundt tolv konkrete steg som følger naturlig etter hverandre: fra oppsett av verktøykjeden, via de fire vanligste angrepskategoriene, til automatisert testing og en trygg deploy-prosess. Du kan følge stegene i rekkefølge og ende opp med en ferdig, testet kontrakt, eller hoppe rett til det steget som dekker sårbarheten du er mest bekymret for akkurat nå. Uansett hvilken vei du velger, ligger den ferdige, samlede kontrakten klar lenger ned i artikkelen, slik at du kan sammenligne din egen kode med sluttresultatet underveis.
Trusselbildet i 2025 og 2026: hvorfor dette fortsatt er et problem
Reentrancy har vært kjent siden The DAO-hacket i 2016, der angriperen tappet 3,6 millioner ether (rundt 60 millioner dollar den gangen) ved å kalle tilbake inn i en kontrakt før saldoen ble oppdatert. Ti år senere skulle man tro problemet var løst. Det er det ikke. En rapport fra Bestla VC om institusjonell risikovurdering av smart kontrakter viser at reentrancy-feil fortsatt dukker opp i rundt 30 prosent av smart kontrakt-revisjoner per 2026. Feilen er velkjent, mønsteret for å fikse den er velkjent, og likevel havner den i produksjonskode gang på gang.
Bildet har også endret seg. Der reentrancy dominerte for noen år siden, viser en analyse fra Blockeden av angrepsfordelingen at reentrancy sto for bare rundt 9 prosent av registrerte DeFi-angrep i 2025, mens oracle-manipulasjon økte til 28 prosent, tilgangskontroll-feil til 22 prosent, og kombinasjoner av flash loan og andre svakheter nådde 25 prosent av angrepene. Andre kilder, som en gjennomgang fra Gate.com, anslår at reentrancy fortsatt stod for rundt 35 prosent av tapene i 2025, mens flash loan-eksploiter utgjorde 28 prosent og logiske feil 22 prosent. Tallene spriker mellom kilder fordi de måler ulike datasett og tidsrom, men konklusjonen er den samme: angriperne kombinerer sjelden bare én svakhet. De kjeder sammen flash loans, prismanipulasjon og dårlig tilgangskontroll i samme transaksjon.
Et konkret eksempel er KiloEx-eksploiten i april 2025, der protokollen tapte rundt 7,5 millioner dollar etter at en angriper manipulerte prisoraklet i en multi-chain DeFi-oppsett. En rapport fra Blockeden om OWASP sin 2026-analyse peker også på at april 2025 alene hadde 15 flash loan-relaterte eksploiter, en økning på 124 prosent i tap sammenlignet med måneden før. En egen 2026-rapport fra samme kilde om revisjonslandskapet anslår at tilgangskontroll-sårbarheter alene sto for 953,2 millioner dollar i tap i det datasettet, mot 63,8 millioner for logiske feil, 35,7 millioner for reentrancy og 33,8 millioner for flash loan-eksploiter. Tallene er ikke direkte sammenlignbare på tvers av rapporter, men de peker samme vei: dette er ikke et teoretisk problem du kan skyve foran deg til etter lansering.
Det som gjør 2026 forskjellig fra tidligere år, er ikke at sårbarhetene er nye, men at verktøyene for å finne dem har modnet betraktelig. OWASP sin Smart Contract Top 10 for 2026 sammenligner nå eksplisitt hvor godt AI-drevne revisjonsverktøy presterer mot menneskelige auditorer på hver kategori, og finner at reentrancy blir fanget nesten like godt av begge (94 til 96 prosent treffsikkerhet), mens oracle-manipulasjon og flash loan-angrep fortsatt har et gap på 32 til 38 prosentpoeng i menneskenes favør. Det betyr i praksis at et automatisert verktøy som Slither er utmerket til å luke ut de kjente, mekaniske feilene, men at du fortsatt trenger et menneskelig blikk før du slipper en kontrakt med reell verdi løs på mainnet. Denne guiden dekker begge deler: den mekaniske koden du kan automatisere bort, og resonnementet du må gjøre selv.
Forutsetninger: dette trenger du før du starter
Sett av rundt 90 minutter til å gå gjennom alle stegene i denne guiden, inkludert installasjon, koding og testing. Du trenger følgende på maskinen din:
- En Linux-, macOS- eller WSL2-maskin (Foundry støtter ikke native Windows uten WSL)
- Git installert og konfigurert
- Node.js 18 LTS eller nyere, hvis du også vil kjøre Hardhat-verktøy ved siden av
- Foundry (forge, cast, anvil) i nyeste versjon
- Python 3.8 eller nyere med pip, for å installere Slither
- En kodeeditor med Solidity-utvidelse (VS Code med Hardhat Solidity-plugin fungerer fint)
- En gratis RPC-nøkkel fra en node-leverandør, for deploy til testnett i det siste steget
- Grunnleggende kjennskap til Solidity-syntaks (funksjoner, modifiers, mapping)
Vi bruker Solidity 0.8.x gjennom hele guiden. Det er viktig fordi Solidity fra og med versjon 0.8.0 har innebygd overflow- og underflow-sjekk på alle regneoperasjoner, noe som fjerner en hel klasse sårbarheter som plaget kontrakter skrevet i 0.7.x og eldre. Skriver du fortsatt kode i en eldre versjon, er det første du bør gjøre å oppgradere pragma-linjen.
Steg 1: Sett opp Foundry og prosjektstrukturen
Foundry er i praksis blitt standardverktøyet for Solidity-utvikling i 2026, ved siden av Hardhat, fordi det lar deg skrive tester direkte i Solidity og kjøre svært rask fuzzing. Installer det og opprett et nytt prosjekt slik:
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge init sikker-kontrakt
cd sikker-kontrakt
forge build
Kommandoen foundryup henter siste stabile versjon av forge, cast, anvil og chisel. Kjør forge build med en gang for å bekrefte at grunnoppsettet fungerer før du fortsetter. Du bør se en melding om at kompileringen var vellykket, og en ny mappe out/ med kompilerte artefakter.
Steg 2: Installer OpenZeppelin Contracts
OpenZeppelin Contracts er et revidert biblioteksett med ferdige, testede implementasjoner av vanlige mønstre: eierskap, rollebasert tilgang, reentrancy-beskyttelse og token-standarder. Å skrive disse selv fra bunnen av er en av de vanligste kildene til nye sårbarheter, så bruk biblioteket i stedet for å finne opp hjulet på nytt.
forge install OpenZeppelin/openzeppelin-contracts
echo '@openzeppelin/=lib/openzeppelin-contracts/' >> remappings.txt
Remapping-linjen gjør at du kan importere med @openzeppelin/contracts/... i Solidity-filene dine i stedet for lange relative stier til lib/-mappen. Vi bruker versjon 5.x av OpenZeppelin Contracts gjennom hele guiden, som er den gjeldende hovedversjonen med moderne tilgangskontroll-mønstre.
Steg 3: Se den sårbare koden først
For å forstå hvorfor beskyttelsen trengs, må du se problemet med egne øyne. Her er en forenklet uttaksfunksjon av typen som har forårsaket ekte tap. En 2026-oppdatert sikkerhetsgjennomgang anslår at reentrancy-angrep alene forårsaket over 420 millioner dollar i tap i løpet av 2025.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SarbarVault {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// IKKE bruk denne i produksjon: sender ETH for state oppdateres
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;
}
}
Problemet ligger i rekkefølgen. Kontrakten sender ETH via call før den trekker beløpet fra balances. Hvis mottakeren er en ondsinnet kontrakt med en receive()-funksjon som kaller withdraw på nytt, vil den kunne tømme hele kontraktens saldo før den første kjøringen noensinne når linjen som oppdaterer balansen. Dette er nøyaktig samme klasse feil som The DAO-hacket utnyttet, bare med moderne syntaks.
Steg 4: Fiks reentrancy med checks-effects-interactions og ReentrancyGuard
Løsningen er todelt. Først følger du mønsteret checks-effects-interactions: sjekk betingelser, oppdater state, og gjør eksterne kall sist. Deretter legger du på et ekstra sikkerhetslag med OpenZeppelin sin ReentrancyGuard, som blokkerer rekursive kall inn i samme funksjon uansett hva resten av koden gjør feil.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract SikkerVault is ReentrancyGuard {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Ikke nok saldo");
// Effects: oppdater state FOR ekstern samtale
balances[msg.sender] -= amount;
// Interactions: ekstern samtale sist
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Overforing feilet");
}
}
Modifieren nonReentrant setter en lås ved inngang til funksjonen og fjerner den ved retur, slik at ethvert forsøk på å kalle funksjonen på nytt før første kjøring er ferdig, feiler umiddelbart. Kombinert med at balansen trekkes ned før den eksterne samtalen, er denne funksjonen beskyttet mot begge de vanligste variantene av reentrancy, både den enkle og den som går via flere kontrakter (cross-function reentrancy).
Steg 5: Legg til rollebasert tilgangskontroll
Gitt tallene fra Blockeden sin 2026-rapport, der tilgangskontroll pekes ut som den enkeltkategorien med høyest tap i deres datasett, fortjener dette temaet like mye oppmerksomhet som reentrancy. Vanlige feil er hardkodede admin-adresser, glemte onlyOwner-sjekker på kritiske funksjoner, og for grovkornede rettigheter der én nøkkel kan gjøre alt.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract RollestyrtSkatt is AccessControl {
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant OPPGRADERER_ROLE = keccak256("OPPGRADERER_ROLE");
bool public pauset;
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
function settPause() external onlyRole(PAUSER_ROLE) {
pauset = true;
}
function oppgraderLogikk(address nyImpl) external onlyRole(OPPGRADERER_ROLE) {
require(nyImpl != address(0), "Ugyldig adresse");
// oppgraderingslogikk her
}
}
Fordelen med AccessControl fremfor en enkel Ownable-modell er at du kan dele opp rettigheter i separate roller. En kompromittert nøkkel med kun PAUSER_ROLE kan stanse kontrakten i en nødsituasjon, men kan ikke oppgradere logikken eller flytte midler. Dette begrenser skadeomfanget betraktelig sammenlignet med én altomfattende eierrolle, og er spesielt viktig i kontrakter som håndterer brukermidler over tid.
Steg 6: Håndter tall trygt, selv med innebygd overflow-sjekk
Solidity 0.8.x sjekker automatisk for overflow og underflow på vanlige regneoperasjoner, og reverserer transaksjonen hvis grensen overskrides. Det betyr ikke at kategorien er helt borte. Utviklere som optimaliserer for gass, bruker ofte unchecked-blokker for å hoppe over disse sjekkene, og gjør de feilen der, er sårbarheten tilbake med vilje.
function trekkGebyr(uint256 belop, uint256 gebyrProsent) external pure returns (uint256) {
// Trygt: normal regning med innebygd sjekk
uint256 gebyr = (belop * gebyrProsent) / 100;
return belop - gebyr;
}
function trekkGebyrOptimalisert(uint256 belop, uint256 gebyrProsent) external pure returns (uint256) {
unchecked {
// Bruk KUN unchecked nar du har bevist matematisk at overflow er umulig
uint256 gebyr = (belop * gebyrProsent) / 100;
return belop - gebyr;
}
}
Regelen er enkel: bruk aldri unchecked med mindre du kan bevise, med grenseverdier og typebredde, at operasjonen aldri kan overflyte. Er du usikker, la Solidity gjøre jobben. Gassbesparelsen fra å droppe én sjekk er sjelden verdt risikoen for en feil som kan koste hele kontraktens innhold. Dette er også et av punktene Slither, som vi setter opp i steg 10, flagger automatisk.
Steg 7: Beskytt mot oracle-manipulasjon
OWASP sin oppdatering av Smart Contract Top 10 for 2025 ga prisoracle-manipulasjon en egen kategori, omtalt som SC02. Den vanligste feilen er å lese prisen direkte fra spotprisen i en likviditetspool, for eksempel en Uniswap-pool, i stedet for å bruke et tidsveiet gjennomsnitt eller en ekstern kilde. Spotprisen kan flyttes midlertidig med et stort nok lån i én og samme transaksjon.
// USIKKERT MONSTER: direkte lesing av spotpris fra en pool
function farligPris(address pool) external view returns (uint256) {
(uint112 reserve0, uint112 reserve1, ) = IUniswapV2Pair(pool).getReserves();
return uint256(reserve1) * 1e18 / uint256(reserve0);
}
// TRYGGERE: bruk en tidsveiet gjennomsnittspris (TWAP) eller ekstern oracle
function tryggPris(AggregatorV3Interface oracle) external view returns (uint256) {
(, int256 pris, , uint256 oppdatertVed, ) = oracle.latestRoundData();
require(pris > 0, "Ugyldig pris");
require(block.timestamp - oppdatertVed < 1 hours, "Pris er foreldet");
return uint256(pris);
}
Merk friskhetssjekken i den siste linjen. Et oracle som ikke har blitt oppdatert på lenge, kan gi like feil resultater som et manipulert ett, spesielt i lavlikviditets-tokens der oppdateringer skjer sjeldnere. Kombiner gjerne flere uavhengige prisfeeds og la kontrakten avvise handler hvis kildene avviker for mye fra hverandre, i stedet for å stole blindt på én enkelt leverandør.
Et vanlig mønster i moderne DeFi-protokoller er å kombinere et eksternt oracle-nettverk med en intern TWAP beregnet over egen likviditetspool, og deretter kreve at de to kildene ikke avviker mer enn noen få prosent fra hverandre før en handel godkjennes. Avviker de mer enn det, reverserer transaksjonen i stedet for å risikere å utføre en handel basert på manipulert data. Denne typen redundans koster litt ekstra gass per transaksjon, men er billig sammenlignet med kostnaden av en vellykket oracle-manipulasjon slik vi så i KiloEx-saken tidligere i artikkelen.
Steg 8: Beskytt mot flash loan-kombinasjonsangrep
En rapport fra Clawditor om flash loan-angrep viser at denne angrepstypen utgjorde over 80 prosent av kvalifiserte DeFi-eksploiter i 2024, ofte i kombinasjon med oracle-manipulasjon slik vi så i forrige steg. Fordi et flash loan lar en angriper låne enorme summer uten sikkerhet, så lenge lånet betales tilbake innen samme transaksjon, kan selv en liten logisk svakhet forsterkes til et angrep i millionklassen.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract FlashLoanBeskyttelse {
uint256 public constant MAKS_ENKELTTRANSAKSJON = 100_000e18;
mapping(address => uint256) private sisteBlokk;
modifier ikkeSammeBlokk() {
require(sisteBlokk[msg.sender] != block.number, "En handling per blokk");
_;
sisteBlokk[msg.sender] = block.number;
}
function bytt(uint256 belop) external ikkeSammeBlokk {
require(belop <= MAKS_ENKELTTRANSAKSJON, "Overstiger grense per transaksjon");
// bytte-logikk her
}
}
To enkle mottiltak fanger opp mye: en øvre grense per transaksjon som gjør et fullskala flash loan-angrep mindre lønnsomt, og en sperre som hindrer at samme adresse utfører flere sensitive handlinger i samme blokk. Ingen av disse er en fullstendig løsning alene, men kombinert med sikre oracles fra forrige steg reduserer de angrepsflaten betydelig uten å skade normal bruk av kontrakten.
Steg 9: Skriv fuzz-tester med Foundry
Manuelle tester dekker scenarioene du tenker på. Fuzz-tester genererer tusenvis av tilfeldige input og leter etter tilfeller du ikke tenkte på. Foundry har fuzzing innebygd, og alt du trenger å gjøre er å ta imot parametere i testfunksjonen.
// test/SikkerVault.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/SikkerVault.sol";
contract SikkerVaultTest is Test {
SikkerVault vault;
function setUp() public {
vault = new SikkerVault();
}
function testFuzz_KanIkkeHeveMerEnnSaldo(uint96 innskudd, uint96 uttak) public {
vm.assume(uttak > innskudd);
vm.deal(address(this), innskudd);
vault.deposit{value: innskudd}();
vm.expectRevert("Ikke nok saldo");
vault.withdraw(uttak);
}
}
Kjør testene med forge test -vvv. Foundry vil automatisk prøve hundrevis av kombinasjoner av innskudd og uttak og rapportere første tilfelle der antagelsen brytes. Får du en feil du ikke hadde forutsett, viser Foundry den nøyaktige input-kombinasjonen som utløste den, klar til å reproduseres deterministisk.
Steg 10: Kjør statisk analyse med Slither
Slither, utviklet av Trail of Bits, leser Solidity-koden din uten å kjøre den, og flagger mønstre som er kjent for å føre til sårbarheter: reentrancy, ubrukte returverdier, feilkonfigurert tilgangskontroll, og mye mer. Installer og kjør det slik:
pip3 install slither-analyzer
slither src/SikkerVault.sol --solc-remaps @openzeppelin/=lib/openzeppelin-contracts/
Et typisk utdrag fra outputen kan se slik ut, avhengig av hvilke mønstre analysatoren finner i koden din:
SikkerVault.withdraw(uint256) (src/SikkerVault.sol#12-19) uses:
- Reentrancy guard applied via ReentrancyGuard: nonReentrant
INFO:Detectors:
0 high, 0 medium, 1 informational finding(s)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation
Ingen funn betyr ikke at kontrakten er hundre prosent trygg. Slither er et statisk verktøy og fanger ikke logiske forretningsfeil som er unike for din kontrakts formål. Det den gjør veldig godt, er å fange de mekaniske, velkjente mønstrene raskt og gratis, før du bruker tid og penger på en manuell revisjon eller en profesjonell auditor.
Steg 11: Automatiser sjekkene i CI med GitHub Actions
En sjekk du kjører manuelt én gang, blir fort glemt neste gang noen endrer koden. Legg testene og Slither inn i en CI-pipeline slik at hver pull request blir sjekket automatisk, uansett hvem som skriver koden.
# .github/workflows/sikkerhet.yml
name: Sikkerhetssjekk
on: [pull_request]
jobs:
test-og-analyse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Installer Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Kjor tester
run: forge test -vvv
- name: Installer og kjor Slither
run: |
pip3 install slither-analyzer
slither src/ --solc-remaps @openzeppelin/=lib/openzeppelin-contracts/
Med denne pipelinen på plass kan ingen kode nå hovedgrenen uten å bestå både fuzz-testene og den statiske analysen. Det er en billig forsikring: noen minutter ekstra byggetid per pull request, mot risikoen for å slippe gjennom en feil som koster brukerne penger etter lansering.
Steg 12: Deploy trygt til testnett
Deploy aldri direkte til mainnet uten å ha kjørt kontrakten på et testnett først, med samme parametere og samme tilgangskontroll-oppsett du planlegger for produksjon. Sepolia er standard Ethereum-testnett i 2026 for denne typen verifisering.
forge create src/SikkerVault.sol:SikkerVault \
--rpc-url $SEPOLIA_RPC_URL \
--private-key $DEPLOYER_KEY \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY
Legg aldri private nøkler rett i terminalkommandoer eller i kildekoden. Bruk miljøvariabler eller, enda bedre, en maskinvarelommebok eller et sikkert nøkkelvelv når du etter hvert deployer til mainnet. Etter deploy, bruk --verify-flagget slik at kildekoden blir offentlig lesbar på Etherscan. Det gir brukerne dine mulighet til å verifisere at det som kjører på kjeden faktisk samsvarer med koden du har vist frem, noe som bygger tillit uten at du trenger å be om den.
Oversikt: sårbarhetstyper, tap og verktøy som fanger dem
Tallene under er hentet fra ulike 2025- og 2026-rapporter og måler ikke nødvendigvis samme tidsrom eller datasett, men gir et bilde av hvor alvorlig hver kategori er vurdert, og hvilket verktøy i denne guiden som adresserer den direkte.
| Sårbarhetstype | Rapportert alvorlighet (kilde) | Verktøy/mønster i denne guiden |
|---|---|---|
| Reentrancy | ~30% av revisjoner i 2026 (Bestla VC); over $420M tapt i 2025 (2026-sikkerhetsguide) | Checks-effects-interactions + ReentrancyGuard (steg 4) |
| Tilgangskontroll | $953,2M i tap i ett 2026-datasett (Blockeden) | AccessControl med separate roller (steg 5) |
| Integer overflow/underflow | Blant de 5 mest utnyttede kategoriene historisk (Metana) | Solidity 0.8.x + unngå unchecked uten bevis (steg 6) |
| Oracle-manipulasjon | 28% av 2025-angrep i ett datasett (Blockeden); egen OWASP-kategori SC02 | TWAP/ekstern oracle med friskhetssjekk (steg 7) |
| Flash loan-kombinasjoner | Over 80% av kvalifiserte eksploiter i 2024 (Clawditor) | Transaksjonsgrenser + samme-blokk-sperre (steg 8) |
Verktøyvalg: Foundry, Hardhat og Slither sammenlignet
Du trenger ikke velge bare ett verktøy. De fleste erfarne Solidity-team i 2026 bruker Foundry for rask testing og fuzzing, beholder Hardhat for kompleks deploy-scripting og plugin-økosystem, og kjører Slither som et statisk sikkerhetslag oppå begge. Tabellen under oppsummerer hva hvert verktøy er best egnet til.
Et poeng som ofte overses: disse verktøyene utfyller hverandre snarere enn å konkurrere. Foundry og Hardhat forteller deg om koden gjør det du tror den gjør, gitt input du har tenkt på (eller som fuzzeren finner for deg). Slither forteller deg om koden inneholder mønstre som historisk sett har ført til tap av penger, uavhengig av om testene dine består. Et prosjekt som består alle tester men får høyalvorlige Slither-funn, er ikke trygt, det er bare testet mot feil ting. Sett opp begge deler fra dag én, ikke som noe du legger til rett før lansering.
| Verktøy | Sterkeste bruksområde | Språk for tester |
|---|---|---|
| Foundry | Rask fuzzing, gass-profilering, forking av mainnet-tilstand | Solidity |
| Hardhat | Plugin-økosystem, komplekse deploy-pipelines, TypeScript-integrasjon | JavaScript/TypeScript |
| Slither | Statisk analyse, automatisk mønstergjenkjenning av kjente sårbarheter | Ingen (leser kildekode direkte) |
Komplett fungerende prosjekt: alt samlet
Under følger en samlet kontrakt som kombinerer reentrancy-beskyttelse, rollebasert tilgangskontroll og en enkel transaksjonsgrense mot flash loan-misbruk. Bruk denne som utgangspunkt for eget prosjekt, og bygg videre med oracle-integrasjonen fra steg 7 hvis kontrakten din håndterer prisfølsom logikk.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SikkerKontrakt2026 is ReentrancyGuard, AccessControl {
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
uint256 public constant MAKS_UTTAK_PER_BLOKK = 50_000e18;
mapping(address => uint256) public balances;
mapping(address => uint256) private sisteUttakBlokk;
bool public pauset;
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PAUSER_ROLE, admin);
}
modifier ikkePauset() {
require(!pauset, "Kontrakten er pauset");
_;
}
function deposit() external payable ikkePauset {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external nonReentrant ikkePauset {
require(balances[msg.sender] >= amount, "Ikke nok saldo");
require(amount <= MAKS_UTTAK_PER_BLOKK, "Overstiger grense");
require(sisteUttakBlokk[msg.sender] != block.number, "En handling per blokk");
sisteUttakBlokk[msg.sender] = block.number;
balances[msg.sender] -= amount;
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Overforing feilet");
}
function settPause(bool tilstand) external onlyRole(PAUSER_ROLE) {
pauset = tilstand;
}
}
Legg merke til at hver av de tre beskyttelsene fra tidligere steg lever side om side uten å forstyrre hverandre: nonReentrant stopper rekursive kall, rollen PAUSER_ROLE gir en nødbrems uten å gi full kontroll, og kombinasjonen av blokkgrense og beløpsgrense demper skaden fra et eventuelt flash loan-forsøk. Ingen enkeltmekanisme er perfekt alene, men sammen dekker de de fire vanligste angrepsvektorene fra tabellen over.
5 vanlige fallgruver utviklere går i
- Å stole på tx.origin for autorisasjon. Bruk
msg.sender, ikketx.origin, ellers kan en ondsinnet mellomkontrakt lure autorisasjonssjekken ved å få et offer til å kalle den, som igjen videresender kallet til din kontrakt med offerets adresse som opphav. - Å glemme at eksterne kall kan feile stille. Sjekk alltid returverdien fra
call, eller bruk et bibliotek som håndterer det for deg, ellers kan feilede overføringer se ut som suksess og etterlate kontrakten i en tilstand som ikke stemmer med det brukeren faktisk mottok. - Å bruke block.timestamp som eneste tilfeldighetskilde. Minere/validatorer har begrenset, men reell, kontroll over tidsstempelet innenfor et lite tidsvindu, så bruk aldri det alene til å avgjøre utfall i spill, loddtrekninger eller andre funksjoner der utfallet har økonomisk verdi.
- Å hoppe over testnett-fasen fordi "koden ser riktig ut". Sårbarheter dukker sjelden opp ved lesing av kode. De dukker opp når noen aktivt prøver å bryte den, noe et testnett med ekte transaksjoner og ekte gasskostnader avdekker langt bedre enn en lokal simulering.
- Å skrive egne implementasjoner av standardmønstre. ERC-20, tilgangskontroll og reentrancy-vern er løst problemer. Bruk OpenZeppelin fremfor å risikere en subtil feil i din egen versjon, selv om din variant virker enklere eller mer skreddersydd for akkurat ditt prosjekt.
Avanserte tips for team som vil videre
Når grunnmuren er på plass, er neste steg å tenke på oppgraderbarhet og formell verifisering. Bruker du proxy-mønsteret for oppgraderbare kontrakter, sørg for at initialiseringsfunksjonen kun kan kalles én gang, og bruk OpenZeppelin sitt Initializable-mønster fremfor en vanlig konstruktør, siden konstruktører ikke kjøres i implementasjonskontrakten bak en proxy.
For kontrakter som håndterer store summer, vurder et bug bounty-program før mainnet-lansering. En ekstern sikkerhetsforsker som finner feilen mot en dusør på noen titusen dollar er langt billigere enn å oppdage den samme feilen etter at den er utnyttet i produksjon. Kombiner dette med en tidsforsinkelse (timelock) på administrative endringer, slik at brukere rekker å reagere hvis en kompromittert adminnøkkel prøver å gjøre en ondsinnet endring.
En timelock trenger ikke være komplisert for å være nyttig. Selv en enkel implementasjon der en administrativ endring må varsles på kjeden og deretter vente en fastsatt periode, for eksempel 48 timer, før den kan utføres, gir brukerne dine et vindu til å trekke ut midler hvis de mistenker noe er galt. Dette er spesielt verdifullt for oppgraderbare kontrakter, der en admin i teorien kan bytte ut hele logikken bak en proxy. Uten en timelock er det ingen forskjell mellom en legitim oppgradering og et fullstendig tyveri, sett fra brukerens ståsted, før det allerede er for sent å reagere.
Til slutt, ikke stopp ved Slither. Verktøy som gjør symbolsk eksekvering eller formell verifisering kan bevise matematisk at bestemte invarianter, for eksempel at total tilbud aldri kan endre seg utenom mint- og burn-funksjoner, alltid holder. Dette er mer tidkrevende å sette opp, men gir en helt annen grad av trygghet enn mønstergjenkjenning alene, og OWASP sin 2026-analyse peker nettopp på at automatiserte AI-revisorer fortsatt ligger 32 til 38 prosentpoeng bak menneskelige auditorer på nettopp oracle-manipulasjon og flash loan-kategoriene, mens begge er nesten like gode på reentrancy.
Overvåkning og hendelsesrespons etter lansering
Sikker kode alene stopper deg ikke fra å bli overrasket. Selv en godt testet kontrakt kan oppføre seg uventet hvis en avhengighet, et oracle eller en integrert protokoll endrer seg etter at du har lansert. Derfor bør enhver kontrakt som håndterer reelle midler ha en overvåkningsplan klar allerede før den treffer mainnet, ikke som noe du improviserer etter at noe har gått galt.
Det enkleste tiltaket er å emittere hendelser (events) på alle tilstandsendrende funksjoner, slik at et eksternt overvåkningsverktøy kan følge med på uvanlige mønstre i sanntid, for eksempel et enkelt uttak som er langt over gjennomsnittet, eller mange transaksjoner fra samme adresse i rask rekkefølge. Kombinert med PAUSER_ROLE-mønsteret fra steg 5, gir dette teamet ditt et reelt handlingsrom: oppdager overvåkningen noe unormalt, kan en operatør pause kontrakten på minutter, ikke timer.
event StortUttak(address indexed bruker, uint256 belop, uint256 tidspunkt);
function withdraw(uint256 amount) external nonReentrant ikkePauset {
require(balances[msg.sender] >= amount, "Ikke nok saldo");
balances[msg.sender] -= amount;
if (amount > MAKS_UTTAK_PER_BLOKK / 2) {
emit StortUttak(msg.sender, amount, block.timestamp);
}
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Overforing feilet");
}
Et enkelt off-chain skript som lytter etter StortUttak-hendelsen kan varsle teamet på Slack eller e-post innen sekunder. Dette er ikke en erstatning for sikker kode, men et sikkerhetsnett for de scenarioene ingen kodegjennomgang klarer å forutse, spesielt i en bransje der nye angrepsteknikker dukker opp raskere enn revisjonssykluser klarer å følge med på.
Feilsøking: 8 vanlige problemer og løsninger
- «forge: command not found» etter installasjon. Sørg for at
~/.foundry/biner lagt til i PATH, og åpne et nytt terminalvindu etterfoundryup, siden endringer i skallets miljøvariabler ikke alltid slår inn i en eksisterende terminal-sesjon. - Kompileringsfeil om manglende import fra @openzeppelin. Bekreft at remappings.txt inneholder riktig sti, og at
lib/openzeppelin-contractsfaktisk finnes etterforge install. Slett og reinstaller submodulen medforge installpå nytt hvis mappen ser tom eller ufullstendig ut. - Slither krasjer med «solc version not found». Installer riktig kompilatorversjon med
solc-select install 0.8.24og aktiver den medsolc-select use 0.8.24før du kjører Slither på nytt. Dette skjer ofte når prosjektet ditt bruker en annen pragma-versjon enn den globalt installerte kompilatoren. - Fuzz-tester feiler tilfeldig ved hver kjøring. Det er ofte forventet: fuzzeren finner en ny input-kombinasjon hver gang og kan avdekke edge-caser som besto forrige kjøring. Bruk
vm.assumetil å begrense inputrommet til gyldige verdier fremfor å ignorere feilen, og undersøk alltid om feilen peker på en reell svakhet før du justerer testen. - «Insufficient funds» ved deploy til testnett. Hent gratis testnett-ETH fra en Sepolia-faucet, og bekreft at lommeboken du bruker faktisk har mottatt beløpet før du prøver på nytt. Noen faucets har daglige grenser per adresse eller krever tilkobling til en sosial konto for å hindre misbruk.
- Etherscan-verifisering feiler etter deploy. Sjekk at kompilatorversjon og optimizer-innstillinger i deploy-kommandoen er identiske med det som ble brukt ved kompilering. Selv en liten forskjell i antall optimizer-runs gir en bytekode som ikke matcher, og verifiseringen avvises.
- nonReentrant-modifier gir uventet «ReentrancyGuard: reentrant call». Dette skjer ofte når to funksjoner med samme modifier kaller hverandre internt i samme transaksjon. Del opp logikken slik at kun det ytre kallet trenger vernet, og la interne hjelpefunksjoner være uten modifieren.
- CI-jobben timer ut på Slither-steget. Store kodebaser kan gjøre statisk analyse treg. Begrens omfanget til de filene som faktisk er endret i pull requesten, eller cache avhengighetene mellom kjøringer slik at Foundry og Slither slipper å laste ned biblioteker på nytt hver gang.
Slik ser resultatet ut i praksis
Etter å ha fullført alle stegene, bør en typisk testkjøring i terminalen se omtrent slik ut:
$ forge test -vvv
Running 3 tests for test/SikkerVault.t.sol:SikkerVaultTest
[PASS] testFuzz_KanIkkeHeveMerEnnSaldo(uint96,uint96) (runs: 256, μ: 51203, ~: 51203)
[PASS] test_DepositOgWithdraw() (gas: 68421)
[PASS] test_PauseBlokkererUttak() (gas: 34122)
Test result: ok. 3 passed; 0 failed; finished in 42.11ms
Ser du «0 failed» og at Slither-rapporten ikke inneholder noen funn merket «High» eller «Medium», er kontrakten klar for neste fase: en ekstern revisjon hvis budsjettet tillater det, eller en gradvis lansering med lave beløpsgrenser i starten hvis den ikke gjør det. Ingen automatisert prosess erstatter en erfaren menneskelig revisor på kompleks forretningslogikk, men den fjerner de kjente, mekaniske feilene før du bruker den dyrebare revisjonstiden på det som faktisk krever et menneskelig blikk.
Sjekkliste før du deployer til mainnet
- Ingen eksterne kall skjer før tilstand (state) er oppdatert, eller funksjonen er beskyttet med ReentrancyGuard
- All kritisk funksjonalitet ligger bak rollebasert tilgangskontroll, ikke én enkelt eierrolle
- Ingen unchecked-blokker uten dokumentert matematisk bevis for at overflow er umulig
- Prisdata kommer fra et tidsveiet gjennomsnitt eller ekstern oracle med friskhetssjekk, aldri rå spotpris
- Transaksjons- og blokkgrenser er på plass for funksjoner som flytter store summer
- Fuzz-tester dekker grenseverdier, ikke bare de "lykkelige" scenarioene
- Slither kjører automatisk i CI ved hver pull request
- Kontrakten er testet og verifisert på testnett før mainnet-deploy
Ofte stilte spørsmål
Er Foundry eller Hardhat best for sikkerhetstesting?
Begge fungerer godt. Foundry har et fortrinn på rå hastighet og innebygd fuzzing siden testene kjører i Solidity uten en JavaScript-bro. Hardhat har fordelen av et større plugin-økosystem og enklere integrasjon i eksisterende TypeScript-verktøykjeder. Mange team i 2026 bruker faktisk begge samtidig, med Foundry for kjernetester og Hardhat for deploy-scripting.
Er Solidity 0.8.x automatisk trygg mot alle regnefeil?
Nei. Den fanger overflow og underflow på standard aritmetikk, men beskytter ikke mot logiske feil i forretningsregningen din, og ikke mot feil i kode pakket inn i en unchecked-blokk. Bruk unchecked kun når du har bevist at det er trygt.
Trenger jeg en profesjonell revisjon selv om Slither ikke finner noe?
Ja, hvis kontrakten skal håndtere betydelige brukermidler. Slither og fuzzing fanger kjente, mekaniske mønstre, men ikke forretningslogiske feil som er unike for akkurat din kontrakts formål. En menneskelig revisor ser helheten på en måte statiske verktøy ikke gjør.
Hva er forskjellen på Ownable og AccessControl?
Ownable gir én adresse full kontroll over alle beskyttede funksjoner. AccessControl lar deg definere flere separate roller med ulike rettigheter, slik at en kompromittert nøkkel ikke automatisk gir angriperen full kontroll over kontrakten.
Hvorfor er flash loan-angrep spesielt vanskelige å stoppe?
Fordi lånet og tilbakebetalingen skjer innenfor samme transaksjon, uten behov for sikkerhet, kan en angriper midlertidig kontrollere enorme summer bare for å manipulere en pris eller et stemmeresultat, og betale alt tilbake før transaksjonen er ferdig. Beskyttelsen ligger derfor ikke i å hindre lånet, men i å gjøre systemet ditt immunt mot midlertidige prisforvrengninger og enkelttransaksjoner med unormalt store beløp.
Kan jeg bruke denne guiden til kontrakter på andre EVM-kjeder enn Ethereum?
Ja. Solidity, Foundry, Hardhat, OpenZeppelin og Slither fungerer identisk på alle EVM-kompatible kjeder, siden de opererer på bytekode-nivå. Det eneste som endrer seg mellom kjeder, er RPC-adresser, blokktid og eventuelle kjedespesifikke oracle-adresser.
Hvor lang tid tar det å sette opp hele dette prosjektet fra bunnen?
Regn med rundt 90 minutter for et team som er kjent med Solidity fra før, inkludert installasjon av verktøy, skriving av kontraktene og kjøring av tester. Første gang du setter opp CI-pipelinen kan det ta noe lenger, men det er en engangskostnad som betaler seg raskt tilbake.




