Grunderna i Web3- och DApp-utveckling · Lektion

Bästa praxis för kontraktssäkerhet

Upptäck viktiga säkerhetsrutiner för utveckling av smarta kontrakt, inklusive skydd mot återinträde, mönstret checks-effects-interactions och åtkomstkontroll.

Lektion 2 av 311 steg

Bästa praxis för kontraktssäkerhet är en gratis lektion i Grunderna i Web3- och DApp-utveckling på CoddyKit. Detta är lektion 2 av 3. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Grunderna i Web3- och DApp-utveckling, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Grunderna i Web3- och DApp-utveckling innehåller totalt 3 lektioner.

Introduktion till säkerhet för smarta kontrakt

Välkommen till den viktiga världen av säkerhet för smarta kontrakt! Till skillnad från traditionell programvara kan buggar i smarta kontrakt leda till oåterkalleliga förluster av tillgångar.

Eftersom smarta kontrakt är oföränderliga när de väl har distribuerats är det extremt svårt att åtgärda sårbarheter. Det kräver ofta komplexa uppgraderingsmekanismer eller till och med att ett nytt kontrakt distribueras.

I den här lektionen går vi igenom viktiga metoder för att bygga säkrare och robustare smarta kontrakt.

Förstå reentrancy

En av de mest ökända sårbarheterna är reentrancy. Den uppstår när ett externt anrop till ett annat kontrakt eller en annan adress ”återgår in” i det anropande kontraktet innan den ursprungliga funktionens tillståndsuppdateringar har slutförts.

Föreställ dig en bankomat som låter dig ta ut pengar. Om den debiterar ditt konto *efter* att ha lämnat ut pengarna skulle en reentrancy-attack vara som att upprepade gånger begära pengar innan systemet uppdaterar ditt saldo, så att bankomaten töms.

Sårbar kod för uttag

Studera det här förenklade kontraktet där en användare kan sätta in och ta ut Ether. Kan du se faran?

Funktionen withdraw() skickar först Ether och uppdaterar sedan saldot. En angripare kan anropa withdraw() igen från sitt skadliga kontrakt under det externa anropet, innan saldot har satts till noll.

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

contract VulnerableBank {
    mapping(address => uint) public balances;

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

    function withdraw(uint _amount) public {
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        // Vulnerable point: send Ether BEFORE updating balance
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Failed to send Ether");

        balances[msg.sender] -= _amount; // This happens AFTER the external call
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

Förhindra reentrancy: tillståndslås

Ett vanligt och effektivt sätt att förhindra reentrancy är att använda ett reentrancy-skydd. Det innebär att kontraktets tillstånd låses under ett externt anrop och låses upp igen efteråt.

Om ett reentrant anrop försöker köra den låsta funktionen återställs anropet. OpenZeppelins ReentrancyGuard är en populär implementation, men du kan också bygga en enkel variant själv.

Implementera reentrancy-skydd

Vi lägger till ett enkelt reentrancy-skydd med hjälp av en boolesk flagga. Det säkerställer att funktionen withdraw inte kan anropas igen förrän den pågående körningen är klar och tillståndet har uppdaterats.

Modifieraren nonReentrant aktiverar ett lås, utför operationen och frigör sedan låset.

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

contract SecureBank {
    mapping(address => uint) public balances;
    bool private _locked; // Reentrancy guard flag

    modifier nonReentrant() {
        require(!_locked, "Reentrant call detected");
        _locked = true;
        _;
        _locked = false;
    }

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

    function withdraw(uint _amount) public nonReentrant {
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        balances[msg.sender] -= _amount; // Update balance BEFORE external call (CEI)

        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }

    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

CEI-mönstret

Checks-Effects-Interactions-mönstret (CEI) är en grundläggande säkerhetsprincip. Det anger en särskild ordning för operationer i en funktion:

  • Checks: Kontrollera villkor (till exempel require-satser och onlyOwner).
  • Effects: Uppdatera kontraktets tillstånd (till exempel balances[msg.sender] -= amount).
  • Interactions: Utför externa anrop till andra kontrakt eller adresser.

Genom att följa CEI förhindrar du olika attacker, inklusive reentrancy, eftersom kontraktets tillstånd är färdigställt *innan* externa anrop görs.

CEI i uttagsfunktionen

Observera hur funktionen withdraw i vårt SecureBank redan följer CEI-mönstret:

  • Checks: require(balances[msg.sender] >= _amount, ...) och require(!_locked, ...) från modifieraren.
  • Effects: balances[msg.sender] -= _amount; uppdaterar tillståndet.
  • Interactions: msg.sender.call{value: _amount}(""); utför den externa överföringen.

Den här ordningen är avgörande för att förhindra reentrancy och säkerställa ett konsekvent tillstånd.

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

contract CEIDemo {
    mapping(address => uint) public balances;
    bool private _locked;

    modifier nonReentrant() {
        require(!_locked, "Reentrant call detected");
        _locked = true;
        _;
        _locked = false;
    }

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

    function withdraw(uint _amount) public nonReentrant {
        // --- CHECKS ---
        require(balances[msg.sender] >= _amount, "Insufficient balance");

        // --- EFFECTS ---
        balances[msg.sender] -= _amount; // State updated BEFORE external call

        // --- INTERACTIONS ---
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }
}

Begränsa åtkomst

Alla funktioner i ett smart kontrakt bör inte kunna anropas av vem som helst. Åtkomstkontroll säkerställer att endast behöriga adresser eller roller kan utföra vissa känsliga operationer.

Vanliga exempel är:

  • En onlyOwner-modifierare för administrativa funktioner.
  • Rollbaserad åtkomstkontroll (RBAC), där olika roller (till exempel MINTER och PAUSER) har specifika behörigheter.

En korrekt åtkomstkontroll är avgörande för att förhindra obehöriga åtgärder och bevara kontraktets integritet.

Ägarbegränsad funktion

Så här implementerar du en enkel onlyOwner-åtkomstkontroll med hjälp av en modifierare. Kontraktet sparar distributörens adress som owner, och endast den adressen kan anropa funktionen setNewAdmin.

Det här mönstret används i stor utsträckning för viktiga administrativa funktioner.

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

contract AccessControlled {
    address public owner;
    address public admin;

    constructor() {
        owner = msg.sender; // Deployer is the owner
        admin = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function");
        _;
    }

    function setNewAdmin(address _newAdmin) public onlyOwner {
        admin = _newAdmin;
    }

    function getAdmin() public view returns (address) {
        return admin;
    }
}

Kontroll av bästa säkerhetspraxis

Du har lärt dig om reentrancy, CEI-mönstret och åtkomstkontroll. Vilka av följande påståenden är sanna när det gäller säker utveckling av smarta kontrakt?

Sammanfattning: Säkra kontrakt

Bra jobbat! Du har förstått grundläggande säkerhetsmetoder för smarta kontrakt:

  • Reentrancy: En kritisk sårbarhet där externa anrop kan ”återgå in” i en funktion innan tillståndet uppdateras.
  • Reentrancy-skydd: Mekanismer (som mutexar eller modifierare) som låser kontraktets tillstånd under externa anrop.
  • CEI-mönstret: Den rekommenderade ordningen för operationer (Checks, Effects, Interactions) för att säkerställa att tillståndet uppdateras före externa anrop.
  • Åtkomstkontroll: Att begränsa känsliga funktioner till behöriga adresser, ofta med onlyOwner-modifierare.

Dessa metoder är avgörande för att bygga robusta och pålitliga decentraliserade applikationer. Fortsätt öva och var vaksam!

Gratis att börja

Lär dig Grunderna i Web3- och DApp-utveckling med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
29
Lektioner
105

Vanliga frågor

Är lektionen ”Bästa praxis för kontraktssäkerhet” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Grunderna i Web3- och DApp-utveckling, inklusive ”Bästa praxis för kontraktssäkerhet”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Grunderna i Web3- och DApp-utveckling innehåller totalt 3 lektioner.

Vad lär jag mig i ”Bästa praxis för kontraktssäkerhet”?

Upptäck viktiga säkerhetsrutiner för utveckling av smarta kontrakt, inklusive skydd mot återinträde, mönstret checks-effects-interactions och åtkomstkontroll. Ni övar på Grunderna i Web3- och DApp-utveckling med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Grunderna i Web3- och DApp-utveckling?

Du behöver inga förkunskaper. Utbildningen i Grunderna i Web3- och DApp-utveckling på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 3.

Hur lång tid tar lektionen ”Bästa praxis för kontraktssäkerhet”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Grunderna i Web3- och DApp-utveckling-lektionen?

Ja. Varje Grunderna i Web3- och DApp-utveckling-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. ERC-standarder (ERC-20, ERC-721)
  2. Bästa praxis för kontraktssäkerhet
  3. Uppgraderingsbara kontrakt
← Tillbaka till Grunderna i Web3- och DApp-utveckling