Laborator 3
Tematica laboratorului: Solidity - notiuni de baza
- Exemplu smart contract - tipuri de date si elemente contract
- Locatii de stocare a variabilelor
- Modificatori de acces
- Asignarea variabilelor (valoare sau referinta)
- Modificatori de functii
- Evenimente
- Variabile globale
Exemplu smart contract
Redam mai jos un exemplu de smart contract cu scopul de a evidentia cateva tipuri principale de date si elemente (enumerari, modificatori, evenimente, functii, constructor), ce se pot regasi in cadrul unui contract. Majoritatea tipurilor de data in Solidity retin direct valoarea. Exista urmatoarele tipuri referinta: vectori (arrays - inclusiv tipurile speciale "string" si "bytes"), structuri si mapari (mapping).
Pentru detalii asupra API-ului Solidity se pot consulta intrarile specifice prezente in documentatia Solidity.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <=0.8.21;
//definitie contract
contract GeneralStructure {
//variabile si constante
uint public integerVar; // variabila de tip intreg explicit publica
string stringVar; // variabila de tip string
address addressVar; // variabila de tip adresa
// descriere API: https://solidity.readthedocs.io/en/v0.8.21/types.html#address
// descriere API: https://docs.soliditylang.org/en/v0.8.21/units-and-global-variables.html#members-of-address-types
person structVar; // variabila de tip structura
bool constant boolConst = true; // constanta booleana
mapping (uint => person) persons; // variabila de tip mapping
//declaratie enumerare - valori indexate incepand cu 0
enum eyecolor {brown, blue, green}
//definitie structura
struct person {
string name;
uint age;
bool isMarried;
eyecolor eyes;
uint[] bankAccounts; // variabila de tip vector
}
//declaratie modificator
modifier onlyBy(){
require(msg.sender == addressVar, "Nu esti cine trebuie sa fii"); // a se vedea require vs assert vs revert
_;
}
//declaratie eveniment
event ageSet(address, uint);
//definitie constructor
constructor() {
addressVar = msg.sender;
integerVar = 0;
}
//definitie functie
function createPersonWithAge(uint _age) onlyBy() payable external returns (uint) {
structVar = person("John", _age, true, eyecolor.brown, new uint[](3)); //instantiere structura
emit ageSet(msg.sender, structVar.age);
persons[integerVar] = structVar;
integerVar++;
return structVar.age;
}
// private
function doPureMath(uint a) private pure returns (uint) {
return a + 1;
}
// e nevoie?
function getIntegerVar() public view returns (uint) {
return integerVar;
}
}
Locatii de stocare a variabilelor
Exista patru zone posibile de stocare, utilizate de catre EVM, dupa cum urmeaza:
- storage - locatia persistenta de stocare disponibila pentru variabilele ce definesc starea contractului si accesibila functiilor din contract pentru creare de noi date sau modificari persistente (tipic, cea mai costisitoare zona)
- memory - locatie de memorie pentru stocare nepersistenta disponibila la executia fiecarei functii din contract, de regula pentru variabilele locale functiei (costul creste odata cu dimensiunea necesara)
- calldata - locatie speciala de memorie, nemodificabila, unde pot fi stocate argumentele functiilor externe pasate la apel (cost mai scazut de acces comparativ cu "memory")
- stack - stiva de memorie ce este utilizata de EVM pentru pastrarea datelor in momentul in care se executa operatii asupra acestora; nu este folosita in mod explicit pentru stocare de catre programator
Modificatori de acces
Atat variabilele declarate in cadrul contractului ce definesc starea acestuia ("state variables") cat si functiile contractului pot avea urmatoarea serie de modificatori de acces programatic (pentru functii specificarea acestora este obligatorie):
- internal - accesul programatic este posibil doar din cadrul contractului sau din contractele derivate ale acestuia
- private - accesul programatic este posibil doar din cadrul contractului
- public - accesul programatic este posibil de oriunde; pentru variabilele din contract declarate public, sunt generate automat intern la compilare metode de tip getter avand numele variabilei ce permit interogarea valorilor
- external - posibil doar pentru specificarea accesului in cazul functiilor; functiile declarate astfel pot fi accesate implicit exclusiv din afara contractului (sau folosind this.nume_functie pentru mod intern, dar aceasta cauzeaza costuri crescute); diferenta fata de acces public consta in accesul la argumentele functiei de tip referinta care pentru apeluri externe se face direct pe spatiul de memorie calldata - in cazul apelurilor publice din cauza ca acestea se pot executa si intern se realizeaza mai intai o copie din spatiul calldata in memory ce creste costul de executie
Asignarea variabilelor (valoare sau referinta)
Asignarea variabilelor se face in aproape toate cazurile prin valoare: variabila ce primeste asignarea va fi independenta, cu o copie a valorii variabilei primite la asignare. Doua exceptii apar la variabilele de tip referinta (structuri, mappings si tablouri, inclusiv string si bytes) la asignarea in cadrul aceluiasi spatiu de stocare - memory la memory sau o variabila din storage la o variabila locala functiei declarata tot in storage (nu si situatia a doua variabile de stare a contractului din storage). In aceste situatii ambele variabile de tip referinta vor referi aceeasi locatie de memorie si orice modificare a unei variabile este vizibila si in cealalta. Mai multe detalii se pot regasi la aceasta referinta.
Modificatori de functii (payable)
Modificatorii de functii sunt constructii ce se pot atasa functiilor, separate de corpul acestora, pentru o lizibilitate mai buna a codului sau pentru reutilizarea usoara. In multe situatii, codul unui modificator implica verificarea unei anumite conditii. Corpul functiei la care se ataseaza modificatorul este specificat prin caracterul underscore "_;".
Un modificator predefinit este "payable". Acesta indica faptul ca functia poate accepta primirea unei balante. Daca functia nu este definita ca payable orice tranzactie care o invoca si care va incerca trimiterea de Ether va esua.
Evenimente
Evenimentele pot fi utilizate in Solidity pentru a emite anumite notificari. Acestea nu pot fi captate de catre contracte in mod direct. Principala utilizare este pentru loguri sau pentru monitorizarea de catre cod/aplicatii externe contractelor.
Variabile globale (msg)
La nivelul contractelor exista un spatiu de nume cu acces global implicit, unde variabilele definite in acest spatiu de nume se modifica in functie de contextul de executie. O descriere a diverselor functii disponibile si a variabilelor membre se regaseste la aceasta adresa.
In particular "msg" e o variabla asociata apelului functiei curente aflata in executie si include doi membri cu utilizare frecventa: msg.sender - adresa initiatorului apelului si msg.value - valoarea balantei trimise de apelant in Wei.
Exercitii
- 1. Redenumiti in tot contractul variabila addressVar ca admin.
- 2. Inlocuiti campul bankAccounts din structura person cu un camp bankAccount de tip adresa platibila si modificati tipul cheii din maparea persoanelor la tipul adresa. (Hint: Declaratia unei adrese ca platibila se face prin specificatorul address payable iar conversia la tipul platibil se face prin apelul payable(adresa de convertit).)
- 3. Modificati functia createPersonWithAge astfel incat sa primeasca ca parametri toate elementele structurii pentru instantierea unei noi persoane si permiteti apelarea acesteia de catre oricine in mod gratuit. Adresa contului bancar va fi setata ca fiind adresa apelantului (msg.sender).
- 4. Creati o functie findPerson care pe baza unei adrese sa returneze numele unei persoane si balanta contului acesteia (campul address.balance din adresa de tip uint256). Functia trebuie sa ceara o plata de minim 1000 wei de la apelant (verificabil prin msg.value). Nota: o functie poate avea mai multe valori de retur sub forma unui tuplu cuprins intre paranteze.
- 5. Creati o functie removePerson care sa stearga o persoana din mapare pe baza adresei care sa fie apelabila doar de admin.