Mit Solidity lassen sich aus­ge­feil­te Smart Contracts für den Betrieb auf der Ethereum-Block­chain pro­gram­mie­ren. Die Sprache bietet einige in­ter­es­san­te Ansätze, die sie von anderen Sprachen abheben.

Was ist Solidity?

Solidity ist eine High-Level-Pro­gram­mier­spra­che für die Er­stel­lung von Smart Contracts auf der Ethereum-Block­chain. Bei Smart Contracts, zu Deutsch „in­tel­li­gen­te Verträge“, handelt es sich um selbst­aus­füh­ren­de Verträge, die den Austausch von Ver­mö­gens­wer­ten zwischen Parteien au­to­ma­ti­sie­ren. Das Besondere dabei ist, dass keine Mit­tel­s­leu­te benötigt werden, um die Ein­hal­tung des Smart Contract si­cher­zu­stel­len.

Solidity-Quellcode wird in Bytecode kom­pi­liert und auf der Ethereum-Block­chain als Smart Contract be­reit­ge­stellt. Ist dies geschehen, lässt sich der Smart Contract von jedem Knoten im Netzwerk ausführen, wobei der Zustand auf der Block­chain ge­spei­chert wird. Wir zeigen ex­em­pla­risch einen simplen Vertrag, der eine NFT-Ver­kaufs­ma­schi­ne mo­del­liert:

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

Für welche An­wen­dun­gen kommt Solidity zum Einsatz?

Solidity wurde speziell für die Er­stel­lung von de­zen­tra­len An­wen­dun­gen („Dis­tri­bu­ted Ap­pli­ca­ti­ons“, DApps kon­zi­piert, die auf der Ethereum Virtual Machine (EVM) betrieben werden. Smart Contracts eignen sich u. a. für das Verwalten digitaler Assets, die Er­stel­lung von de­zen­tra­len Börsen und die Im­ple­men­tie­rung von Ab­stim­mungs­sys­te­men.

Web­hos­ting
Das beste Web­hos­ting zum Spit­zen­preis
  • 3x schneller und 60 % günstiger
  • Maximale Ver­füg­bar­keit mit > 99.99 %
  • Nur bei IONOS: Bis zu 500 GB Spei­cher­platz inklusive

De­zen­tra­le Finanzen (DeFi)

Solidity kommt zum Einsatz für die Ent­wick­lung von DeFi-An­wen­dun­gen wie de­zen­tra­len Börsen, Kredit- und Ver­leih­platt­for­men, Vor­her­sa­ge­märk­ten und Kryp­to­wäh­run­gen. DeFi hat sich zu einem der be­lieb­tes­ten An­wen­dungs­fäl­le für Block­chain-Tech­no­lo­gie ent­wi­ckelt. Solidity ist dabei zu einem un­ver­zicht­ba­ren Werkzeug zum Aufbau von DeFi-An­wen­dun­gen im Ethereum-Netzwerk geworden.

Non-Fungible Tokens

Das Non-Fungible Token (NFT), zu Deutsch „nicht aus­tausch­ba­res Token“, erfreut sich seit den 2020er Jahren großer Be­liebt­heit. NFTs sind ein­zig­ar­ti­ge digitale Assets, die auf der Block­chain ge­spei­chert sind. Dabei kann es sich etwa um digitale Kunst­wer­ke, Sport­me­mo­ra­bi­lia oder Artefakte aus der Gaming-Industrie handeln. Solidity wird verwendet, um die Smart Contracts zu erstellen, die NFTs antreiben.

Lie­fer­ket­ten­ma­nage­ment

Mithilfe von Solidity lassen sich Smart Contracts zur Über­wa­chung und Ver­wal­tung von Lie­fer­ket­ten erstellen. Die Verträge kommen zum Einsatz, um ver­schie­de­ne Prozesse der Lie­fer­ket­te zu au­to­ma­ti­sie­ren. Dazu zählen die Ver­fol­gung der Wa­ren­be­we­gung, die Über­prü­fung der Echtheit von Produkten und die Ab­wick­lung von Zahlungen zwischen den Parteien.

Ab­stim­mungs­sys­te­me

Mit Solidity lassen sich Smart Contracts erstellen, die sichere und trans­pa­ren­te Ab­stim­mungs­sys­te­me auf der Block­chain im­ple­men­tie­ren. Die Verträge können verwendet werden, um si­cher­zu­stel­len, dass Stimmen korrekt gezählt und dass der Ab­stim­mungs­pro­zess fair und trans­pa­rent ist.

Welche Vor- und Nachteile hat Solidity?

Insgesamt ist Solidity eine leis­tungs­star­ke Sprache zum Erstellen von Smart Contracts auf der Ethereum-Block­chain. Wie jede Tech­no­lo­gie hat Solidity jedoch spe­zi­fi­sche Vor- und Nachteile, deren sich Ent­wick­ler und Ent­wick­le­rin­nen beim Erstellen von Smart Contracts bewusst sein sollten. In jedem Fall erfordert die Ent­wick­lung sicherer Smart Contracts ein gewisses Maß an Fach­wis­sen und Sorgfalt.

Ex­em­pla­risch zeigen wir einen Smart Contract, der als schwarzes Loch fungiert: Jeglicher an den Contract gesendete Ether wird un­wie­der­bring­lich ge­schluckt. Es besteht keine Mög­lich­keit, den Ether wieder aus­zu­zah­len:

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

Vorteile von Solidity

  • Fle­xi­bi­li­tät: Solidity ist eine viel­sei­ti­ge Sprache. Sie lässt sich verwenden, um ganz un­ter­schied­li­che Smart Contracts mit einer Vielzahl an An­wen­dungs­fäl­len zu ent­wi­ckeln.
  • Si­cher­heit: Solidity wurde mit Blick auf Si­cher­heit ent­wi­ckelt. Die Sprache enthält Funk­tio­nen wie Zu­griffs­kon­trol­len, Aus­nah­me­be­hand­lung und Aus­fall­me­cha­nis­men, die Ent­wick­lern und Ent­wick­le­rin­nen helfen, sichere Verträge zu schreiben.
  • Ethereum-Kom­pa­ti­bi­li­tät: Solidity ist die derzeitig vor­ran­gi­ge Sprache zur Er­stel­lung von Smart Contracts auf der Ethereum-Block­chain.
  • Aus­ge­präg­te Community: Rund um Solidity existiert eine große Community von Block­chain-Ent­wick­lern und -Ent­wick­le­rin­nen. So finden sich zahl­rei­che Res­sour­cen zum Lernen und Lösen von Problemen.

Nachteile von Solidity

  • Lernkurve: Solidity hat eine relativ steile Lernkurve für Ent­wick­ler und Ent­wick­le­rin­nen, die neu in der Block­chain- und Smart-Contract-Ent­wick­lung sind.
  • Un­ver­än­der­lich­keit: Sobald ein Smart Contract auf der Block­chain be­reit­ge­stellt wird, kann er nicht weiter verändert werden. Daraus folgt, dass Ent­wick­ler und Ent­wick­le­rin­nen beim Schreiben und Testen äußerst vor­sich­tig sein müssen.
  • Fehlende formale Über­prü­fung: Solidity verfügt über keine in­te­grier­ten Tools zur formalen Über­prü­fung des Codes. Das bedeutet, dass Ent­wick­ler und Ent­wick­le­rin­nen auf externe Tools an­ge­wie­sen sind, um die Kor­rekt­heit ihrer Verträge si­cher­zu­stel­len.
  • Be­grenz­tes Tooling: Soliditys Tooling-Ökosystem ist noch relativ wenig aus­ge­prägt. Daraus ergeben sich mög­li­cher­wei­se Probleme mit IDEs, Test-Frame­works und anderen Ent­wick­lungs­tools.

Wie sieht die grund­le­gen­de Syntax von Solidity aus?

Solidity ist eine auf Smart Contracts spe­zia­li­sier­te, ob­jekt­ori­en­tier­te Sprache, die von Ja­va­Script, Python und C++ be­ein­flusst wurde. Die Syntax der Sprache ähnelt Ja­va­Script, wobei es einige in­ter­es­san­te Ei­gen­hei­ten gibt.

Variablen in Solidity

Zunächst scheinen Variablen in Solidity genauso zu funk­tio­nie­ren wie in ver­gleich­ba­ren Sprachen. Jedoch ergibt sich ein wichtiger Un­ter­schied daraus, dass die Ethereum Virtual Machine (EVM) als Aus­füh­rungs­um­ge­bung zum Einsatz kommt. Alle Ope­ra­tio­nen auf der EVM sowie die Spei­che­rung von Daten kosten eine gewisse Menge „Gas“. D. h. man muss bei der Pro­gram­mie­rung ggf. abwägen, wie eine Operation möglichst effizient im­ple­men­tiert werden kann.

Neben „normalen“ Variablen kennt Solidity Kon­stan­ten, die bei der Kom­pi­lie­rung definiert sein müssen. Kon­stan­ten benötigen weniger Gas für die Spei­che­rung:

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

Ähnlich verhält es sich mit den immutable-Variablen. Diese ver­brau­chen ebenfalls weniger Gas und lassen sich nach der Zuweisung nicht verändern. Im Gegensatz zu constant-Variablen darf die Zuweisung zur Laufzeit erfolgen.

Kon­troll­an­wei­sun­gen in Solidity

Als im­pe­ra­ti­ve Pro­gram­mier­spra­che un­ter­stützt Solidity die bekannten Kon­troll­an­wei­sun­gen, etwa Ver­zwei­gun­gen und Schleifen. Wir zeigen den Code zur Auswahl der größeren von zwei Zahlen, a und b:

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

Die for-Schleife in Solidity ent­spricht der aus aus Ja­va­Script oder C++ bekannten Syntax:

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

Auch die while-Schleife funk­tio­niert wie gewohnt. Wir kom­bi­nie­ren eine Ab­bruch­be­din­gung mit einer nu­me­ri­schen Counter-Variable:

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

Simple Typen in Solidity

Solidity ist eine statisch-ty­pi­sier­te Sprache und un­ter­stützt die üb­li­cher­wei­se in Pro­gram­mier­spra­chen vor­han­de­nen Typen. Zu den simplen Typen, die einzelne Werte abbilden, gehören Booleans, Zahlen und Strings.

Booleans in Solidity bilden die Werte true und false ab. Sie lassen sich über die ein­schlä­gig bekannten boole­schen Ope­ra­to­ren ver­knüp­fen und in if-An­wei­sun­gen einsetzen:

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

Solidity un­ter­stützt ein breites Spektrum nu­me­ri­scher Typen. Bei Integer-Zahlen lässt sich zwischen si­gnier­ten (int) und un­si­gnier­ten (uint) Zahlen un­ter­schei­den, wobei letztere nur positiv sein dürfen. Ferner lässt sich der Bereich einer Zahl in Schritten von 8 bit festlegen, von int8 über int16 bis hin zu int265:

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

Strings kommen in Solidity vor allem beim Erzeugen von Sta­tus­mel­dun­gen zum Einsatz. Die Sprache un­ter­stützt einzelne und doppelte An­füh­rungs­zei­chen sowie Unicode-Zeichen:

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

Funk­tio­nen in Solidity

Wie in den meisten Pro­gram­mier­spra­chen sind Funk­tio­nen ein zentraler Be­stand­teil von Solidity. Die De­fi­ni­ti­on einer Funktion erinnert an Ja­va­Script, wobei die Typen der Argumente explizit angegeben werden müssen. Ferner kommt ein returns-Schlüs­sel­wort zum Einsatz, um die Typen der Rück­ga­be­wer­te aus­zu­zeich­nen:

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

Der Aufruf einer Funktion erfolgt wie gewohnt:

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

In­ter­es­san­ter­wei­se lassen sich analog zu den benannten Ar­gu­men­ten auch die Rück­ga­be­wer­te benennen. In diesem Fall genügt das Zuweisen der ent­spre­chen­den Variablen im Körper der Funktion. Eine explizite Rückgabe per return ist dann nicht er­for­der­lich:

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

Ähnlich wie constant- oder immutable-Variablen lassen sich in Solidity Funk­tio­nen als nicht zu­stands­ver­än­dernd markieren. Dabei kommen die Schlüs­sel­wör­ter view und pure zum Einsatz. Eine view-Funktion verändert den Zustand nicht, während eine pure-Funktion zu­sätz­lich ga­ran­tiert, keine Zustands-Variablen aus­zu­le­sen.

Smart Contracts in Solidity

Neben den gängigen Typen kennt Solidity eine Handvoll auf Smart Contracts spe­zia­li­sier­ter Typen. Der grund­le­gen­de Typ ist address und bildet Ethereum-Adressen ab. Adressen, die payable sind, können Transfers in Ether erhalten. Dazu stellen payable-Adressen die Methoden balance() und transfer() bereit.

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

Aufbauend auf dem address-Typ gibt es den contract-Typ als zentrales Sprach­kon­strukt. Contracts ent­spre­chen in etwa Klassen in ob­jekt­ori­en­tier­ten Pro­gram­mier­spra­chen. So bündeln Contracts Zu­stands­da­ten und Funk­tio­nen und schirmen diese von der Außenwelt ab. Dabei un­ter­stüt­zen Contracts multiple Vererbung, wie aus Python oder C++ bekannt.

Contracts beginnen für ge­wöhn­lich mit einer pragma-Zeile, die die zulässige Solidity-Version angibt, gefolgt von der ei­gent­li­chen De­fi­ni­ti­on:

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

Smart Contracts können Zu­stands­da­ten und Funk­tio­nen de­fi­nie­ren. Wie aus C++ und Java bekannt, lässt sich jeweils eine von drei Zu­griffs­stu­fen festlegen:

  • public: Auf die Variable kann von innerhalb des Contracts lesend und schrei­bend zu­ge­grif­fen werden. Zu­sätz­lich wird au­to­ma­tisch eine view-Funktion als Getter für den lesenden Zugriff von außen erzeugt.
  • internal: Die Variable ist von jeglichem äußeren Zugriff ab­ge­schirmt. Von innerhalb des Contracts sowie aus erbenden Contracts kann lesend und schrei­bend zu­ge­grif­fen werden.
  • private: wie internal, jedoch besteht kein Zugriff aus erbenden Contracts

Funk­tio­nen lassen sich ferner als external aus­zeich­nen. Eine external-Funktion fungiert als Teil der Contract-Schnitt­stel­le und dient für den Zugriff von außen. Die wichtige receive-Funktion für den Empfang von Ether ist ein bekanntes Beispiel:

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

Modifiers in Solidity

Mit den Modifiers existiert ein in­ter­es­san­tes Sprach­kon­strukt in Solidity. Die Funk­tio­na­li­tät erinnert an Pythons De­co­ra­tors; wie dort kommen Modifiers zum Einsatz, um den Aufruf einer Funktion zu mo­di­fi­zie­ren. Sie werden häufig genutzt, um si­cher­zu­stel­len, dass vor dem Ausführen einer Funktion eine Bedingung erfüllt ist:

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­ak­ti­ons-Ma­nage­ment mit Solidity

Solidity verfügt über ein ein­ge­bau­tes Trans­ak­ti­ons-Ma­nage­ment. So lässt sich si­cher­stel­len, dass ein Ether-Transfer entweder komplett oder gar nicht ab­ge­wi­ckelt wird. Die Sprache kennt dafür das spezielle revert-Schlüs­sel­wort, das ein „roll-back“ einer Trans­ak­ti­on auslöst. Per error-Schlüs­sel­wort lassen sich eigene Feh­ler­codes de­fi­nie­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

Ein weiteres häufig an­zu­tref­fen­des Muster ist der Einsatz der require()-Funktion. Diese lässt sich analog zu revert verwenden:

// Using `require()` function
if (!condition) revert("Error message");
// Equivalent to
require(condition, "Error message");
solidity
Zum Hauptmenü