Solidity wordt gebruikt voor het maken van complexe slimme con­trac­ten die zijn ontworpen om te werken op de Ethereum-block­chain. Deze taal biedt unieke stra­te­gie­ën die hem on­der­schei­den van andere pro­gram­meer­ta­len.

Wat is Solidity?

Solidity is een pro­gram­meer­taal op hoog niveau voor het maken van slimme con­trac­ten op de Ethereum-block­chain. Slimme con­trac­ten zijn zelf­uit­voe­ren­de con­trac­ten die de uit­wis­se­ling van activa tussen partijen au­to­ma­ti­se­ren. Het bij­zon­de­re aan deze con­trac­ten is dat er geen tus­sen­per­so­nen nodig zijn om naleving van het slimme contract te waar­bor­gen.

Solidity-broncode wordt ge­com­pi­leerd tot bytecode en ge­ïm­ple­men­teerd op de Ethereum-block­chain als een slim contract. Zodra dit is gebeurd, kan het slimme contract worden uit­ge­voerd door elk knooppunt op het netwerk en wordt de status op­ge­sla­gen op de block­chain. We tonen een voorbeeld van een eenvoudig contract dat een NFT-automaat mo­del­leert:

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

Voor welke toe­pas­sin­gen is Solidity geschikt?

Solidity is speciaal ontworpen voor het maken van ge­dis­tri­bu­eer­de ap­pli­ca­ties of DApps die draaien op de Ethereum Virtual Machine (EVM). Slimme con­trac­ten zijn onder andere geschikt voor het beheren van digitale activa, het creëren van ge­de­cen­tra­li­seer­de uit­wis­se­lin­gen en het im­ple­men­te­ren van stem­sys­te­men.

Ge­de­cen­tra­li­seer­de financiën (DeFi)

Solidity wordt gebruikt om DeFi-toe­pas­sin­gen te ont­wik­ke­len, zoals ge­de­cen­tra­li­seer­de beurzen, krediet- en leen­plat­forms, voor­spel­lings­mark­ten en crypt­ova­lu­ta’s. DeFi is een van de po­pu­lair­ste toe­pas­sin­gen van block­chain­tech­no­lo­gie geworden. Daarbij is Solidity een onmisbaar hulp­mid­del geworden voor het bouwen van DeFi-toe­pas­sin­gen op het Ethereum-netwerk.

Niet-ver­vang­ba­re tokens

De non-fungible token (NFT) is sinds de jaren 2020 enorm populair. NFT’s zijn unieke digitale activa die op de block­chain worden op­ge­sla­gen. Het kan gaan om digitale kunst­wer­ken, sport­me­mo­ra­bi­lia of ar­te­fac­ten uit de ga­ming­in­du­strie. Solidity wordt gebruikt om de slimme con­trac­ten te creëren die NFT’s mogelijk maken.

Beheer van de le­ve­rings­ke­ten

Solidity kan worden gebruikt om slimme con­trac­ten te maken voor het monitoren en beheren van toe­le­ve­rings­ke­tens. De con­trac­ten worden gebruikt om ver­schil­len­de processen in de toe­le­ve­rings­ke­ten te au­to­ma­ti­se­ren. Deze omvatten het volgen van de be­we­gin­gen van goederen, het ve­ri­fi­ë­ren van de au­then­ti­ci­teit van producten en het verwerken van be­ta­lin­gen tussen partijen.

Ver­zoe­nings­sys­te­men

Solidity kan worden gebruikt om slimme con­trac­ten te maken die veilige en trans­pa­ran­te stem­sys­te­men op de block­chain im­ple­men­te­ren. De con­trac­ten kunnen worden gebruikt om ervoor te zorgen dat stemmen correct worden geteld en dat het stem­pro­ces eerlijk en trans­pa­rant verloopt.

Wat zijn de voor- en nadelen van Solidity?

Hoewel Solidity een krachtige taal is voor het bouwen van slimme con­trac­ten op de Ethereum-block­chain, heeft het spe­ci­fie­ke voor- en nadelen waarmee ont­wik­ke­laars rekening moeten houden bij het ont­wik­ke­len van slimme con­trac­ten. Het opstellen van veilige slimme con­trac­ten vereist echter een bepaald niveau van vaar­dig­heid en voor­zich­tig­heid.

Ter il­lu­stra­tie volgt hieronder een slim contract dat func­ti­o­neert als een zwart gat. Alle Ether die naar het contract wordt gestuurd, wordt permanent verbruikt, zonder mo­ge­lijk­heid om de Ether terug te halen of uit te betalen:

// 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

Voordelen van Solidity

  • Flexi­bi­li­teit: Solidity is een veel­zij­di­ge taal. Het kan worden gebruikt om ver­schil­len­de slimme con­trac­ten te ont­wik­ke­len met een ver­schei­den­heid aan ge­bruiks­sce­na­rio’s.
  • Be­vei­li­ging: Solidity is ont­wik­keld met de nadruk op be­vei­li­ging en bevat functies zoals toe­gangs­con­tro­le, uit­zon­de­rings­af­han­de­ling en sto­rings­me­cha­nis­men om ont­wik­ke­laars te helpen bij het opstellen van veilige con­trac­ten.
  • Com­pa­ti­bi­li­teit met Ethereum: Momenteel is Solidity de voor­keurs­taal voor het pro­du­ce­ren van slimme con­trac­ten op de Ethereum-block­chain.
  • On­der­schei­den­de community: Een grote community van block­chain-ont­wik­ke­laars werkt met Solidity, wat re­sul­teert in een overvloed aan bronnen voor leren en het oplossen van problemen.

Nadelen van Solidity

  • Leercurve: Voor ont­wik­ke­laars die nog niet bekend zijn met block­chain en de ont­wik­ke­ling van slimme con­trac­ten, heeft Solidity een relatief steile leercurve.
  • On­ver­an­der­lijk­heid: Zodra een smart contract op de block­chain is ge­ïm­ple­men­teerd, kan het niet meer worden gewijzigd. Dit betekent dat ont­wik­ke­laars uiterst voor­zich­tig moeten zijn bij het schrijven en testen.
  • Gebrek aan formele ve­ri­fi­ca­tie: Solidity mist in­ge­bouw­de tools voor formele co­de­re­view. Dit maakt het nood­za­ke­lijk dat ont­wik­ke­laars externe tools gebruiken om de nauw­keu­rig­heid van hun con­trac­ten te ga­ran­de­ren.
  • Beperkte tooling: Het tooling-eco­sys­teem van Solidity bevindt zich nog in een pril stadium, wat kan leiden tot problemen met ge­ïn­te­greer­de ont­wik­kelom­ge­vin­gen (IDE’s), test­fra­me­works en andere ont­wik­kel­tools.

Wat is de ba­sis­syn­taxis van Solidity?

Solidity is een ob­ject­ge­o­ri­ën­teer­de pro­gram­meer­taal die is ontworpen voor slimme con­trac­ten. De taal is ge­ïn­spi­reerd op Ja­vaScript, Python en C++. De syntaxis van de taal lijkt op die van Ja­vaScript, maar heeft een aantal in­te­res­san­te ei­gen­aar­dig­he­den.

Va­ri­a­be­len in Solidity

Op het eerste gezicht lijkt de va­ri­a­be­le­ver­wer­king van Solidity misschien ver­ge­lijk­baar met die van andere pro­gram­meer­ta­len. Er is echter een be­lang­rijk verschil: de Ethereum Virtual Machine (EVM) fungeert als uit­voe­ringsom­ge­ving. Alle be­wer­kin­gen op de EVM, inclusief ge­ge­vens­op­slag, brengen bepaalde ‘gas’-kosten met zich mee. Bij het pro­gram­me­ren moet daarom de ef­fi­ci­ën­tie van een bewerking worden afgewogen en moet worden bepaald hoe deze zo efficiënt mogelijk kan worden ge­ïm­ple­men­teerd.

Naast reguliere va­ri­a­be­len heeft Solidity ook con­stan­ten, die tijdens het com­pi­le­ren moeten worden ge­de­fi­ni­eerd. Con­stan­ten vereisen minder gas voor opslag:

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

Hetzelfde geldt voor immutable va­ri­a­be­len, omdat ze minder gas vereisen en niet kunnen worden gewijzigd na toe­wij­zing. In te­gen­stel­ling tot constant va­ri­a­be­len kan de toe­wij­zing van on­ver­an­der­lij­ke va­ri­a­be­len tijdens runtime worden uit­ge­voerd.

Control sta­te­ments in Solidity

Als im­pe­ra­tie­ve pro­gram­meer­taal on­der­steunt Solidity bekende con­tro­le­sta­te­ments, zoals branches en loops. We tonen de code voor het se­lec­te­ren van het grootste van twee getallen, a en b:

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

De for -lus in Solidity komt overeen met de syntaxis die bekend is uit Ja­vaScript of C++:

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

De while 3-lus werkt ook zoals ge­woon­lijk. We com­bi­ne­ren een be­ëin­di­gings­voor­waar­de met een numerieke tel­l­er­va­ri­a­be­le:

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

Een­vou­di­ge typen in Solidity

Solidity is een statisch ge­ty­peer­de taal en on­der­steunt de typen die vaak voorkomen in pro­gram­meer­ta­len. Een­vou­di­ge typen die enkele waarden ver­te­gen­woor­di­gen zijn onder andere boole­aan­se waarden, getallen en te­ken­reek­sen.

Boole­aan­se waarden in Solidity komen overeen met de waarden true en false. Ze kunnen worden gekoppeld met behulp van de bekende Boole­aan­se ope­ra­to­ren en worden gebruikt in if:

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

Solidity on­der­steunt een breed scala aan numerieke typen. Gehele getallen kunnen worden on­der­schei­den tussen getallen met teken (int) en getallen zonder teken (uint), waarbij de laatste alleen positief mogen zijn. Bovendien kan het bereik van een getal worden ge­spe­ci­fi­ceerd in stappen van 8 bits, van int8 via int16 tot int265:

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

Strings worden in Solidity voor­na­me­lijk gebruikt voor het genereren van sta­tus­be­rich­ten. De taal on­der­steunt enkele en dubbele aan­ha­lings­te­kens, evenals Unicode-tekens:

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

Functies in Solidity

Functies zijn een fun­da­men­teel aspect van Solidity, net als in de meeste pro­gram­meer­ta­len. De definitie van een functie is ver­ge­lijk­baar met Ja­vaScript, waarbij de ar­gu­ment­ty­pes expliciet moeten worden ge­spe­ci­fi­ceerd. Daarnaast wordt een returns-sleu­tel­woord gebruikt om de return waar­de­ty­pes aan te geven.

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

Het aanroepen van een functie gebeurt zoals ge­woon­lijk:

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

In­te­res­sant is dat, net als bij benoemde ar­gu­men­ten, ook re­tour­waar­den in Solidity een naam kunnen krijgen. In dit geval volstaat het om de bij­be­ho­ren­de va­ri­a­be­len in de func­tie­li­chaam toe te wijzen, en is een ex­pli­cie­te retour via return niet nodig:

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

Net als bij constant of immutable va­ri­a­be­len kunnen functies in Solidity worden ge­mar­keerd als niet-sta­tus­wij­zi­gend. Hiervoor worden de sleu­tel­woor­den view en pure gebruikt. Een view wijzigt de status niet, terwijl een pure bovendien ga­ran­deert dat er geen sta­tus­va­ri­a­be­len worden gelezen.

Slimme con­trac­ten in Solidity

Naast stan­daard­ty­pen kent Solidity een aantal speciale typen voor slimme con­trac­ten. Het basistype is address en koppelt Ethereum-adressen. Adressen die payable zijn, kunnen over­boe­kin­gen in Ether ontvangen. Voor dit doel bieden payable balance() en 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

Voort­bou­wend op het type address bestaat het type contract als een centrale taal­con­struc­tie. Con­trac­ten komen grofweg overeen met klassen in ob­ject­ge­o­ri­ën­teer­de pro­gram­meer­ta­len. Con­trac­ten bundelen dus sta­tus­ge­ge­vens en functies en schermen deze af van de bui­ten­we­reld. Con­trac­ten on­der­steu­nen meer­vou­di­ge over­er­ving, zoals bekend uit Python of C++.

Con­trac­ten beginnen meestal met een regel pragma waarin de toe­ge­sta­ne Solidity-versie wordt ge­spe­ci­fi­ceerd, gevolgd door de fei­te­lij­ke definitie:

// 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

Slimme con­trac­ten kunnen sta­tus­ge­ge­vens en functies de­fi­ni­ë­ren. Zoals bekend uit C++ en Java, kan in elk geval een van de drie toe­gangs­ni­veaus worden ge­de­fi­ni­eerd:

  • public: De variabele is toe­gan­ke­lijk door te lezen en te schrijven vanuit het contract. Bovendien wordt au­to­ma­tisch een view ge­ge­ne­reerd als een getter voor externe lees­toe­gang.
  • internal: De variabele wordt af­ge­schermd tegen externe toegang. Lezen en schrijven is mogelijk vanuit het contract zelf en vanuit over­ge­no­men con­trac­ten.
  • private: zoals internal, maar er is geen toegang vanuit over­ge­no­men con­trac­ten

Functies kunnen verder worden ge­ka­rak­te­ri­seerd als external. Een external fungeert als onderdeel van de con­trac­tin­ter­fa­ce en wordt gebruikt voor externe toegang. De receive voor het ontvangen van ether is een bekend voorbeeld:

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

Mo­di­fi­ca­to­ren in Solidity

Solidity heeft een in­tri­ge­ren­de taal­con­struc­tie in de vorm van modifiers, die lijken op de de­co­ra­tors van Python. Net als in Python worden modifiers in Solidity gebruikt om de aanroep van een functie te wijzigen. Ze worden vaak gebruikt om ervoor te zorgen dat aan een bepaalde voor­waar­de is voldaan voordat een functie wordt uit­ge­voerd:

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

Trans­ac­tie­be­heer met Solidity

Solidity heeft een ingebouwd trans­ac­tie­be­heer. Dit kan worden gebruikt om ervoor te zorgen dat een ether­o­ver­dracht volledig wordt af­ge­wik­keld of helemaal niet wordt af­ge­wik­keld. De taal begrijpt het revert -sleu­tel­woord, dat een ‘roll-back’ van een trans­ac­tie activeert. Met het error sleu­tel­woord kunt u uw eigen foutcodes de­fi­ni­ë­ren:

// 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

Een ander veel­voor­ko­mend patroon is het gebruik van de functie require(). Deze kan op dezelfde manier worden gebruikt als revert:

// Using `require()` function
if (!condition) revert("Error message");
// Equivalent to
require(condition, "Error message");
solidity
Ga naar hoofdmenu