Vai al contenuto principale
Change page

Anatomia dei contratti intelligenti

Ultimo aggiornamento della pagina: 23 febbraio 2026

Un contratto intelligente è un programma che viene eseguito a un indirizzo su Ethereum. Sono costituiti da dati e funzioni che possono essere eseguiti alla ricezione di una transazione. Ecco una panoramica di ciò che compone un contratto intelligente.

Prerequisiti

Assicurati di aver prima letto dei contratti intelligenti. Questo documento presuppone che tu abbia già familiarità con linguaggi di programmazione come JavaScript o Python.

Dati

Qualsiasi dato del contratto deve essere assegnato a una posizione: storage o memory. Modificare lo storage in un contratto intelligente è costoso, quindi devi considerare dove dovrebbero risiedere i tuoi dati.

Storage

I dati persistenti sono definiti storage e sono rappresentati da variabili di stato. Questi valori vengono archiviati in modo permanente sulla blockchain. Devi dichiararne il tipo in modo che il contratto possa tenere traccia di quanto spazio di archiviazione sulla blockchain necessita durante la compilazione.

1// Esempio in Solidity
2contract SimpleStorage {
3 uint storedData; // Variabile di stato
4 // ...
5}
1# Vyper example
2storedData: int128

Se hai già programmato in linguaggi orientati agli oggetti, probabilmente avrai familiarità con la maggior parte dei tipi. Tuttavia, address dovrebbe esserti nuovo se sei agli inizi con lo sviluppo su Ethereum.

Un tipo address può contenere un indirizzo Ethereum, che equivale a 20 byte o 160 bit. Viene restituito in notazione esadecimale con un 0x iniziale.

Altri tipi includono:

  • booleani
  • interi
  • numeri a virgola fissa
  • array di byte a dimensione fissa
  • array di byte a dimensione dinamica
  • letterali razionali e interi
  • letterali di stringa
  • letterali esadecimali
  • enumerazioni (enum)

Per ulteriori spiegazioni, dai un'occhiata alla documentazione:

Memory

I valori che vengono archiviati solo per la durata dell'esecuzione di una funzione del contratto sono chiamati variabili di memoria (memory). Poiché non vengono archiviati in modo permanente sulla blockchain, sono molto più economici da usare.

Scopri di più su come l'EVM archivia i dati (Storage, Memory e Stack) nella documentazione di Solidity (opens in a new tab).

Variabili d'ambiente

Oltre alle variabili che definisci nel tuo contratto, ci sono alcune variabili globali speciali. Sono utilizzate principalmente per fornire informazioni sulla blockchain o sulla transazione corrente.

Esempi:

ProprietàVariabile di statoDescrizione
block.timestampuint256Timestamp dell'epoca del blocco corrente
msg.senderaddressMittente del messaggio (chiamata corrente)

Funzioni

Nei termini più semplici, le funzioni possono ottenere informazioni o impostare informazioni in risposta alle transazioni in entrata.

Esistono due tipi di chiamate di funzione:

  • internal – queste non creano una chiamata EVM
    • Le funzioni interne e le variabili di stato possono essere accessibili solo internamente (cioè, dall'interno del contratto corrente o dai contratti che ne derivano)
  • external – queste creano una chiamata EVM
    • Le funzioni esterne fanno parte dell'interfaccia del contratto, il che significa che possono essere chiamate da altri contratti e tramite transazioni. Una funzione esterna f non può essere chiamata internamente (cioè, f() non funziona, ma this.f() funziona).

Possono anche essere public o private

  • le funzioni public possono essere chiamate internamente dall'interno del contratto o esternamente tramite messaggi
  • le funzioni private sono visibili solo per il contratto in cui sono definite e non nei contratti derivati

Sia le funzioni che le variabili di stato possono essere rese pubbliche o private

Ecco una funzione per aggiornare una variabile di stato in un contratto:

1// Esempio in Solidity
2function update_name(string value) public {
3 dapp_name = value;
4}
  • Il parametro value di tipo string viene passato alla funzione: update_name
  • È dichiarata public, il che significa che chiunque può accedervi
  • Non è dichiarata view, quindi può modificare lo stato del contratto

Funzioni View

Queste funzioni promettono di non modificare lo stato dei dati del contratto. Esempi comuni sono le funzioni "getter": potresti usarle per ricevere il saldo di un utente, ad esempio.

1// Esempio in Solidity
2function balanceOf(address _owner) public view returns (uint256 _balance) {
3 return ownerPizzaCount[_owner];
4}
1dapp_name: public(String[24])
2
3@external
4@view
5def readName() -> String[24]:
6 return self.dapp_name

Cosa è considerato una modifica dello stato:

  1. Scrivere su variabili di stato.
  2. Emettere eventi (opens in a new tab).
  3. Creare altri contratti (opens in a new tab).
  4. Usare selfdestruct.
  5. Inviare ether tramite chiamate.
  6. Chiamare qualsiasi funzione non contrassegnata come view o pure.
  7. Usare chiamate di basso livello.
  8. Usare assembly inline che contiene determinati opcode.

Funzioni Constructor

Le funzioni constructor vengono eseguite solo una volta quando il contratto viene distribuito per la prima volta. Come il constructor in molti linguaggi di programmazione basati su classi, queste funzioni spesso inizializzano le variabili di stato ai loro valori specificati.

1// Esempio in Solidity
2// Inizializza i dati del contratto, impostando l'`owner`
3// all'indirizzo del creatore del contratto.
4constructor() public {
5 // Tutti gli smart contract si basano su transazioni esterne per attivare le proprie funzioni.
6 // `msg` è una variabile globale che include dati rilevanti sulla transazione data,
7 // come l'indirizzo del mittente e il valore in ETH incluso nella transazione.
8 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
9 owner = msg.sender;
10}
Mostra tutto
1# Vyper example
2@external
3def __init__(_beneficiary: address, _bidding_time: uint256):
4 self.beneficiary = _beneficiary
5 self.auctionStart = block.timestamp
6 self.auctionEnd = self.auctionStart + _bidding_time

Funzioni integrate

Oltre alle variabili e alle funzioni che definisci nel tuo contratto, ci sono alcune funzioni integrate speciali. L'esempio più ovvio è:

  • address.send() – Solidity
  • send(address) – Vyper

Queste consentono ai contratti di inviare ETH ad altri account.

Scrivere funzioni

La tua funzione necessita di:

  • variabile e tipo del parametro (se accetta parametri)
  • dichiarazione di internal/external
  • dichiarazione di pure/view/payable
  • tipo di ritorno (se restituisce un valore)
1@external
2def update_name(value: String[24]):
3 self.dapp_name = value

Un contratto completo potrebbe assomigliare a questo. Qui la funzione constructor fornisce un valore iniziale per la variabile dapp_name.

Eventi e log

Gli eventi consentono al tuo contratto intelligente di comunicare con il tuo frontend o altre applicazioni iscritte. Una volta che una transazione è convalidata e aggiunta a un blocco, i contratti intelligenti possono emettere eventi e registrare informazioni, che il frontend può quindi elaborare e utilizzare.

Esempi annotati

Questi sono alcuni esempi scritti in Solidity. Se desideri giocare con il codice, puoi interagirvi in Remix (opens in a new tab).

Hello world

1// Specifica la versione di Solidity, usando il versionamento semantico.
2// Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragma solidity ^0.5.10;
4
5// Definisce un contratto chiamato `HelloWorld`.
6// Un contratto è una raccolta di funzioni e dati (il suo stato).
7// Una volta distribuito, un contratto risiede a un indirizzo specifico sulla blockchain di Ethereum.
8// Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contract HelloWorld {
10
11 // Dichiara una variabile di stato `message` di tipo `string`.
12 // Le variabili di stato sono variabili i cui valori sono memorizzati in modo permanente nell'archiviazione del contratto.
13 // La parola chiave `public` rende le variabili accessibili dall'esterno di un contratto
14 // e crea una funzione che altri contratti o client possono chiamare per accedere al valore.
15 string public message;
16
17 // Similmente a molti linguaggi orientati agli oggetti basati su classi, un costruttore è
18 // una funzione speciale che viene eseguita solo alla creazione del contratto.
19 // I costruttori sono usati per inizializzare i dati del contratto.
20 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
21 constructor(string memory initMessage) public {
22 // Accetta un argomento stringa `initMessage` e imposta il valore
23 // nella variabile di archiviazione `message` del contratto).
24 message = initMessage;
25 }
26
27 // Una funzione pubblica che accetta un argomento stringa
28 // e aggiorna la variabile di archiviazione `message`.
29 function update(string memory newMessage) public {
30 message = newMessage;
31 }
32}
Mostra tutto

Token

1pragma solidity ^0.5.10;
2
3contract Token {
4 // Un `address` è paragonabile a un indirizzo email: è usato per identificare un account su Ethereum.
5 // Gli indirizzi possono rappresentare uno smart contract o account esterni (di utenti).
6 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/types.html#address
7 address public owner;
8
9 // Un `mapping` è essenzialmente una struttura dati a tabella hash.
10 // Questo `mapping` assegna un intero senza segno (il saldo del token) a un indirizzo (il detentore del token).
11 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types
12 mapping (address => uint) public balances;
13
14 // Gli eventi consentono di registrare le attività sulla blockchain.
15 // I client di Ethereum possono ascoltare gli eventi per reagire ai cambiamenti di stato del contratto.
16 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events
17 event Transfer(address from, address to, uint amount);
18
19 // Inizializza i dati del contratto, impostando l'`owner`
20 // all'indirizzo del creatore del contratto.
21 constructor() public {
22 // Tutti gli smart contract si basano su transazioni esterne per attivare le proprie funzioni.
23 // `msg` è una variabile globale che include dati rilevanti sulla transazione data,
24 // come l'indirizzo del mittente e il valore in ETH incluso nella transazione.
25 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
26 owner = msg.sender;
27 }
28
29 // Crea una quantità di nuovi token e li invia a un indirizzo.
30 function mint(address receiver, uint amount) public {
31 // `require` è una struttura di controllo usata per imporre determinate condizioni.
32 // Se un'istruzione `require` restituisce `false`, viene attivata un'eccezione,
33 // che annulla tutte le modifiche apportate allo stato durante la chiamata corrente.
34 // Scopri di più: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
35
36 // Solo il proprietario del contratto può chiamare questa funzione
37 require(msg.sender == owner, "You are not the owner.");
38
39 // Impone una quantità massima di token
40 require(amount < 1e60, "Maximum issuance exceeded");
41
42 // Aumenta il saldo di `receiver` di `amount`
43 balances[receiver] += amount;
44 }
45
46 // Invia una quantità di token esistenti da qualsiasi chiamante a un indirizzo.
47 function transfer(address receiver, uint amount) public {
48 // Il mittente deve avere abbastanza token da inviare
49 require(amount <= balances[msg.sender], "Insufficient balance.");
50
51 // Regola i saldi dei token dei due indirizzi
52 balances[msg.sender] -= amount;
53 balances[receiver] += amount;
54
55 // Emette l'evento definito in precedenza
56 emit Transfer(msg.sender, receiver, amount);
57 }
58}
Mostra tutto

Risorsa digitale unica

1pragma solidity ^0.5.10;
2
3// Imports symbols from other files into the current contract.
4// In this case, a series of helper contracts from OpenZeppelin.
5// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files
6
7import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
8import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
9import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";
10import "../node_modules/@openzeppelin/contracts/utils/Address.sol";
11import "../node_modules/@openzeppelin/contracts/drafts/Counters.sol";
12import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";
13
14// The `is` keyword is used to inherit functions and keywords from external contracts.
15// In this case, `SimpleToken` inherits from the `ERC165` and `IERC721` contracts.
16// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance
17contract SimpleToken is ERC165, IERC721 {
18
19 using SafeMath for uint256;
20 using Address for address;
21 using Counters for Counters.Counter;
22
23 // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
24 // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
25 bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
26
27 // Mapping from token ID to owner
28 mapping (uint256 => address) private _tokenOwner;
29
30 // Mapping from token ID to approved address
31 mapping (uint256 => address) private _tokenApprovals;
32
33 // Mapping from owner to number of owned token
34 mapping (address => Counters.Counter) private _ownedTokensCount;
35
36 // Mapping from owner to operator approvals
37 mapping (address => mapping (address => bool)) private _operatorApprovals;
38
39 /*
40 * bytes4(keccak256('balanceOf(address)')) == 0x70a08231
41 * bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
42 * bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
43 * bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
44 * bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
45 * bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c5
46 * bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
47 * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
48 * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
49 *
50 * => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
51 * 0xa22cb465 ^ 0xe985e9c5 ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
52 */
53 bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
54
55 constructor () public {
56 // register the supported interfaces to conform to ERC721 via ERC165
57 _registerInterface(_INTERFACE_ID_ERC721);
58 }
59
60 function balanceOf(address owner) public view returns (uint256) {
61 require(owner != address(0), "ERC721: balance query for the zero address");
62
63 return _ownedTokensCount[owner].current();
64 }
65
66 function ownerOf(uint256 tokenId) public view returns (address) {
67 address owner = _tokenOwner[tokenId];
68 require(owner != address(0), "ERC721: owner query for nonexistent token");
69
70 return owner;
71 }
72
73 function approve(address to, uint256 tokenId) public {
74 address owner = ownerOf(tokenId);
75 require(to != owner, "ERC721: approval to current owner");
76
77 require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
78 "ERC721: approve caller is not owner nor approved for all"
79 );
80
81 _tokenApprovals[tokenId] = to;
82 emit Approval(owner, to, tokenId);
83 }
84
85 function getApproved(uint256 tokenId) public view returns (address) {
86 require(_exists(tokenId), "ERC721: approved query for nonexistent token");
87
88 return _tokenApprovals[tokenId];
89 }
90
91 function setApprovalForAll(address to, bool approved) public {
92 require(to != msg.sender, "ERC721: approve to caller");
93
94 _operatorApprovals[msg.sender][to] = approved;
95 emit ApprovalForAll(msg.sender, to, approved);
96 }
97
98 function isApprovedForAll(address owner, address operator) public view returns (bool) {
99 return _operatorApprovals[owner][operator];
100 }
101
102 function transferFrom(address from, address to, uint256 tokenId) public {
103 //solhint-disable-next-line max-line-length
104 require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
105
106 _transferFrom(from, to, tokenId);
107 }
108
109 function safeTransferFrom(address from, address to, uint256 tokenId) public {
110 safeTransferFrom(from, to, tokenId, "");
111 }
112
113 function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
114 transferFrom(from, to, tokenId);
115 require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
116 }
117
118 function _exists(uint256 tokenId) internal view returns (bool) {
119 address owner = _tokenOwner[tokenId];
120 return owner != address(0);
121 }
122
123 function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
124 require(_exists(tokenId), "ERC721: operator query for nonexistent token");
125 address owner = ownerOf(tokenId);
126 return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
127 }
128
129 function _mint(address to, uint256 tokenId) internal {
130 require(to != address(0), "ERC721: mint to the zero address");
131 require(!_exists(tokenId), "ERC721: token already minted");
132
133 _tokenOwner[tokenId] = to;
134 _ownedTokensCount[to].increment();
135
136 emit Transfer(address(0), to, tokenId);
137 }
138
139 function _burn(address owner, uint256 tokenId) internal {
140 require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");
141
142 _clearApproval(tokenId);
143
144 _ownedTokensCount[owner].decrement();
145 _tokenOwner[tokenId] = address(0);
146
147 emit Transfer(owner, address(0), tokenId);
148 }
149
150 function _burn(uint256 tokenId) internal {
151 _burn(ownerOf(tokenId), tokenId);
152 }
153
154 function _transferFrom(address from, address to, uint256 tokenId) internal {
155 require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
156 require(to != address(0), "ERC721: transfer to the zero address");
157
158 _clearApproval(tokenId);
159
160 _ownedTokensCount[from].decrement();
161 _ownedTokensCount[to].increment();
162
163 _tokenOwner[tokenId] = to;
164
165 emit Transfer(from, to, tokenId);
166 }
167
168 function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
169 internal returns (bool)
170 {
171 if (!to.isContract()) {
172 return true;
173 }
174
175 bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
176 return (retval == _ERC721_RECEIVED);
177 }
178
179 function _clearApproval(uint256 tokenId) private {
180 if (_tokenApprovals[tokenId] != address(0)) {
181 _tokenApprovals[tokenId] = address(0);
182 }
183 }
184}
Mostra tutto

Letture di approfondimento

Dai un'occhiata alla documentazione di Solidity e Vyper per una panoramica più completa sui contratti intelligenti:

Questo articolo è stato utile?