1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
|
---
language: Solidity
filename: learnSolidity.sol
contributors:
- ["Nemil Dalal", "https://www.nemil.com"]
- ["Joseph Chow", ""]
- ["Bhoomtawath Plinsut", "https://github.com/varshard"]
- ["Shooter", "https://github.com/liushooter"]
- ["Patrick Collins", "https://gist.github.com/PatrickAlphaC"]
translators:
- ["Al", "http://github.com/al-ias"]
lang: it-it
---
Solidity permette di programmare su [Ethereum](https://www.ethereum.org/), una
macchina virtuale basata sulla blockchain che consente la creazione e
l'esecuzione degli smart contract senza che sia richiesta centralizzazione o
fiducia negli attori coinvolti.
Solidity è un linguaggio di programmazione di contratti tipizzato staticamente e
ha molte cose in comune con Javascript e C. Come per gli oggetti nella
programmazione ad oggetti, ogni contratto contiene variabili di stato, funzioni
e tipi di dato semplici. Tra le funzionalità specifiche dei contratti troviamo
le clausole (guardie) dei modifier, gli event notifier per i listener, e le
variabili globali custom.
Come esempi di contratti su Ethereum troviamo sistemi di crowdfunding, voto,
[finanza decentralizzata](https://defipulse.com/) e aste al buio.
Compiere errori nel codice Solidity può portare a rischi e costi alti, quindi
bisogna fare attenzione a testare e rilasciare le modifiche lentamente. A
CAUSA DEI CONTINUI CAMBIAMENTI DI ETHEREUM È IMPROBABILE CHE QUESTO DOCUMENTO
RESTI AGGIORNATO, QUINDI COSNIGLIAMO DI SEGUIRE LA CHAT ROOM DI SOLIDITY E IL
BLOG DI ETHEREUM PER TENERSI AGGIORNATI. TUTTO IL CODICE QUI PRESENTE E' FORNITO
COSÌ COM'È, CON ANNESSI RISCHI SOSTANZIALI DI ERRORI O PATTERN DI PROGRAMMAZIONE
DEPRECATI.
A differenza di altri tipi di codice, potresti aver bisogno di usare pattern di
pausing, deprecation e throttling usage per ridurre il rischio. Questo documento
tratta principalmene la sintassi e quindi esclude molti design pattern in voga.
Visto che Solidity e Ethereum sono in continuo sviluppo, le funzionalità
sperimentali o beta sono evidenziate e soggette a cambiamenti. Ogni Pull Request
è ben accetta.
# Lavorare con Remix e Metamask
Uno dei modi più semplici di scrivere, distribuire e testare il codice Solidity
è usare :
1. [L'ambiente di sviluppo online Remix](https://remix.ethereum.org/)
2. [Il wallet Metamask](https://metamask.io/).
Per cominciare, [scarichiamo l'estesione per browser di Metamask](https://metamask.io/).
Una volta installata, potremo iniziare ad usare Remix. Il codice seguente è
pre-inizializzato, ma prima di addentrarci, diamo un'occhiata a qualche
trucchetto per iniziare ad usare Remix. Carica tutto il necessario [clickando su questo link](https://remix.ethereum.org/#version=soljson-v0.6.6+commit.6c089d02.js&optimize=false&evmVersion=null&gist=f490c0d51141dd0515244db40bbd0c17&runs=200).
1. Scegli il compilatore per Solidity
![Solidity-in-remix](../images/solidity/remix-solidity.png)
2. Apri il file che si caricherà su quel link
![Solidity-choose-file](../images/solidity/remix-choose-file.png)
3. Compila il file
![Solidity-compile](../images/solidity/remix-compile.png)
4. Fai il deploy
![Solidity-deploy](../images/solidity/remix-deploy.png)
5. Smanetta con i contratti
![Solidity-deploy](../images/solidity/remix-interact.png)
Hai distribuito il tuo primo contrattto! Congratulazioni!
Potrai testarlo e smanettare con le funzioni già definite. Dai un'occhiata ai
commenti per scoprire cosa fanno.
## Lavorare su una testnet
Distribuire e testare su una testnet è il modo più accurato per mettere alla
prova i tuoi smart contract in Solidity. Per farlo procuriamoci prima degli ETH
di test dalla testnet Kovan.
[Entra in questo Gitter Channel](https://gitter.im/kovan-testnet/faucet) e
scrivici l'indirizzo del tuo wallet Metamask.
Sul tuo Metamask, dovrai cambiare la testnet in `Kovan`.
![Solidity-in-remix](../images/solidity/metamask-kovan.png)
Riceverai degli Ethereum di test gratuiti. Per distribuire degli smart contract
su una testnet abbiamo bisogno di Ethereum.
Nell'esempio precedente non avevamo usato una testnet, ma avevamo distribuito
su un ambiente virtuale fittizio. Quando si lavora su una testnet, possiamo
davvero monitorare e interagire con i nostri contratti in maniera persistente.
Per distribuire su una testnet, allo step `#4 Fai il deploy`, cambia
l'`environment` selezionato in `injected web3`. In questo modo verrà usato
come network su cui fare il deploy qualsiasi network selezionato sul tuo
Metamask.
![Solidity-in-remix](../images/solidity/remix-testnet.png)
Per ora continua a usare la `Javascript VM` a meno che non ti sia detto di
cambiarla. Quando distribuisci su una testnet, Metamask aprirà un pop up che
ti chiederà di "confermare" la transazione. Premi `yes` e dopo un certo lasso
di tempo, ti apparirà la stessa interfaccia per il contratto nella parte
inferiore dello schermo.
```javascript
// Iniziamo con un semplice contratto su una Banca
// Permette di depositare, prelevare e fare l'estratto conto
// simple_bank.sol (nota l'estensione .sol)
/* **** INIZIO DELL'ESEMPIO **** */
// Dichiara la versione del compilatore per il file sorgente
pragma solidity ^0.6.6;
// Inizia con il commento Natspec (i tre slash)
// viene usato per la documentazione - e per i dati descrittivi per gli elementi
// dell'interfaccia utente / azioni
/// @title SimpleBank
/// @author nemild
/* 'contract' somiglia a 'class' in altri linguaggi (ha variabili di classe,
ereditarietà, etc.) */
contract SimpleBank { // CapWords
// Dichiariamo le variabili di stato fuori dalle funzioni, persisteranno
// durante tutta la vita del contratto
// i dizionari mappano gli indirizzi con i saldi
// fai sempre attenzione agli overflow attack che sfruttano i numeri
mapping (address => uint) private balances;
// "private" significa che che altri contratti non possono leggere i
// saldi ma le informazioni restano visibili ad altri attori sulla blockchain
address public owner;
// 'public' lo rende leggibile dall'esterno (ma non modificabile) dagli
// utenti e dai contratti
// Gli 'event' pubblicano le azioni in modo che siano ascoltabili da
// listener esterni
event LogDepositMade(address accountAddress, uint amount);
// I 'constructor' possono ricevere una o più parametri; Si può
// dichiarare un solo costruttore
constructor() public {
// 'msg' fornisce i dettagli sul messaggio che è stato mandato al contratto
// 'msg.sender' è chi invoca il contratto (l'indirizzo di chi lo crea)
owner = msg.sender;
}
/// @notice Deposita ether nella banca
/// @return Il saldo dell'utente dopo che è stato effettualto il deposito
function deposit() public payable returns (uint) {
// Usiamo 'require' per testare gli input dell'utente, 'assert' per gli
// invarianti interni. Qui ci assicuriamo di non avere a che fare con
// un overflow
require((balances[msg.sender] + msg.value) >= balances[msg.sender]);
balances[msg.sender] += msg.value;
// Non servono "this." o "self." con le variabili di stato
// Tutti i valori iniziali delle variabili sono impostati automaticamente
// al valore di default per quel tipo di dato
emit LogDepositMade(msg.sender, msg.value); // Fa scattare l'evento
return balances[msg.sender];
}
/// @notice Preleva ether dalla banca
/// @dev Non restituisce gli ether inviati in eccesso
/// @param withdrawAmount L'importo che si vuole ritirare
/// @return remainingBal
function withdraw(uint withdrawAmount) public returns (uint remainingBal) {
require(withdrawAmount <= balances[msg.sender]);
// Notiamo come per prima cosa scaliamo i soldi dal saldo, prima di
// invarli. Ogni .transfer/.send in questo contratto può chiamare una
// funzione esterna. Questa cosa potrebbe permettere a chi invoca la
// funzione di richiedere un importo maggiore del suo saldo usando
// una chiamata ricorsiva. Miriamo ad aggiornare lo stato prima che sia
// chiamata una funzione esterna, incluse .transfer/.send
balances[msg.sender] -= withdrawAmount;
// Qui lancia automaticamente un errore se fallisce, il che implica
// che il saldo (non più aggiornato) viene ripristinato a prima della
// transazione
msg.sender.transfer(withdrawAmount);
return balances[msg.sender];
}
/// @notice Recupera il saldo
/// @return Il saldo dell'utente
// 'view' (ex: constant) impedisce alle funzioni di modificare lo stato
// delle variabili; consente alle le funzioni di essere disponibili in
// locale/fuori dalla blockchain
function balance() view public returns (uint) {
return balances[msg.sender];
}
}
// ** FINE DELL'ESEMPIO **
// Passiamo alle basi di Solidity
// 1. TIPI DI DATO E I LORO METODI
// uint viene usato per gli importi in valuta (non ci sono double o float)
// e per le date (in unix time)
uint x;
// int di 256 bit, non possono essere modificati dopo l'istanziazione
int constant a = 8;
int256 constant a = 8; // stesso effetto della riga prima, qui viene
// dichiarato esplicitamente che è di 256 bit
uint constant VERSION_ID = 0x123A1; // Una costante esadecimale
// con 'constant', il compilatore rimpiazza ogni occorrenza con il valore
// Tutte le variabili di stato (quelle fuori da una funzione)
// sono 'interne' di default e accessibili SOLO dall'interno del contratto
// e da tutti contratti che le ereditano
// Bisogna usare esplicitamente 'public' per consentire l'accesso dai contratti
// esterni
int256 public a = 8;
// Per int e uint possiamo esplicitamente assegnare una dimensione tra 8 e 256
// es. int8, int16, int24
uint8 b;
int64 c;
uint248 e;
// Attenzione a non andare in overflow, e a proteggersi dagli attacchi che lo fanno
// Ad esempio per quanto rigrada l'addizione, conviene fare:
uint256 c = a + b;
assert(c >= a); // 'assert' testa gli invarianti interni; require viene usato
// per gli input
// Per altri esempi di problemi comuni con le operazioni aritmentiche, dai una
// occhiata alla Zeppelin's SafeMath library
// https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/math/SafeMath.sol
// Non ci sono funzioni random built-in, puoi ottenere un numero pseudo-casuale
// hashando l'ultimo blockhash, o ottenere un numero realmente casuale usando
// qualcosa come Chainlink VRF.
// https://docs.chain.link/docs/get-a-random-number
// Conversione di tipo
int x = int(b);
bool b = true; // oppure 'var b = true;' per l'inferenza di tipo
// Indirizzi - contengono indirizzi Ethereum di 20 byte/160 bit
// Non sono consentite operazioni aritmetiche
address public owner;
// Tipi di account:
// Contract account: l'indirizzo viene impostato quando lo si crea (funzione con
// l'indirzzo di chi lo crea, il numero della transazione inviata)
// External Account: (persona/enitità esterna): l'indirizzo viene creato dala
// chiave pubblica
// Aggiungi il campo 'public' per indicare che è pubblico/accessibile dall'esterno
// un getter viene creato automaticamente, ma NON un setter
// Si possono mandare ether a tutti gli indirizzi
owner.transfer(SOME_BALANCE); // fallisce e, in tal caso, ripristina
// lo stato precedente
// Possiamo anche usare la funzione di livello più basso .send, che restituisce
// false se fallisce
if (owner.send) {} // RICORDA: metti la send in un 'if' dato che gli indirizzi
// usati nei contratti hanno delle funzioni, che vengono eseguite quando viene
// fatta una send, che possono fallire
// Inoltre fai attenzione a scalare i saldi PRIMA di provare a fare una send,
// dato il rischio di chiamate riscorsive che potrebbero prosciugare il contratto
// Possiamo controllare il saldo
owner.balance; // il saldo del propietario (utente o contratto)
// I Byte sono disposibili in dimensioni da 1 a 32
byte a; // 'byte' è la stessa cosa di 'bytes1'
bytes2 b;
bytes32 c;
// Byte con dimensione dinamica
bytes m; // Un array particolare, la stessa cosa dell'array 'byte[]' (ma scritto stringato)
// È più dispendioso di byte1-byte32, che di solito sono preferibili
// come bytes, ma non permette di accedere alla lunghezza o all'indice (per ora)
string n = "hello"; // salvato in UTF8, nota i doppi apici, non singoli
// le utility function per le stringhe saranno aggiunte in futuro
// sono preferibili bytes32/bytes, dato che UTF8 occupa più memoria
// Inferenza di tipo
// 'var' fa inferenza di tipo a seconda del primo assegnamento,
// non può essere usata tra i parametri di una funzione
var a = true;
// da usare con cautela, può inferire un tipo errato
// es. un int8 quando un contatore dev'essere un int16
// var può essere usata per assegnare una funzione ad una variabile
function a(uint x) returns (uint) {
return x * 2;
}
var f = a;
f(22); // chiamata
// di default, tutte le variabili sono impostate a 0 durante l'istanziazione
// Delete può essere chiamato sulla maggior parte dei valori
// (NON distrugge il valore, ma lo setta a 0, il valore did default)
uint x = 5;
// Destructuring/Tuple
(x, y) = (2, 7); // assegna/scambia più valori
// 2. STRUTTURE DATI
// Array
bytes32[5] nicknames; // array statico
bytes32[] names; // array dinamico
uint newLength = names.push("John"); // aggiungere un elemento restituisce
// la nuova dimensione dell'array
// Dimesione
names.length; // ottenere la dimensione
names.length = 1; // la dimensione può essere assegnata (solo per gli array nello storage)
// array multidimensionali
uint[][5] x; // array con 5 array dinamici (ordine opposto rispetto ad
// altri linguaggi)
// Dizionari (da un tipo qualsiasi a un tipo qualsiasi)
mapping (string => uint) public balances;
balances["charles"] = 1;
// il risultato balances["ada"] è 0, tutte le chiavi non settate
// restituiscono zero
// 'public' permette che si possa fare questo da un altro contratto:
contractName.balances("charles"); // restituisce 1
// 'public' ha creato getter (ma non un setter), come il seguente:
function balances(string _account) returns (uint balance) {
return balances[_account];
}
// Mapping annidati
mapping (address => mapping (address => uint)) public custodians;
// Fare una delete
delete balances["John"];
delete balances; // assegna 0 a tutti gli elementi
// Diversamente da altri linguaggi NON si può iterare tra gli elementi di un
// mapping senza conoscere le chiavi - ma si può costruire una struttura dati a monte
// che lo faccia
// Strutture dati
struct Bank {
address owner;
uint balance;
}
Bank b = Bank({
owner: msg.sender,
balance: 5
});
// oppure
Bank c = Bank(msg.sender, 5);
c.balance = 5; // imposta ad un nuovo valore
delete b;
// reimposta, imposta tutte le variabili della struttura a 0, tranne i mapping
// Enumerazioni
enum State { Created, Locked, Inactive }; // di solito si usano per le macchine di stato
State public state; // Dichiara una variabile da un enum
state = State.Created;
// Le enum possono essere convertite esplicitamente in int
uint createdState = uint(State.Created); // 0
// Data location: Memory vs. storage vs. calldata - tutti i tipi complessi
// (array, struct) hanno una data location
// 'memory' non è persistente, 'storage' sì
// Il default è 'storage' per varibili locali e di stato;
// 'memory' per i parametri delle funzioni
// Lo stack può contenere poche varaibili locali
// Per la maggior parte dei tipi, si può impostare esplicitamente
// quale data location usare
// 3. Operatori semplici
// Ci sono operatori logici, a bit e aritmetici
// Potenza: **
// Or esclusivo: ^
// Negazione bitwise: ~
// 4. Variabili globali degne di nota
// ** this **
this; // indirizzo del contratto
// di solito si usa per trasferire il aldo rimanente altrove
// al termine della vita del contratto
this.balance;
this.someFunction(); // invoca una funzione esterna tramite chiamata,
// non attraverso un salto interno
// ** msg - Il messaggio corrente ricevuto dal contratto **
msg.sender; // indirizzo di chi ha inviato msg
msg.value; // l'importo di ether forniti a questo contratto espresso in "wei",
// la funzione dovrebbe essere marcata come "payable"
msg.data; // in bytes, tutti gli argomenti del messaggio
msg.gas; // 'gas' restante
// ** tx - Questa transazione **
tx.origin; // l'indirizzo di chi ha avviato questa transazione
tx.gasprice; // il prezzo del "gas" per la transazione
// ** block - Informazioni sul blocco attuale **
now; // ora corrente (approssimatamente), alias di block.timestamp (in Unix time)
// Da notare come può essere manipolata dai miner, quindi da usare con cautela
block.number; // numero del blocco attuale
block.difficulty; // difficulty del blocco attuale
block.blockhash(1); // restituisce un bytes32, funziona solo per i 256 blocchi
// più recenti
block.gasLimit();
// ** storage - Memoria persistente (in hash) **
storage['abc'] = 'def'; // mappa da parole di 256 bit a parole di 256 bit
// 4. FUNZIONI E ALTRO
// A. Funzioni
// Una semplice funzione
function increment(uint x) returns (uint) {
x += 1;
return x;
}
// Le funzioni possono restituire molti valori,
// e visto che i valori di ritorno vengono dichiarati prima
// non è richiesta un'instruzione return esplicita
function increment(uint x, uint y) returns (uint x, uint y) {
x += 1;
y += 1;
}
// Chiama la funzione di cui sopra
uint (a,b) = increment(1,1);
// 'view' (un alias di 'constant')
// indica che la funzione non cambia / non può cambiare le variabili persistenti
// Le funzioni definite con view vengono eseguite localmente, non sulla blockchain
// N.B. la keyword constant sarà presto deprecata
uint y = 1;
function increment(uint x) view returns (uint x) {
x += 1;
y += 1; // questa riga fallirebbe
// y è una variabile di stato, e non può essere cambiata in una funzione di view
}
// 'pure' è più restrittivo di 'view' o 'constant', e non
// permette nemmeno di leggere le varaibili di stato
// In realtà è più complicato, per approfondire su
// view/pure:
// http://solidity.readthedocs.io/en/develop/contracts.html#view-functions
// Modificatori di visibilità per le funzioni
// Possono essere messi vicino a 'view' e includono:
// public - visibile esternamente e internamente (di default per function)
// external - visible solo esternamente (comprese le chiamate fatte con this.)
// private - visibile slo dal contratto attuale
// internal - visibile solo dal contratto attuale, e da quelli che ne derivano
// Di solito è una buona idea marcare esplicitamente ogni funzione
// Funzioni hoisted - e si può assegnare una funzione ad una variabile
function a() {
var z = b;
b();
}
function b() {
}
// Tutte le funzioni che ricevono ether devono essere dichiarate come 'payable'
function depositEther() public payable {
balances[msg.sender] += msg.value;
}
// I cicli sono da preferire alla ricorsione
// (la profondità massima dello stack è 1024)
// Inoltre, non impostare dei loop senza limiti,
// perchè potresti reggiungere il limite per il gas
// B. Eventi
// Gli eventi notificano a terze parti; è facile ispezionare e
// accedere agli eventi al di fuori della blockchain (con client leggeri);
// Tipicamente si dichiarano dopo i parametri del contratto
// Tipicamente, sono capitalized - si usa Log come prefisso per esplicitarli
// meglio ed evitare che si confondano con una chiamata a funzione
// Dichiarazione
event LogSent(address indexed from, address indexed to, uint amount);
// Da notare le prime lettere maiuscole
// Chiamata
LogSent(from, to, amount);
/**
Una terza parte esterna (entità o contratto), può osservare usando
la libreria Javascript Web3:
// Quel che se segue è codice Javascript, non Solidity
Coin.LogSent().watch({}, '', function(error, result) {
if (!error) {
console.log("Trasferimento valuta: " + result.args.amount +
" la valuta è stata mandata da " + result.args.from +
" a " + result.args.to + ".");
console.log("I saldi ora sono:\n" +
"Mittente: " + Coin.balances.call(result.args.from) +
"Destinatario: " + Coin.balances.call(result.args.to));
}
}
**/
// È prassi che un contratto dipenda da un altro (es. che dipenda
// dai tassi di cambio forniti da un altro contratto)
// C. Modifier
// I modifier validano gli input per conto dele funzioni verificando ad esempio
// il saldo minimo o l'autenticazione dell'utente;
// sono simili alle calusole di guardia di altri linguaggi
// '_' (underscore) viene spesso posizionato nell'ultima riga del body, e indica
// che la funzione chiamata dev'essere posizionata lì
modifier onlyAfter(uint _time) { require (now >= _time); _; }
modifier onlyOwner { require(msg.sender == owner) _; }
// usate comunemente negli automi a stati finiti
modifier onlyIfStateA (State currState) { require(currState == State.A) _; }
// Si dichiarano appena dopo la definizione di una funzione
function changeOwner(newOwner)
onlyAfter(someTime)
onlyOwner()
onlyIfState(State.A)
{
owner = newOwner;
}
// L'underscore può essere messo prima della fine del body,
// ma un'istruzione di ritorno esplicita lo salterebbe,
// quindi è da usare con cautela
modifier checkValue(uint amount) {
_;
if (msg.value > amount) {
uint amountToRefund = amount - msg.value;
msg.sender.transfer(amountToRefund);
}
}
// 6. ISTRUZIONI CONDIZIONALI E CICLI
// Troviamo tutte le istruzioni condizionali di base - incluse if/else, for,
// while, break, continue e return - ma non c'è lo switch
// La sintassi è la stessa di javascript, ma non esiste la conversione di tipo
// in booleano dai non booleani (bisogna usare gli operatori logici per
// ottenere il valore boolean)
// Bisogna stare attenti i loop che iterano in base al comportamento
// dell'utente, dato che i contratti hanno un tetto massimo di gas
// per blocco di codice e falliranno se lo superano
// Ad esempio:
for(uint x = 0; x < refundAddressList.length; x++) {
refundAddressList[x].transfer(SOME_AMOUNT);
}
// Ci sono due errori nel codice precedente:
// 1. Un fallimento su una transfer impedisce al loop di completare tutti
// i cicli, bloccando dei soldi;
// 2. Questo loop potrebbe essere arbitrariamente lungo (si basa sul numero
// degli utenti che hanno diritto al rimborso), quindi potrebbe fallire sempre
// se supera il tetto massimo di gas per blocco;
// Come soluzione, si potrebbe permettere agli utenti di prelevare
// individualmente dal loro subaccount e segnare il rimborso come riscosso
// Ad es. preferire pull payments ai push payment
// 7. OGGETTI/CONTRATTI
// A. Invocare un contratto esterno
contract InfoFeed {
function info() payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed; // punta ad un contratto sulla blockchain
// Imposta il feed sull'istanza del contratto esistente
function setFeed(address addr) {
// fare attenzione alla conversione di tipo automatica;
// il costruttore non viene invocato
feed = InfoFeed(addr);
}
// Imposta il feed ad una nuova istanza del contratto
function createNewFeed() {
feed = new InfoFeed(); // viene creata una nuova istanza;
// viene invocato il costruttore
}
function callFeed() {
// le parentesi finali invocano il contratto, opzionalmente si può
// specificare un importo custom di ether o di gas
feed.info.value(10).gas(800)();
}
}
// B. ereditarietà
// Conta l'ordine, l'ultimo contratto ereditato (es. 'def') può andare
// in overriding su parti dei contratti precedentemente ereditati
contract MyContract is abc, def("a custom argument to def") {
// Funzione in overriding
function z() {
if (msg.sender == owner) {
def.z(); // invoca la funzione overridden da def
super.z(); // chiama la funzione overridden del padre
}
}
}
// Funzioni astratte
function someAbstractFunction(uint x);
// non possono essere compilate, vengono usate nei contratti base/astratti
// e poi verranno implementate
// C. Import
import "filename";
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol";
// 8. ALTRE KEYWORD
// A. Selfdestruct
// autodistrugge il contratto corrente, inviando i fondi ad un indirizzo
// (di solito il creatore)
selfdestruct(SOME_ADDRESS);
// rimuove il codice e quanto in memoria dal blocco corrente e tutti i futuri blocchi
// aiuta ad alleggerire i client, ma le informazioni precedenti continueranno
// a persistere sulla blockchain
// È un pattern comune, permette al proprietario di terminare il contratto
// e ricevere i fondi rimasti
function remove() {
if(msg.sender == creator) { // Solo il creatore del contratto può farlo
selfdestruct(creator); // Cessa l'attività del contratto, trasferisce i fondi
}
}
// Si potrebbe voler disattivare il contratto manualmente, anzichè usare una
// selfdestruct (gli ether inviati ad un contratto dopo una selfdestruct
// vengono persi)
// 9. NOTE SUL DESIGN DEI CONTRATTI
// A. Offruscamento
// Tutte le variabili sono pubblicamente visibili sulla blockchain, quindi
// qualsiasi cosa privata ha bisogno di essere offruscata (es. hash con una
// chiave segreta)
// Passi: 1. Impegnarsi pagare una certa cifra, 2. Rivelare l'impegno preso
keccak256("una_puntata_d_asta", "un segreto"); // impegno
// in futuro, l'invocazione della funzione rivelatrice del contratto
// mostrerà la puntata ed il segreto che produce lo SHA3
reveal(100, "ilMioSegreto");
// B. Ottimizzazione della memoria (storage)
// Scrivere dati sulla blockchain può essere costoso visto che vengono
// conservati per sempre; siamo incoraggati ad usare la memoria in maniera
// scaltra (un giorno la compilazione migliorerà, ma per ora è vantaggioso
// pianificare le strutture dati da usare - e conservarne il minimo possibile
// sulla blockchain)
// I costi per conservare cose come array multidimensionali sono spesso alti
// (costa conservare dati - non dichiarare variabili parzialmente vuote)
// C. Accesso ai dati sulla blockchain
// Non si può impedire alle persone o ai computer di leggere il contenuto
// o lo stato delle transazioni
// Anche se 'private' non permette agli altri *contratti* di leggere alcune
// informazioni direttamente, qualsiasi altro attore può leggere i dati
// sulla blockchain
// Tutti i dati, dall'inizio, vegono conservati sulla blockchain e
// tutti possono accedere alle informazioni passate e ai cambiamenti futuri
// D. Oracle e dati esterni
// Gli oracle consentono di interagire con i tuoi smart contract
// al di fuori della blockchain.
// Vengono usati per ricevere informazioni dal mondo reale, mandare
// richieste post al mondo reale o vice versa.
// Anche le implementazioni che sfruttano l'ora vengono fatte attraverso
// gli oracle, visto che i contratti devono essere chiamati direttamente e
// non possono fare una "subscribe" a un certo orario.
// Data la decentralizzazione degli smart contract, vorrai ricevere informazioni
// in maniera decentralizzata, altrimenti rischi di ricreare l'accentramento
// che la progettazione degli smart contract si prefigge di prevenire.
// Il modo migliore di ottenere e usare dati decentralizzati già pronti
// è attraverso i Chainlink Data Feeds
// https://docs.chain.link/docs/get-the-latest-price
// Possiamo fare riferimento a certe informazioni della blockchain
// che sono già state aggregate da più fonti e ridistribuite on-chain,
// usandole come "banche dati" di fonti di informazione.
// Puoi vedere altri esempi che effettuano chiamate alle API qui:
// https://docs.chain.link/docs/make-a-http-get-request
// E ovviamente puoi costruire la tua rete di orace, ma assicurati di sapere
// quant'è accentrata o decentralizzata la tua applicazione.
// Mettere su una rete di oracle per conto tuo
// E. Cron Job
// I contratti devono essere chiamati manualmente per gestire la lo scheduling
// in base all'orario; si può creare un codice esterno che li pinghi reglarmente
// oppure fornire degli incentivi (ether) a qualcun'altro che lo faccia
// F. Pattern Observer
// Un pattern observer permette di iscriversi come osservatore e
// registrare una funzione che verrà chiamata dall'oracle
// (N.B. l'oracolo paga perchè sia eseguita quest'azione)
// Ci sono alcune somoglianze nella registrazione con Pub/sub
// Questo è un contratto astratto che importano sia il client che il server
// Il client dovrebbe implementarlo
contract SomeOracleCallback {
function oracleCallback(int _value, uint _time, bytes32 info) external;
}
contract SomeOracle {
SomeOracleCallback[] callbacks; // array di tutti gli osservatori iscritti
// Osservatori iscritti
function addSubscriber(SomeOracleCallback a) {
callbacks.push(a);
}
function notify(value, time, info) private {
for(uint i = 0;i < callbacks.length; i++) {
// tutti gli osservatori iscritti dovranno implementare la oracleCallback
callbacks[i].oracleCallback(value, time, info);
}
}
function doSomething() public {
// Codice che fa qualcosa
// Notifica a tutti gli iscritti
notify(_value, _time, _info);
}
}
// Il contratto client può aggiungersi agli iscritti (con addSubscriber)
// del contratto SomeOracle, importando SomeOracleCallback
// G. Automi a stati finiti
// vedi l'esempio sotto che usa enum per lo stato e il modifier inState
```
Prova l'esempio completo qui sotto [usando remix e la `Javascript VM`](https://remix.ethereum.org/#version=soljson-v0.6.6+commit.6c089d02.js&optimize=false&evmVersion=null&gist=3d12cd503dcedfcdd715ef61f786be0b&runs=200)
```javascript
// *** ESEMPIO: Un esempio di crowdfunding (molto simile a Kickstarter) ***
// ** START EXAMPLE **
// CrowdFunder.sol
pragma solidity ^0.6.6;
/// @title CrowdFunder
/// @author nemild
contract CrowdFunder {
// Variabili impostate alla creazione dal creatore
address public creator;
address payable public fundRecipient; // il creatore può essere diverso
// da chi riceve i fondi, che dev'essere payable
uint public minimumToRaise; // è richiesto per chiedere il finanziamento,
// altrimenti tutti ricevono un rimborso
string campaignUrl;
byte version = "1";
// Strutture dati
enum State {
Fundraising,
ExpiredRefund,
Successful
}
struct Contribution {
uint amount;
address payable contributor;
}
// Variabili di stato
State public state = State.Fundraising; // inizializzato alla creazione
uint public totalRaised;
uint public raiseBy;
uint public completeAt;
Contribution[] contributions;
event LogFundingReceived(address addr, uint amount, uint currentTotal);
event LogWinnerPaid(address winnerAddress);
modifier inState(State _state) {
require(state == _state);
_;
}
modifier isCreator() {
require(msg.sender == creator);
_;
}
// Aspetta 24 settimane dopo l'ultimo cambio di stato prima di consentire
// che in contratto venga distrutto
modifier atEndOfLifecycle() {
require(((state == State.ExpiredRefund || state == State.Successful) &&
completeAt + 24 weeks < now));
_;
}
function crowdFund(
uint timeInHoursForFundraising,
string memory _campaignUrl,
address payable _fundRecipient,
uint _minimumToRaise)
public
{
creator = msg.sender;
fundRecipient = _fundRecipient;
campaignUrl = _campaignUrl;
minimumToRaise = _minimumToRaise;
raiseBy = now + (timeInHoursForFundraising * 1 hours);
}
function contribute()
public
payable
inState(State.Fundraising)
returns(uint256 id)
{
contributions.push(
Contribution({
amount: msg.value,
contributor: msg.sender
}) // usiamo un array per iterare
);
totalRaised += msg.value;
emit LogFundingReceived(msg.sender, msg.value, totalRaised);
checkIfFundingCompleteOrExpired();
return contributions.length - 1; // restituisce l'id
}
function checkIfFundingCompleteOrExpired()
public
{
if (totalRaised > minimumToRaise) {
state = State.Successful;
payOut();
// qui si può incentivare chi ha provocato il cambiamento di stato
} else if ( now > raiseBy ) {
state = State.ExpiredRefund; // ora i finanziatori possono avere
// il rimborso chiamando getRefund(id)
}
completeAt = now;
}
function payOut()
public
inState(State.Successful)
{
fundRecipient.transfer(address(this).balance);
LogWinnerPaid(fundRecipient);
}
function getRefund(uint256 id)
inState(State.ExpiredRefund)
public
returns(bool)
{
require(contributions.length > id && id >= 0 && contributions[id].amount != 0 );
uint256 amountToRefund = contributions[id].amount;
contributions[id].amount = 0;
contributions[id].contributor.transfer(amountToRefund);
return true;
}
function removeContract()
public
isCreator()
atEndOfLifecycle()
{
selfdestruct(msg.sender);
// il creatore riceve tutti i fondi che non sono stati riscossi
}
}
// ** END EXAMPLE **
```
Qualche altra funzionalità.
```javascript
// 10. ATRE FUNZIONALITA' NATIVE
// Unità di valuta
// La valuta viene definita partendo dai wei, l'unità più piccola di Ether
uint minAmount = 1 wei;
uint a = 1 finney; // 1 ether == 1000 finney
// Per altre unità, vedi: http://ether.fund/tool/converter
// Unità temporali
1 == 1 second
1 minutes == 60 seconds
// Le unità temporali si possono moltiplicare, visto che non vegono salvate
// nelle variabili
uint x = 5;
(x * 1 days); // 5 giorni
// Attenzione ad usare l'operatore di uguaglianza con i secondi/anni bisestili
// (sono da preferire maggiore/minore di)
// Crittografia
// Tutte le stringhe che vengono passate vengono concatenate prima di
// calcolare l'hash
sha3("ab", "cd");
ripemd160("abc");
sha256("def");
```
|