Solidity bruges til at udarbejde komplekse smarte kon­trak­ter, der er designet til at fungere på Ethereum-blo­ck­chain. Dette sprog præ­sen­te­rer unikke stra­te­gi­er, der adskiller det fra andre pro­gram­me­rings­sprog.

Hvad er Solidity?

Solidity er et højniveau-pro­gram­me­rings­sprog til op­ret­tel­se af smarte kon­trak­ter på Ethereum-blo­ck­chain. Smarte kon­trak­ter er sel­vud­før­en­de kon­trak­ter, der au­to­ma­ti­se­rer ud­veks­lin­gen af aktiver mellem parter. Det særlige ved dem er, at der ikke er behov for mellemled for at sikre over­hol­del­se af den smarte kontrakt.

Solidity-kil­de­ko­den kom­pi­le­res til bytecode og im­ple­men­te­res på Ethereum-blo­ck­chain som en smart kontrakt. Når dette er gjort, kan den smarte kontrakt udføres af enhver node på netværket, og status gemmes på blo­ck­chain. Vi viser et eksempel på en simpel kontrakt, der mo­del­le­rer en NFT-automat:

pragma Solidity 0.8.7;
contract NFTVendingMachine {
    // Declare state variables
    address public owner;
    mapping (address => uint) public nftBalance;
    // Run on deployment
    constructor() {
        owner = msg.sender;
        nftBalance[address(this)] = 100;
    }
    // Allow the owner to restock the NFT balance
    function restock(uint amount) public {
        require(msg.sender == owner, "Only the owner can restock.");
        nftBalance[address(this)] += amount;
    }
    // Allow anyone to purchase NFTs
    function purchase(uint amount) public payable {
        require(msg.value >= amount * 1 ether, "You must pay at least 1 ETH per NFT");
        require(nftBalance[address(this)] >= amount, "Not enough NFTs in stock to complete this purchase");
        nftBalance[address(this)] -= amount;
        nftBalance[msg.sender] += amount;
    }
}
solidity

Hvilke ap­pli­ka­tio­ner er Solidity velegnet til?

Solidity er specielt designet til at skabe di­stri­bu­e­re­de ap­pli­ka­tio­ner eller DApps, der kører på Ethereum Virtual Machine (EVM). Smarte kon­trak­ter er blandt andet velegnede til at ad­mi­ni­stre­re digitale aktiver, skabe de­cen­tra­li­se­re­de børser og im­ple­men­te­re af­stem­nings­sy­ste­mer.

De­cen­tra­li­se­re­de finanser (DeFi)

Solidity bruges til at udvikle DeFi-ap­pli­ka­tio­ner såsom de­cen­tra­li­se­re­de børser, kredit- og lå­ne­p­lat­for­me, for­ud­si­gel­ses­mar­ke­der og kryp­tova­luta­er. DeFi er blevet en af de mest populære an­ven­del­ser af blo­ck­chain-teknologi. I den proces er Solidity blevet et uund­vær­ligt værktøj til at opbygge DeFi-ap­pli­ka­tio­ner på Ethereum-netværket.

Ikke-fungible tokens

Non-fungible token (NFT) har været meget populært siden 2020’erne. NFT’er er unikke digitale aktiver, der er gemt på blo­ck­chain. Det kan være digitale kunst­vær­ker, sport­s­min­der eller ar­te­fak­ter fra spi­lin­du­stri­en. Solidity bruges til at oprette de smarte kon­trak­ter, der driver NFT’er.

Le­ve­rings­kæ­de­ad­mi­ni­stra­tion

Solidity kan bruges til at oprette smarte kon­trak­ter til over­våg­ning og styring af for­sy­nings­kæ­der. Kon­trak­ter­ne bruges til at au­to­ma­ti­se­re for­skel­li­ge for­sy­nings­kæ­de­pro­ces­ser. Disse omfatter sporing af varers be­væ­gel­ser, ve­ri­fi­ka­tion af pro­duk­ters ægthed og be­hand­ling af be­ta­lin­ger mellem parter.

Af­stem­nings­sy­ste­mer

Solidity kan bruges til at oprette smarte kon­trak­ter, der im­ple­men­te­rer sikre og gen­nem­sig­ti­ge af­stem­nings­sy­ste­mer på blo­ck­chai­nen. Kon­trak­ter­ne kan bruges til at sikre, at stemmerne tælles korrekt, og at af­stem­nings­pro­ces­sen er fair og gen­nem­sig­tig.

Hvad er fordele og ulemper ved Solidity?

Solidity er et kraft­fuldt sprog til opbygning af smarte kon­trak­ter på Ethereum-blo­ck­chain, men det har sine spe­ci­fik­ke fordele og ulemper, som udviklere bør tage i be­tragt­ning, når de udvikler smarte kon­trak­ter. Ikke desto mindre kræver ud­ar­bej­del­sen af sikre smarte kon­trak­ter et bestemt niveau af færdighed og for­sig­tig­hed.

Som il­lu­stra­tion er der nedenfor et smart kontrakt, der fungerer som et sort hul. Enhver Ether, der sendes til kon­trak­ten, forbruges permanent, uden mulighed for at hente Ether eller foretage en ud­be­ta­ling:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >= 0.9.0;
// This contract swallows all Ether sent to it
contract Blackhole {
    event Received(address, uint);
    receive() external payable {
            emit Received(msg.sender, msg.value);
    }
}
solidity

Fordele ved Solidity

  • Flek­si­bi­li­tet: Solidity er et alsidigt sprog. Det kan bruges til at udvikle for­skel­li­ge smarte kon­trak­ter med en række for­skel­li­ge an­ven­del­ses­mu­lig­he­der.
  • Sikkerhed: Solidity er udviklet med fokus på sikkerhed og in­de­hol­der funk­tio­ner som ad­gangs­kon­trol, hånd­te­ring af und­ta­gel­ser og fejl­me­ka­nis­mer, der hjælper udviklere med at udarbejde sikre kon­trak­ter.
  • Ethereum-kom­pa­ti­bi­li­tet: I øje­blik­ket er Solidity det fo­re­truk­ne sprog til at producere smarte kon­trak­ter på Ethereum-blo­ck­chain.
  • Særskilt community: Et stort community af blo­ck­chain-udviklere arbejder med Solidity, hvilket re­sul­te­rer i en overflod af res­sour­cer til læring og pro­blem­løs­ning.

Ulemper ved Solidity

  • Læ­rings­kur­ve: For udviklere, der er nye inden for blo­ck­chain og udvikling af smarte kon­trak­ter, har Solidity en relativt stejl læ­rings­kur­ve.
  • Ufor­an­der­lig­hed: Når en smart kontrakt er im­ple­men­te­ret på blo­ck­chain, kan den ikke ændres yder­li­ge­re. Det betyder, at udviklere skal være yderst for­sig­ti­ge, når de skriver og tester.
  • Mangel på formel ve­ri­fi­ka­tion: Solidity mangler ind­byg­ge­de værktøjer til formel ko­de­gen­nem­gang. Dette kræver, at udviklere bruger eksterne værktøjer for at garantere nøj­ag­tig­he­den af deres kon­trak­ter.
  • Begrænset værktøj: Soliditys værk­tøj­s­øko­sy­stem er stadig i sin vorden, hvilket kan medføre problemer med in­te­gre­re­de ud­vik­lings­mil­jø­er (IDE’er), te­stram­mer og andre ud­vik­lings­værk­tø­jer.

Hvad er den grund­læg­gen­de syntaks i Solidity?

Solidity er et ob­jekt­o­ri­en­te­ret pro­gram­me­rings­sprog designet til smarte kon­trak­ter. Det er in­spi­re­ret af Ja­va­Script, Python og C++. Sprogets syntaks ligner Ja­va­Script, dog med nogle in­ter­es­san­te særheder.

Variabler i Solidity

Ved første øjekast kan Soliditys hånd­te­ring af variabler virke lig andre pro­gram­me­rings­sprog. Der er dog en vigtig forskel, som skyldes, at Ethereum Virtual Machine (EVM) fungerer som ek­se­kve­rings­mil­jø. Alle ope­ra­tio­ner på EVM, herunder da­ta­lag­ring, medfører en vis mængde “gas”-om­kost­nin­ger. Derfor skal man under pro­gram­me­rin­gen afveje en ope­ra­tions ef­fek­ti­vi­tet og afgøre, hvordan den kan im­ple­men­te­res så effektivt som muligt.

Ud over al­min­de­li­ge variabler har Solidity kon­stan­ter, som skal defineres under kom­pi­le­ring. Kon­stan­ter kræver mindre gas til op­be­va­ring:

// Regular variable can be declared without defining
int a;
// Constant needs to be defined at declaration
int constant b = 51;
solidity

Det samme gælder for immutable variabler, idet de kræver mindre gas og ikke kan ændres efter tildeling. I mod­sæt­ning til constant variabler kan til­de­lin­gen af ufor­an­der­li­ge variabler foretages under kørsel.

Kon­trol­sæt­nin­ger i Solidity

Som et im­pe­ra­tivt pro­gram­me­rings­sprog un­der­støt­ter Solidity velkendte kon­trol­sæt­nin­ger, såsom for­gre­nin­ger og sløjfer. Vi viser koden til at vælge det største af to tal, a og b:

int largerNumber = 0;
// If-else statement
if (a > b) {
    largerNumber = a;
} else {
        largerNumber = b;
}
solidity

for kken i Solidity svarer til den syntaks, der kendes fra Ja­va­Script eller C++:

// Loop 10 times
for (int i = 0; i < 10; i++) {
    // …
}
solidity

23-løkken fungerer også som normalt. Vi kom­bi­ne­rer en af­slut­nings­be­tin­gel­se med en numerisk tæl­ler­va­ri­a­bel:

bool continueLoop = true;
int counter = 0;
// Loop at most 10 times
while (continueLoop && counter < 10) {
    // …
    counter++;
}
solidity

Enkle typer i Solidity

Solidity er et statisk typet sprog og un­der­støt­ter de typer, der normalt findes i pro­gram­me­rings­sprog. Enkle typer, der re­præ­sen­te­rer en­kelt­vær­di­er, omfatter booleske værdier, tal og strenge.

Boo­le­an­ske værdier i Solidity kort­læg­ger værdierne true og false. De kan sam­men­kæ­des ved hjælp af de kendte booleske ope­ra­to­rer og bruges i if sætninger:

bool paymentReceived = true;
bool itemsStocked = true;
bool continueTransaction = paymentReceived && itemsStocked;
if (continueTransaction) {
    // ...
}
solidity

Solidity un­der­støt­ter en bred vifte af numeriske typer. Heltal kan skelnes mellem tegnede (int) og utegnede (uint) tal, hvor sidst­nævn­te kun kan være positive. Desuden kan et tals ræk­ke­vid­de spe­ci­fi­ce­res i trin på 8 bit, fra int8 via int16 op til int265:

uint8 smallNumber = 120;
int8 negativeNumber = -125;
int8 result = smallNumber + negativeNumber;
assert(result == -5)
solidity

Strings bruges i Solidity ho­ved­sa­ge­ligt til at generere sta­tus­med­del­el­ser. Sproget un­der­støt­ter både enkelt og dobbelt an­før­sels­tegn samt Unicode-tegn:

string message = 'Hello World';
string success = unicode"Transfer sent";
Solidity

Funk­tio­ner i Solidity

Funk­tio­ner er et grund­læg­gen­de aspekt af Solidity, som i de fleste pro­gram­me­rings­sprog. De­fi­ni­tio­nen af en funktion ligner Ja­va­Script, hvor ar­gu­ment­ty­per­ne skal angives eksplicit. Derudover bruges et re­tur­ne­rings­nøg­le­ord til at angive de return vær­di­ty­per.

// Define a function
function addNumbers(int a, int b) returns (int) {
    return a + b;
}
solidity

Funk­tio­nen kaldes som normalt:

// Call the function
int result = addNumbers(2, 3);
solidity

In­ter­es­sant nok kan re­tur­vær­di­er i Solidity, analogt med navngivne ar­gu­men­ter, også navngives. I denne situation er det til­stræk­ke­ligt at tildele de til­sva­ren­de variabler i funk­tions­krop­pen, og en eksplicit re­tur­ne­ring via return er unød­ven­dig:

function divideNumbers(int dividend, int divisor) returns (int quotient) {
    quotient = dividend / divisor;
    // No `return` necessary
}
solidity

Ligesom constant eller immutable variabler kan funk­tio­ner i Solidity markeres som ikke-til­stands­mæs­sigt ændrende. Nøg­le­or­de­ne view og pure bruges til dette formål. En view ændrer ikke til­stan­den, mens en pure desuden ga­ran­te­rer, at til­stands­va­ri­ab­ler ikke læses.

Smarte kon­trak­ter i Solidity

Ud over stan­dard­ty­per­ne kender Solidity en håndfuld spe­ci­a­li­se­re­de typer til smarte kon­trak­ter. Den grund­læg­gen­de type er address og kort­læg­ger Ethereum-adresser. Adresser, der er payable, kan modtage over­førs­ler i Ether. Til dette formål leverer payable balance() og transfer().

// Get address of this contract
address mine = address(this);
// Get payable external address
address payable other = payable(0x123);
// Transfer if balances fulfill conditions
if (other.balance < 10 && mine.balance >= 100) {
    other.transfer(10);
}
solidity

Baseret på typen address findes typen contract som en central sprog­kon­struk­tion. Kon­trak­ter svarer groft sagt til klasser i ob­jekt­o­ri­en­te­re­de pro­gram­me­rings­sprog. Kon­trak­ter samler således til­stands­da­ta og funk­tio­ner og beskytter dem mod om­ver­de­nen. Kon­trak­ter un­der­støt­ter multiple arv, som det kendes fra Python eller C++.

Kon­trak­ter begynder normalt med en linje, pragma angiver den tilladte Solidity-version, ef­ter­fulgt af den egentlige de­fi­ni­tion:

// Make sure Solidity version matches
pragma Solidity >=0.7.1 <0.9.0;
// Contract definition
contract Purchase {
    // Public state variables
    address seller;
    address buyer;
    
    // View-function
    function getSeller() external view returns (address) {
        return seller;
    }
}
solidity

Smarte kon­trak­ter kan definere til­stands­da­ta og funk­tio­ner. Som kendt fra C++ og Java kan der i hvert tilfælde defineres et af tre ad­gangs­ni­veau­er:

  • public: Variablen kan tilgås ved at læse og skrive fra kon­trak­ten. Derudover genereres der au­to­ma­tisk en view som en getter til ekstern læ­se­ad­gang.
  • internal: Variablen er beskyttet mod ekstern adgang. Læse- og skri­ve­ad­gang er mulig fra kon­trak­ten såvel som fra ar­ve­kon­trak­ter.
  • private: Som internal, men der er ingen adgang fra ar­ve­kon­trak­ter.

Funk­tio­ner kan yder­li­ge­re ka­rak­te­ri­se­res som external. En external fungerer som en del af kon­trakt­græn­se­fla­den og bruges til ekstern adgang. receive til mod­ta­gel­se af ether er et velkendt eksempel:

// Define without `function` keyword
receive() external payable {
    // Handle Ether
}
solidity

Mo­di­fi­ka­to­rer i Solidity

Solidity har en in­ter­es­sant sprog­kon­struk­tion i form af mo­di­fi­ka­to­rer, der ligner Pythons de­ko­ra­to­rer. Ligesom i Python bruges mo­di­fi­ka­to­rer i Solidity til at ændre på­kal­del­sen af en funktion. De bruges ofte til at sikre, at en bestemt be­tin­gel­se er opfyldt, før en funktion udføres:

contract Sale {
    uint price;
    address payable owner;
    modifier onlyOwner {
        // Will throw error if called by anyone other than the owner
        require(
            msg.sender == owner,
            "Only owner can call this function."
        );
        // The wrapped function's body is inserted here
        _;
    }
    
    // `onlyOwner` wraps `changePrice`
    function changePrice(uint newPrice) public onlyOwner {
        // We'll only get here if the owner called this function
        price = newPrice;
    }
}
solidity

Transak­tions­sty­ring med Solidity

Solidity har en indbygget transak­tions­sty­ring. Denne kan bruges til at sikre, at en ether-over­før­sel enten afvikles fuld­stæn­digt eller slet ikke afvikles. Sproget forstår nøg­le­or­det revert, som udløser en “roll-back” af en transak­tion. Med nøg­le­or­det error kan du definere dine egne fejlkoder:

// Custom error definition
error InsufficientPayment(uint256 paid, uint256 required);
// Contract representing a sale
contract Sale {
    uint price;
    // Purchase if enough ether transferred
    function purchase() public payable {
        if (msg.value < price) {
            revert InsufficientPayment(msg.value, price);
        }
        // Complete purchase
    }
}
solidity

Et andet hyppigt fo­re­kom­men­de mønster er brugen af funk­tio­nen require(). Denne kan bruges analogt med revert:

// Using `require()` function
if (!condition) revert("Error message");
// Equivalent to
require(condition, "Error message");
solidity
Gå til ho­ved­me­nu­en