Solidity: Programmiersprache für Smart Contracts
Mit Solidity lassen sich ausgefeilte Smart Contracts für den Betrieb auf der Ethereum-Blockchain programmieren. Die Sprache bietet einige interessante Ansätze, die sie von anderen Sprachen abheben.
Was ist Solidity?
Solidity ist eine High-Level-Programmiersprache für die Erstellung von Smart Contracts auf der Ethereum-Blockchain. Bei Smart Contracts, zu Deutsch „intelligente Verträge“, handelt es sich um selbstausführende Verträge, die den Austausch von Vermögenswerten zwischen Parteien automatisieren. Das Besondere dabei ist, dass keine Mittelsleute benötigt werden, um die Einhaltung des Smart Contract sicherzustellen.
Solidity-Quellcode wird in Bytecode kompiliert und auf der Ethereum-Blockchain als Smart Contract bereitgestellt. Ist dies geschehen, lässt sich der Smart Contract von jedem Knoten im Netzwerk ausführen, wobei der Zustand auf der Blockchain gespeichert wird. Wir zeigen exemplarisch einen simplen Vertrag, der eine NFT-Verkaufsmaschine modelliert:
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;
}
}
solidityFür welche Anwendungen kommt Solidity zum Einsatz?
Solidity wurde speziell für die Erstellung von dezentralen Anwendungen („Distributed Applications“, DApps konzipiert, die auf der Ethereum Virtual Machine (EVM) betrieben werden. Smart Contracts eignen sich u. a. für das Verwalten digitaler Assets, die Erstellung von dezentralen Börsen und die Implementierung von Abstimmungssystemen.
- Flexibel: Hosting, das jedem Website-Traffic standhält
- Verlässlich: Inklusive 24/7-Support und persönlicher Beratung
- Sicher: Kostenloses SSL-Zertifikat, DDoS-Schutz und Backups
Dezentrale Finanzen (DeFi)
Solidity kommt zum Einsatz für die Entwicklung von DeFi-Anwendungen wie dezentralen Börsen, Kredit- und Verleihplattformen, Vorhersagemärkten und Kryptowährungen. DeFi hat sich zu einem der beliebtesten Anwendungsfälle für Blockchain-Technologie entwickelt. Solidity ist dabei zu einem unverzichtbaren Werkzeug zum Aufbau von DeFi-Anwendungen im Ethereum-Netzwerk geworden.
Non-Fungible Tokens
Das Non-Fungible Token (NFT), zu Deutsch „nicht austauschbares Token“, erfreut sich seit den 2020er Jahren großer Beliebtheit. NFTs sind einzigartige digitale Assets, die auf der Blockchain gespeichert sind. Dabei kann es sich etwa um digitale Kunstwerke, Sportmemorabilia oder Artefakte aus der Gaming-Industrie handeln. Solidity wird verwendet, um die Smart Contracts zu erstellen, die NFTs antreiben.
Lieferkettenmanagement
Mithilfe von Solidity lassen sich Smart Contracts zur Überwachung und Verwaltung von Lieferketten erstellen. Die Verträge kommen zum Einsatz, um verschiedene Prozesse der Lieferkette zu automatisieren. Dazu zählen die Verfolgung der Warenbewegung, die Überprüfung der Echtheit von Produkten und die Abwicklung von Zahlungen zwischen den Parteien.
Abstimmungssysteme
Mit Solidity lassen sich Smart Contracts erstellen, die sichere und transparente Abstimmungssysteme auf der Blockchain implementieren. Die Verträge können verwendet werden, um sicherzustellen, dass Stimmen korrekt gezählt und dass der Abstimmungsprozess fair und transparent ist.
Welche Vor- und Nachteile hat Solidity?
Insgesamt ist Solidity eine leistungsstarke Sprache zum Erstellen von Smart Contracts auf der Ethereum-Blockchain. Wie jede Technologie hat Solidity jedoch spezifische Vor- und Nachteile, deren sich Entwickler und Entwicklerinnen beim Erstellen von Smart Contracts bewusst sein sollten. In jedem Fall erfordert die Entwicklung sicherer Smart Contracts ein gewisses Maß an Fachwissen und Sorgfalt.
Exemplarisch zeigen wir einen Smart Contract, der als schwarzes Loch fungiert: Jeglicher an den Contract gesendete Ether wird unwiederbringlich geschluckt. Es besteht keine Möglichkeit, den Ether wieder auszuzahlen:
// 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);
}
}
solidityVorteile von Solidity
- Flexibilität: Solidity ist eine vielseitige Sprache. Sie lässt sich verwenden, um ganz unterschiedliche Smart Contracts mit einer Vielzahl an Anwendungsfällen zu entwickeln.
- Sicherheit: Solidity wurde mit Blick auf Sicherheit entwickelt. Die Sprache enthält Funktionen wie Zugriffskontrollen, Ausnahmebehandlung und Ausfallmechanismen, die Entwicklern und Entwicklerinnen helfen, sichere Verträge zu schreiben.
- Ethereum-Kompatibilität: Solidity ist die derzeitig vorrangige Sprache zur Erstellung von Smart Contracts auf der Ethereum-Blockchain.
- Ausgeprägte Community: Rund um Solidity existiert eine große Community von Blockchain-Entwicklern und -Entwicklerinnen. So finden sich zahlreiche Ressourcen zum Lernen und Lösen von Problemen.
Nachteile von Solidity
- Lernkurve: Solidity hat eine relativ steile Lernkurve für Entwickler und Entwicklerinnen, die neu in der Blockchain- und Smart-Contract-Entwicklung sind.
- Unveränderlichkeit: Sobald ein Smart Contract auf der Blockchain bereitgestellt wird, kann er nicht weiter verändert werden. Daraus folgt, dass Entwickler und Entwicklerinnen beim Schreiben und Testen äußerst vorsichtig sein müssen.
- Fehlende formale Überprüfung: Solidity verfügt über keine integrierten Tools zur formalen Überprüfung des Codes. Das bedeutet, dass Entwickler und Entwicklerinnen auf externe Tools angewiesen sind, um die Korrektheit ihrer Verträge sicherzustellen.
- Begrenztes Tooling: Soliditys Tooling-Ökosystem ist noch relativ wenig ausgeprägt. Daraus ergeben sich möglicherweise Probleme mit IDEs, Test-Frameworks und anderen Entwicklungstools.
Wie sieht die grundlegende Syntax von Solidity aus?
Solidity ist eine auf Smart Contracts spezialisierte, objektorientierte Sprache, die von JavaScript, Python und C++ beeinflusst wurde. Die Syntax der Sprache ähnelt JavaScript, wobei es einige interessante Eigenheiten gibt.
Variablen in Solidity
Zunächst scheinen Variablen in Solidity genauso zu funktionieren wie in vergleichbaren Sprachen. Jedoch ergibt sich ein wichtiger Unterschied daraus, dass die Ethereum Virtual Machine (EVM) als Ausführungsumgebung zum Einsatz kommt. Alle Operationen auf der EVM sowie die Speicherung von Daten kosten eine gewisse Menge „Gas“. D. h. man muss bei der Programmierung ggf. abwägen, wie eine Operation möglichst effizient implementiert werden kann.
Neben „normalen“ Variablen kennt Solidity Konstanten, die bei der Kompilierung definiert sein müssen. Konstanten benötigen weniger Gas für die Speicherung:
// 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 verbrauchen ebenfalls weniger Gas und lassen sich nach der Zuweisung nicht verändern. Im Gegensatz zu constant
-Variablen darf die Zuweisung zur Laufzeit erfolgen.
Kontrollanweisungen in Solidity
Als imperative Programmiersprache unterstützt Solidity die bekannten Kontrollanweisungen, etwa Verzweigungen 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;
}
solidityDie for
-Schleife in Solidity entspricht der aus aus JavaScript oder C++ bekannten Syntax:
// Loop 10 times
for (int i = 0; i < 10; i++) {
// …
}
solidityAuch die while
-Schleife funktioniert wie gewohnt. Wir kombinieren eine Abbruchbedingung mit einer numerischen Counter-Variable:
bool continueLoop = true;
int counter = 0;
// Loop at most 10 times
while (continueLoop && counter < 10) {
// …
counter++;
}
soliditySimple Typen in Solidity
Solidity ist eine statisch-typisierte Sprache und unterstützt die üblicherweise in Programmiersprachen vorhandenen 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 einschlägig bekannten booleschen Operatoren verknüpfen und in if
-Anweisungen einsetzen:
bool paymentReceived = true;
bool itemsStocked = true;
bool continueTransaction = paymentReceived && itemsStocked;
if (continueTransaction) {
// ...
}
soliditySolidity unterstützt ein breites Spektrum numerischer Typen. Bei Integer-Zahlen lässt sich zwischen signierten (int
) und unsignierten (uint
) Zahlen unterscheiden, 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)
solidityStrings kommen in Solidity vor allem beim Erzeugen von Statusmeldungen zum Einsatz. Die Sprache unterstützt einzelne und doppelte Anführungszeichen sowie Unicode-Zeichen:
string message = 'Hello World';
string success = unicode"Transfer sent";
SolidityFunktionen in Solidity
Wie in den meisten Programmiersprachen sind Funktionen ein zentraler Bestandteil von Solidity. Die Definition einer Funktion erinnert an JavaScript, wobei die Typen der Argumente explizit angegeben werden müssen. Ferner kommt ein returns
-Schlüsselwort zum Einsatz, um die Typen der Rückgabewerte auszuzeichnen:
// Define a function
function addNumbers(int a, int b) returns (int) {
return a + b;
}
solidityDer Aufruf einer Funktion erfolgt wie gewohnt:
// Call the function
int result = addNumbers(2, 3);
solidityInteressanterweise lassen sich analog zu den benannten Argumenten auch die Rückgabewerte benennen. In diesem Fall genügt das Zuweisen der entsprechenden Variablen im Körper der Funktion. Eine explizite Rückgabe per return
ist dann nicht erforderlich:
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 Funktionen als nicht zustandsverändernd markieren. Dabei kommen die Schlüsselwörter view
und pure
zum Einsatz. Eine view
-Funktion verändert den Zustand nicht, während eine pure
-Funktion zusätzlich garantiert, keine Zustands-Variablen auszulesen.
Smart Contracts in Solidity
Neben den gängigen Typen kennt Solidity eine Handvoll auf Smart Contracts spezialisierter Typen. Der grundlegende 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);
}
solidityAufbauend auf dem address
-Typ gibt es den contract
-Typ als zentrales Sprachkonstrukt. Contracts entsprechen in etwa Klassen in objektorientierten Programmiersprachen. So bündeln Contracts Zustandsdaten und Funktionen und schirmen diese von der Außenwelt ab. Dabei unterstützen Contracts multiple Vererbung, wie aus Python oder C++ bekannt.
Contracts beginnen für gewöhnlich mit einer pragma
-Zeile, die die zulässige Solidity-Version angibt, gefolgt von der eigentlichen Definition:
// 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;
}
}
soliditySmart Contracts können Zustandsdaten und Funktionen definieren. Wie aus C++ und Java bekannt, lässt sich jeweils eine von drei Zugriffsstufen festlegen:
-
public
: Auf die Variable kann von innerhalb des Contracts lesend und schreibend zugegriffen werden. Zusätzlich wird automatisch eineview
-Funktion als Getter für den lesenden Zugriff von außen erzeugt. -
internal
: Die Variable ist von jeglichem äußeren Zugriff abgeschirmt. Von innerhalb des Contracts sowie aus erbenden Contracts kann lesend und schreibend zugegriffen werden. -
private
: wieinternal
, jedoch besteht kein Zugriff aus erbenden Contracts
Funktionen lassen sich ferner als external
auszeichnen. Eine external
-Funktion fungiert als Teil der Contract-Schnittstelle 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
}
solidityModifiers in Solidity
Mit den Modifiers existiert ein interessantes Sprachkonstrukt in Solidity. Die Funktionalität erinnert an Pythons Decorators; wie dort kommen Modifiers zum Einsatz, um den Aufruf einer Funktion zu modifizieren. Sie werden häufig genutzt, um sicherzustellen, 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;
}
}
solidityTransaktions-Management mit Solidity
Solidity verfügt über ein eingebautes Transaktions-Management. So lässt sich sicherstellen, dass ein Ether-Transfer entweder komplett oder gar nicht abgewickelt wird. Die Sprache kennt dafür das spezielle revert
-Schlüsselwort, das ein „roll-back“ einer Transaktion auslöst. Per error
-Schlüsselwort lassen sich eigene Fehlercodes definieren:
// 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
}
}
solidityEin weiteres häufig anzutreffendes 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