Laborator 5
Tematica laboratorului: MetaMask si Design Patterns
MetaMask
MetaMask reprezinta o extensie de browser ce se poate instala de la aceasta adresa.
Aceasta permite conectarea la o retea Ethereum (atat din cele publice cat si una locala rulate de exemplu folosind Ganache) si initierea de tranzactii prin intermediul browserului, sau a altor aplicatii ce ruleaza in browser, tipic prin intermediul unei biblioteci JavaScript numita web3.
Un contract scris in Remix poate fi de exemplu instantiat si accesat pe o retea publica Ethereum prin intermediul browserului, selectand "Injected Provider" ca "Environment" in pluginul pentru deployment, si confirmand tranzactia si plata aferenta prin MetaMask.
Retelele de test publice Ethereum disponibile in prezent sunt urmatoarele:
Pentru a obtine fonduri pe retelele de test publice Ethereum, se pot utiliza asa numitele site-uri de tip "faucet", ce distribuie ETH de test gratuit in anumite conditii (de exemplu unele necesita o cerere facuta prin intermediul unei retele sociale).
O indexare a diverse alternative de faucets este disponibila la aceasta adresa.
Tranzactiile de pe retelele de test pot fi monitorizate pe adresele Etherscan respective: Goerli, Sepolia, Holesky.
Oracle
Acest tip de pattern este legat de necesitatea de a obtine in cadrul unui contract informatii externe contextului retelei blockchain, prin intermediul unui contract tert specializat - asa zis "oracol".
Contractul respectiv primeste cereri pentru furnizarea de astfel de informatii externe pe care le acceseaza printr-o comunicare externa si le trimite de obicei ca raspuns asincron contractului solicitant (serviciul e asigurat de regula contra cost). Printre furnizorii independenti de astfel de servicii de tip "oracol" se numara: Provable (fost Oraclize), Chainlink si Town Crier.
De regula, pentru utilizarea unui oracol este necesara implementarea intr-un contract a doua functii:
- O functie cu rolul de a initia si trimite cererea catre oracol in cadrul unei tranzactii, ce depinde de API-ul pus la dispozitie de implementarea oracolului. Functia poate include diversi parametri, transfera costul necesar cererii si retine eventual un identificator al cererii primit ca retur.
- O functie callback ce va fi apelata asincron de oracol pentru a furniza raspunsul la cererea formulata cand acest raspuns este disponibil. Functia respectiva poate stoca raspunsul sau initia alte operatii in cadrul contractului dupa caz. Avand in vedere ca astfel de operatii initiate la primirea raspunsului pot induce alte costuri, un element necesar in functia callback este de regula verificarea ca apelantul functiei este strict oracolul, iar apelul este un raspuns la cererea trimisa (in functie de identitatea contractului respectiv, ID cerere, etc.).
Mai jos se regaseste un exemplu de contract ce foloseste un oracol creat utilizand API-ul pus la dispozitie de Provable. Functia care trimite cererea este updatePrice, iar functia de callback este __callback. Provable ofera suport in prezent pe retelele de test Goerli si Sepolia. API-ul Provable va detecta automat reteaua, contractul importat pentru acces la API oferind aceasta posibilitate in codul sursa. Primul apel updatePrice din partea contractului de mai jos catre oracol va fi gratuit. Pentru urmatoarele insa se va percepe o taxa din balanta contractului, deci aceasta trebuie alimentata (fie la instantiere prin constructor care este payable, fie prin functia pay, fie direct la apelul updatePrice care este de asemenea payable).
Alte exemple de utilizare Provable se pot regasi la aceasta adresa (nota: majoritatea exemplelor sunt compatibile cu versiuni mai vechi de Solidity, necesitand unele adaptari pentru ultima varianta de API).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "github.com/provable-things/ethereum-api/provableAPI.sol";
contract ExampleContract is usingProvable {
string public ETHUSD;
mapping(bytes32=>bool) public validIds;
event LogConstructorInitiated(string nextStep);
event LogPriceUpdated(string price);
event LogNewProvableQuery(string description, uint queryPrice, bytes32 queryId);
constructor () payable {
emit LogConstructorInitiated("Constructor was initiated. Call 'updatePrice()' to send the Provable Query.");
}
function pay() public payable {}
function balance() public view returns(uint256) {
return address(this).balance;
}
function updatePrice() public payable {
uint queryPrice = provable_getPrice("URL");
if (queryPrice > address(this).balance) {
emit LogNewProvableQuery("Provable query was NOT sent, please add some ETH to cover for the query fee", queryPrice, 0);
} else {
bytes32 queryId = provable_query("URL", "json(https://api.pro.coinbase.com/products/ETH-USD/ticker).price");
validIds[queryId] = true;
emit LogNewProvableQuery("Provable query was sent, standing by for the answer..", queryPrice, queryId);
}
}
function __callback(bytes32 myid, string memory result) public {
//verificarea corespondentei raspunsului cu o cerere trimisa
if (!validIds[myid]) revert();
//verificarea apelantului functiei ca fiind oracolul
if (msg.sender != provable_cbAddress()) revert();
ETHUSD = result;
emit LogPriceUpdated(result);
delete validIds[myid];
}
}
Nota: Oracolul oferit de Provable manifesta uneori downtimes pe diversele retele de test. Stadiul curent poate fi de regula aflat in informatiile oferite la aceasta adresa: https://gitter.im/provable/ethereum-api.
Proxy delegate
Principala utilitate a acestui pattern este necesitatea de a modifica anumite contracte. Fiindca un contract care este "deployed" este in esenta nemodificabil, si pentru a nu crea probleme privind posibilele dependente, modalitatea uzuala de a permite un asa zis "update" la un contract (efectiv in practica un nou deployment) este de a referi contractul existent pentru acces prin intermediul unui "proxy". Patternul poate implica in esenta trei nivele de contract:
- contractul care apeleaza o functie dintr-un alt contract: caller contract
- contractul intermediar care asigura transmiterea apelului spre ultima versiune a contractului apelat: proxy contract
- contractul la versiunea din care se face efectiv apelul: delegate contract
In esenta contractul proxy nu se modifica, dar se pot modifica valorile din variabilele sale de stare ce pot include adrese de contract delegat in vederea efectuarii unui update a versiunii acestuia.
Apelurile catre contractul delegat se fac prin intermediul proxy-ului prin delegare catre versiunea de contract activa, ce include codul actualizat al functiilor.
Rezultatul este returnat catre proxy, care la randul sau il va returna catre apelant.
Noile versiuni de contract catre care se face delegarea apelurilor, pot avea deci modificari de functii, dar nu ar trebui sa aiba modificari structurale si in ce priveste variabilele de stare (ar trebui sa pastreze aceeasi configuratie a variabilelor existente in proxy). Motivul este ca apelurile catre versiunea de contract delegat se realizeaza din proxy prin intermediul metodei delegatecall. Aceasta va executa codul din contractul delegat dar in contextul contractului proxy, ceea ce implica operarea pe variabilele de stare ale contractului proxy, si nu pe ale contractului delegat. Eventuale modificari structurale ale variabilelor de stare ar putea cauza deci probleme.
Apelul functiilor prin delegare se vor face efectiv in functia fallback din cadrul contractului proxy. Aceasta va fi apelata implicit la orice apel al unei functii ce nu exista in contractul proxy, si ar trebui sa includa un apel delegatecall care sa preia signatura functiei apelate si sa o invoce din contractul delegat. Avand in vedere ca implementarea respectiva ar trebui sa fie independenta - pliabila pe orice functie - se prefera ca in functia fallback sa fie folosita o secventa "standard" de cod assembly care sa poata prelua orice apel de functie si sa poata returna orice retur de functie. Un exemplu in acest sens se poate regasi la aceasta adresa care propune un contract proxy abstract ce poate fi derivat pentru o implementare proprie a patternului.
Tools: Tenderly Debugger
O varianta disponibila posibil utila pentru debugging in ce priveste executia contractelor este oferita de platforma Tenderly (necesita inregistrare, dar ofera si un plan de utilizare gratuit). Pentru observarea executiei contractelor in Tenderly este necesara instantierea acestora pe o retea publica de test, si furnizarea catre Tenderly a sursei contractului (sau a ABI-ului corespondent obtinut in urma compilarii), precum si a versiunii de compilator folosita. Platforma Tenderly ofera diverse functionalitati ce pot fi utile intr-o sesiune de debugging: trace-ul pas cu pas pe executia tranzactiilor cu monitorizarea gas-ului consumat, inspectarea unei valori sau expresii din contextul de executie a unei functii, re-simularea unei tranzactii vechi, posibilitatea stabilirii indexului de bloc din blockchain la care se face simularea, etc.
La aceasta adresa se regaseste exemplul de contract pentru utilizarea oracolului discutat mai sus incarcat public in Tenderly, in urma instantierii pe reteaua Goerli, fiind disponibil pentru simularea si debugging-ul tranzactiilor executate (necesita in principiu includerea intr-un proiect personal de lucru creat in platforma).