Solidity: linguaggio di programmazione per contratti intelligenti
Solidity può essere utilizzato per programmare sofisticati contratti intelligenti da utilizzare sulla blockchain Ethereum. Offre alcuni approcci interessanti che lo distinguono da altri linguaggi.
Cos’è Solidity?
Solidity è un linguaggio di programmazione di alto livello per la creazione di contratti intelligenti sulla blockchain Ethereum. I contratti intelligenti (in inglese: “smart contracts”) sono accordi auto-esecutivi che automatizzano lo scambio di beni tra le parti. Ciò che li rende speciali è che non sono necessari intermediari per garantire il rispetto di tale contratto.
Il codice sorgente di Solidity viene compilato in bytecode e messo a disposizione sulla blockchain Ethereum come contratto intelligente. Dopodiché quest’ultimo può essere eseguito da qualsiasi nodo della rete e lo stato viene memorizzato sulla blockchain. Di seguito ti mostriamo un esempio di un semplice contratto che modella un distributore automatico di NFT:
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;
}
}
solidityPer quali applicazioni viene utilizzato Solidity?
Solidity è specificamente progettato per la creazione di applicazioni distribuite, DApp (“Distributed Applications”), in esecuzione sulla macchina virtuale di Ethereum (EVM). I contratti intelligenti sono adatti, tra l’altro, alla gestione di beni digitali, alla creazione di scambi decentralizzati e all’implementazione di sistemi di voto.
- Certificato SSL e protezione DDoS
- Velocità, flessibilità e scalabilità
- Dominio e consulente personale
- 1 anno gratis del gestionale di fatturazione elettronica FlexTax
Finanza decentralizzata (DeFi)
Solidity viene utilizzato per sviluppare applicazioni DeFi come borse decentralizzate, piattaforme di scambio e di prestiti, mercati di previsione e criptovalute. La DeFi è diventata uno dei casi d’uso più popolari per la tecnologia blockchain, rendendo Solidity uno strumento indispensabile per costruire applicazioni DeFi sulla rete Ethereum.
Non-Fungible Token
I Non-Fungible Token (NFT), in italiano “gettone non fungibile”, sono beni digitali unici memorizzati sulla blockchain, diventati molto popolari a partire dal 2020. Possono essere opere d’arte digitali, cimeli sportivi o manufatti nei videogiochi. Solidity viene utilizzato per creare smart contract che alimentano gli NFT.
Gestione della catena di approvvigionamento
Solidity può essere utilizzato per creare contratti intelligenti per il monitoraggio e la gestione delle catene di approvvigionamento. I contratti vengono utilizzati per automatizzare vari processi di questa catena. Tra questi rientrano il monitoraggio del movimento delle merci, la verifica dell’autenticità dei prodotti e l’elaborazione dei pagamenti tra le parti.
Sistemi di voto
Grazie a Solidity si creano contratti intelligenti che implementano sistemi di voto sicuri e trasparenti sulla blockchain. Questi ultimi possono essere utilizzati per garantire che i voti siano contati correttamente e che il processo di voto sia equo e trasparente.
Quali sono i vantaggi e gli svantaggi di Solidity?
Nel complesso, Solidity è un linguaggio potente per creare contratti intelligenti sulla blockchain di Ethereum. Tuttavia, come ogni tecnologia, vi sono vantaggi e svantaggi specifici di cui gli sviluppatori e le sviluppatrici dovrebbero essere consapevoli. In ogni caso, lo sviluppo di contratti intelligenti sicuri richiede un certo livello di competenza e attenzione.
A titolo di esempio, ti mostriamo uno smart contract che agisce come un buco nero: qualsiasi Ether inviato al contratto viene inghiottito irrimediabilmente; non c’è quindi alcuna possibilità di versare nuovamente gli Ether:
// 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);
}
}
solidityVantaggi di Solidity
- Flessibilità: Solidity è un linguaggio versatile. Può essere utilizzato per sviluppare contratti intelligenti molto diversi tra loro e con una varietà di casi d’uso.
- Sicurezza: Solidity è stato progettato tenendo conto della sicurezza. Il linguaggio include caratteristiche come i controlli di accesso, la gestione delle eccezioni e i meccanismi contro i malfunzionamenti per aiutare gli sviluppatori e le sviluppatrici a scrivere contratti sicuri.
- Compatibilità con Ethereum: Solidity è il linguaggio attualmente prioritario per la creazione di contratti intelligenti sulla blockchain di Ethereum.
- Community ben affermata: intorno a Solidity esiste una grande community di sviluppatori e sviluppatrici di blockchain. Ciò significa che esistono numerose risorse per imparare e risolvere i problemi.
Svantaggi di Solidity
- Fase di apprendimento: Solidity richiede un certo tempo di apprendimento per gli sviluppatori e le sviluppatrici che si affacciano per la prima volta allo sviluppo di blockchain e smart contract.
- Invariabilità: dopo che un contratto intelligente viene distribuito sulla blockchain, non può essere ulteriormente modificato. Ne consegue che gli sviluppatori e le sviluppatrici devono prestare estrema attenzione durante la scrittura e la fase di test.
- Verifica formale mancante: Solidity non dispone di strumenti integrati per la revisione formale del codice. Ciò significa che gli sviluppatori e le sviluppatrici devono affidarsi a strumenti esterni per garantire la correttezza dei loro contratti.
- Strumenti limitati: l’ecosistema di strumenti di Solidity è ancora relativamente poco sviluppato. Ne conseguono pertanto problemi con IDE, framework di test e altri strumenti di sviluppo.
Qual è la sintassi di base di Solidity?
Solidity è un linguaggio orientato agli oggetti specializzato in contratti intelligenti e influenzato da JavaScript, Python e C++. La sintassi del linguaggio è simile a quella di JavaScript, anche se ci sono alcune interessanti peculiarità.
Variabili in Solidity
A prima vista, le variabili sembrano funzionare in Solidity allo stesso modo dei linguaggi analoghi. Tuttavia, un’importante differenza deriva dal fatto che, come ambiente di esecuzione, viene utilizzata la Ethereum Virtual Machine (EVM). Tutte le operazioni sulla EVM e la memorizzazione dei dati costano una certa quantità di “gas”. Ciò significa che, in fase di programmazione, può essere necessario valutare come implementare un’operazione nel modo più efficiente possibile.
Oltre alle variabili “normali”, Solidity conosce delle costanti che devono essere definite durante la compilazione. Queste ultime richiedono meno energia per la memorizzazione:
// Regular variable can be declared without defining
int a;
// Constant needs to be defined at declaration
int constant b = 51;
solidityLa situazione è simile con le variabili immutable
. Anche queste consumano meno energia e non permettono di essere modificate dopo l’assegnazione. A differenza delle variabili constant
, l’assegnazione può avvenire in fase di esecuzione.
Dichiarazioni di controllo in Solidity
In qualità di linguaggio di programmazione imperativo, Solidity supporta le note istruzioni di controllo, come rami e cicli. Ti mostriamo il codice per selezionare il più grande tra due numeri, a
e b
:
int largerNumber = 0;
// If-else statement
if (a > b) {
largerNumber = a;
} else {
largerNumber = b;
}
solidityIl ciclo for
in Solidity corrisponde alla sintassi nota di JavaScript o C++:
// Loop 10 times
for (int i = 0; i < 10; i++) {
// …
}
solidityAnche il ciclo while
funziona come al solito. Combiniamo una condizione di terminazione con una variabile numerica counter:
bool continueLoop = true;
int counter = 0;
// Loop at most 10 times
while (continueLoop && counter < 10) {
// …
counter++;
}
soliditySolidity è un linguaggio a tipizzazione statica e supporta i tipi comunemente presenti nei linguaggi di programmazione. I tipi semplici che rappresentano valori singoli includono booleani, numeri e stringhe.
I booleani in Solidity sono rappresentati dai valori true
e false
. Possono essere collegati tramite i relativi operatori booleani noti e utilizzati nelle istruzioni if
:
bool paymentReceived = true;
bool itemsStocked = true;
bool continueTransaction = paymentReceived && itemsStocked;
if (continueTransaction) {
// ...
}
soliditySolidity supporta un’ampia gamma di tipi numerici. Nel caso dei numeri interi, si può fare una distinzione tra numeri firmati (int
) e non firmati (uint
), dove questi ultimi possono essere solo positivi. Inoltre, l’intervallo di un numero può essere definito in passi di 8 bit, da int8
passando per int16
fino a int265
:
uint8 smallNumber = 120;
int8 negativeNumber = -125;
int8 result = smallNumber + negativeNumber;
assert(result == -5)
solidityLe stringhe sono usate principalmente in Solidity per creare messaggi di stato. Il linguaggio supporta gli apici singoli e doppi e i caratteri Unicode:
string message = 'Hello World';
string success = unicode"Transfer sent";
SolidityFunzioni in Solidity
Come nella maggior parte dei linguaggi di programmazione, le funzioni sono una parte centrale di Solidity. La definizione di una funzione ricorda JavaScript, dove i tipi di argomenti devono essere specificati esplicitamente. Inoltre, viene utilizzata la parola chiave returns
per indicare i tipi dei valori di ritorno:
// Define a function
function addNumbers(int a, int b) returns (int) {
return a + b;
}
solidityLa chiamata di una funzione viene eseguita come di consueto:
// Call the function
int result = addNumbers(2, 3);
solidityÈ interessante notare che analogamente agli argomenti nominati, anche i valori di ritorno possono essere nominati. In questo caso, è sufficiente assegnare le variabili corrispondenti nel corpo della funzione. Non è quindi necessario un ritorno esplicito tramite return
:
function divideNumbers(int dividend, int divisor) returns (int quotient) {
quotient = dividend / divisor;
// No `return` necessary
}
solidityIn maniera simile alle variabili constant
o immutable
, in Solidity le funzioni possono essere contrassegnate come non mutevoli. Qui si usano le parole chiave view
e pure
. Una funzione view
non cambia lo stato, mentre pure
garantisce di non leggere le variabili di stato.
Contratti intelligenti in Solidity
Oltre ai tipi comuni, Solidity conosce una manciata di tipologie specializzate per gli smart contract. Il tipo di base è address
e mappa gli indirizzi Ethereum. Gli indirizzi che sono payable
possono ricevere trasferimenti in Ether. A tal fine, gli indirizzi payable
forniscono i metodi balance()
e 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);
}
solidityPartendo dal tipo address
, esiste il tipo contract
come costrutto centrale del linguaggio. I contratti corrispondono all’incirca alle classi dei linguaggi di programmazione orientati agli oggetti. Pertanto, i contratti raggruppano i dati di stato e le funzioni, proteggendoli dal mondo esterno. In più, supportano l’ereditarietà multipla, come quella conosciuta in Python o C++.
Di solito iniziano con una riga pragma
che specifica la versione di Solidity consentita, seguita dalla definizione vera e propria:
// 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;
}
}
solidityI contratti intelligenti possono definire dati e funzioni di stato. Come noto da C++ e Java, in ogni caso è possibile definire uno dei tre livelli di accesso:
-
public
: la variabile è accessibile dall’interno del contratto in lettura e scrittura. Inoltre, viene automaticamente creata una funzioneview
come getter per l’accesso in lettura dall’esterno. -
internal
: la variabile è protetta da qualsiasi accesso esterno. L’accesso in lettura e scrittura è possibile dall’interno del contratto e da quelli ereditati. -
private
: comeinternal
, ma non c’è accesso da parte dei contratti ereditari.
Le funzioni possono anche essere definite esterne. Una funzione external
agisce come parte dell’interfaccia del contratto e viene utilizzata per l’accesso dall’esterno. L’importante funzione receive
per la ricezione di Ether è un esempio ben noto:
// Define without `function` keyword
receive() external payable {
// Handle Ether
}
solidityModificatori in Solidity
Con i modificatori (“modifiers” in inglese), in Solidity esiste un interessante costrutto linguistico. La funzionalità ricorda i decoratori di Python; come in quel caso, i modificatori sono usati per modificare la chiamata di una funzione e per garantire che una condizione sia soddisfatta prima di eseguire una funzione:
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;
}
}
solidityGestione delle transazioni con Solidity
Solidity dispone di una gestione delle transazioni integrata. Ciò consente di garantire che un trasferimento di dati sia elaborato completamente o non sia elaborato affatto. Il linguaggio conosce la parola chiave speciale revert
, che attiva un “roll-back” di una transazione. La parola chiave error
può essere usata per definire i propri codici di errore:
// 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
}
}
solidityUn altro schema frequente è riconducibile all’uso della funzione require()
, che può essere usata in modo analogo a revert
:
// Using `require()` function
if (!condition) revert("Error message");
// Equivalent to
require(condition, "Error message");
solidity