Contractele proxy sunt un instrument important pentru dezvoltatorii de contracte inteligente. Astăzi, există mai multe modele de agenție și reguli de utilizare corespunzătoare în sistemul contractual. Am subliniat anterior cele mai bune practici de securitate pentru contractele de proxy actualizabile.

În acest articol vom introduce un alt model proxy care este popular în comunitatea de dezvoltatori, modelul proxy diamant.

Contractul de agenție Diamond, cunoscut și sub numele de „Diamond”, este un model de design pentru contractele inteligente Ethereum introdus de Ethereum Improvement Proposal (EIP) 2535.

Modelul Diamond permite unui contract să aibă o funcționalitate nelimitată prin împărțirea funcționalității sale în contracte mai mici (cunoscute și la figurat sub numele de „slices”). Diamantele acționează ca proxy, funcția de rutare apelează la aspectele corespunzătoare.

Modul diamant este conceput pentru a rezolva problema limitei maxime a dimensiunii contractului a rețelei Ethereum. Prin împărțirea unui contract mare în fațete mai mici, modelul Diamond permite dezvoltatorilor să construiască contracte inteligente mai complexe și mai bogate în funcții, fără a fi supus unor constrângeri de dimensiune.

Diamond Agents oferă o flexibilitate extraordinară în comparație cu contractele tradiționale care pot fi actualizate. Acestea permit actualizarea părților contractuale, adăugarea, înlocuirea sau eliminarea funcțiilor parțiale selectate fără a atinge alte părți.

Acest articol oferă o prezentare generală a EIP-2535, inclusiv comparații cu modul proxy transparent utilizat pe scară largă și modul proxy UUPS, precum și considerațiile sale de securitate pentru comunitatea dezvoltatorilor.

În contextul EIP-2535, „Diamant” este un contract de proxy a cărui implementare funcțională este asigurată de diferite contracte logice, numite „aspecte”.

Imaginați-vă că diamantele reale au părți diferite, numite fațete, apoi contractul de diamante Ethereum corespunzător are și fațete diferite. Fiecare contract de funcție de creditare a diamantelor are aspecte sau fațete diferite.

Standardul Diamond extinde capabilitățile Diamond Cut prin analogie pentru a adăuga, înlocui sau șterge fațete și caracteristici.

În plus, Diamond Standard oferă o caracteristică numită „Diamond Loupe” care returnează informații despre fațete și prezența diamantelor.

În comparație cu modelul tradițional de agenție, „diamantul” este echivalent cu contractul de agenție, în timp ce diferite „aspecte” corespund contractului de implementare. Diferite aspecte ale unui agent Diamond pot partaja funcții interne, biblioteci și variabile de stare. Componentele cheie ale unui diamant sunt următoarele:

Un contract central care acționează ca un proxy și funcția de rută apelează la aspectele corespunzătoare. Conține o mapare a selectoarelor de funcții la adrese „aspect”.

Un singur contract care implementează o anumită funcție. Fiecare aspect conține un set de funcții care pot fi numite de diamant.

este un set de funcții standard definite în EIP-2535 care oferă informații despre fațetele și selectoarele de funcții utilizate în diamante. Diamond Magnifier permite dezvoltatorilor și utilizatorilor să inspecteze și să înțeleagă structura diamantelor.

Funcții pentru adăugarea, înlocuirea sau eliminarea fațetelor și selectoarelor de caracteristici corespunzatoare acestora într-un diamant. Doar adresele autorizate (de exemplu, proprietarul diamantului sau un contract cu semnături multiple) pot efectua tăierea diamantelor.

Similar agenților tradiționali, atunci când există un apel de funcție pe agentul diamant, funcția de rezervă a agentului (funcția de rezervă) va fi declanșată. Principala diferență față de proxy-ul diamant este că în funcția de rezervă, există o mapare selectorToFacet care stochează și determină ce adresă logică a contractului are implementarea funcției apelate. Apoi folosește delegatecall pentru a executa funcția, la fel ca un delegat tradițional.

Toate proxy-urile folosesc funcția fallback() pentru a delega apelurile de funcții către adrese externe. Urmează implementarea agentului de diamant și implementarea agentului tradițional.

Este demn de remarcat faptul că blocurile lor de cod de asamblare sunt foarte asemănătoare, astfel încât singura diferență este adresa aspectului în apelul de delegat proxy diamant și adresa impl în apelul tradițional de delegat proxy.

Principala diferență este că în proxy-ul diamant, adresa aspectului este determinată de hashmap-ul msg.sig al apelantului (selector de funcție) la adresa aspectului, în timp ce în proxy-ul tradițional, adresa impl nu depinde de intră apelantul.

Funcția de rezervă a agentului diamant

Funcție tradițională de rezervă proxy

Maparea SelectorToFacet determină ce contract conține implementarea fiecărui selector de funcție. Personalul proiectului trebuie adesea să adauge, să înlocuiască sau să elimine această mapare a selectorilor de funcții la contractele de implementare. EIP-2535 stipulează: Pentru a atinge acest scop, trebuie să existe o funcție diamondCut(). Mai jos este un exemplu de interfață.

Fiecare structură FacetCut conține o adresă de fațetă și o matrice de patru octeți de selectoare de caracteristici care urmează să fie actualizate în contractul agentului de diamant. FaceCutAction permite oamenilor să adauge, să înlocuiască și să elimine selectoare de funcții. Implementarea funcției diamondCut() ar trebui să includă un control adecvat al accesului pentru a preveni coliziunile sloturilor de stocare, a recupera în caz de defecțiune etc.

Pentru a verifica ce funcții are un agent diamant și ce fațete folosește, folosim „Lupa de diamant”. „Lupa de diamant” este un aspect special care implementează următoarea interfață definită în EIP-2535:

Funcția facets() ar trebui să returneze adresele tuturor fațetelor și selectoarelor lor de funcții de patru octeți. Funcția facetFunctionSelectors() ar trebui să returneze toți selectorii de funcție acceptați de un anumit aspect. Funcția facetAddresses() ar trebui să returneze adresele tuturor fațetelor utilizate de un diamant.

Funcția facetAddress() ar trebui să returneze fațeta care acceptă selectorul dat sau adresa (0) dacă nu este găsită. Rețineți că nu ar trebui să existe mai mult de o adresă de aspect cu același selector de caracteristici.

Având în vedere că Diamond Agent delegă diferite apeluri de funcții către diferite contracte de implementare, este esențial să gestionați corect sloturile de stocare pentru a preveni conflictele. EIP-2535 menționează mai multe metode de gestionare a sloturilor de stocare.

Acest aspect poate declara variabile de stare în structură. Acest aspect poate folosi orice număr de structuri, fiecare cu o locație de depozitare diferită. Fiecare structură are o locație specifică în depozitul contractual. Aspectele își pot declara propriile variabile de stare, dar nu pot intra în conflict cu locația de stocare a variabilelor de stare declarate de alte aspecte. EIP-2535 oferă o bibliotecă de mostre și un contract de stocare a diamantelor, așa cum se arată în figura de mai jos:

App Storage este o versiune mai specializată a Diamond Storage. Acest model este folosit pentru a face mai convenabilă și mai ușoară partajarea variabilelor de stare de aspect. O structură de stocare a aplicației este definită pentru a conține orice număr și tip de variabile de stare cerute de o aplicație. Un aspect declară întotdeauna structura AppStorage ca prima și singura variabilă de stare, situată la slotul 0. Diferite aspecte pot accesa apoi variabile din această structură.

Există și alte strategii de gestionare a sloturilor de stocare, inclusiv o combinație de Diamond Storage și AppStorage. De exemplu, unele structuri sunt partajate între diferite aspecte, iar unele sunt unice pentru anumite aspecte. În toate cazurile, este important să se prevină coliziunile accidentale ale rezervoarelor de stocare.

Comparație cu proxy transparente și proxy UUPS

Cele două moduri proxy principale utilizate în prezent de comunitatea de dezvoltatori Web3 sunt modul proxy transparent și modul proxy UUPS. În această secțiune, comparăm pe scurt modelul proxy Diamond cu modelele proxy transparent și UUPS.

1.EPI-2535:https://eips.ethereum.org/EIPS/eip-2535 #Facets,% 20 State% 20 Variables% 20 și% 20 Diamond% 20 Stocare

2.EPI-1967:https://eips.ethereum.org/EIPS/eip-1967    

3.Implementarea referințelor proxy Diamond: https://github.com/mudgen/Diamond    

4.Implementarea OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.7.0/contracts/proxy

Agenții și soluțiile scalabile sunt sisteme mai complexe, iar OpenZeppelin oferă biblioteci de cod și documentație cuprinzătoare pentru agenții scalabili UUPS, transparenți și Beacon. Cu toate acestea, pentru modelul proxy diamant, deși OpenZeppelin și-a afirmat beneficiile, au decis totuși să nu includă implementarea diamant EIP-2535 în biblioteca lor.

Prin urmare, dezvoltatorii care folosesc biblioteci terțe existente sau care implementează singuri soluția trebuie să fie extrem de precauți atunci când o implementează. Aici am compilat o listă cu cele mai bune practici de securitate pe care comunitatea de dezvoltatori să le ia în considerare.

Prin descompunerea logicii contractului în module mai mici și mai ușor de gestionat, dezvoltatorii își pot testa și audita mai ușor codul.

În plus, această abordare permite dezvoltatorilor să se concentreze pe construirea și menținerea unor aspecte specifice ale contractului, mai degrabă decât pe gestionarea unei baze de cod complexe, monolitice. Rezultatul final este o bază de cod mai flexibilă și modulară, care poate fi ușor actualizată și modificată fără a afecta alte părți ale contractului.

Sursa: Aavegotchi Github

Când contractul de proxy diamant este implementat, acesta trebuie să adauge adresa contractului DiamondCutFacet la contractul de proxy diamant și să implementeze funcția diamondCut(). Funcția diamondCut() este utilizată pentru a adăuga, șterge sau înlocui aspecte și funcții Fără DiamondCutFacet și diamondCut(), agentul de diamant nu poate funcționa corect.

Sursa: Mugen’s Diamond-3-Hardhat

Când adăugați o nouă variabilă de stare la o structură de stocare într-un contract inteligent, aceasta trebuie adăugată la sfârșitul structurii. Adăugarea unei noi variabile de stare la începutul sau la mijlocul unei structuri va face ca noua variabilă de stare să suprascrie datele existente ale variabilei de stare, iar orice variabilă de stare după noua variabilă de stare poate face referire la locația de stocare greșită.

Modelul AppStorage necesită declararea unei singure structuri pentru agentul de diamant, iar această structură este împărtășită de toate aspectele. Dacă sunt necesare mai multe structuri, trebuie utilizat modelul DiamondStorage.

Nu plasați o structură direct în interiorul unei alte structuri decât dacă sunteți sigur că nu intenționați să adăugați mai multe variabile de stare structurii interioare. Nu puteți adăuga variabile de stare noi la o structură internă într-o actualizare fără a suprascrie sloturile de stocare variabile declarate după structură.

Soluția este să adăugați noua variabilă de stare la structura hărții de stocare în loc să plasați „struct” direct în „struct”. Sloturile de stocare variabile din hartă sunt calculate diferit și nu sunt învecinate în stocare.

Mărimea matricei va fi afectată de dimensiunea structurii. Când o nouă variabilă de stare este adăugată la o structură, aceasta modifică dimensiunea și aspectul structurii.

Acest lucru poate cauza probleme dacă structura este utilizată ca element într-o matrice. Dacă dimensiunea și aspectul structurii se schimbă, atunci și dimensiunea și aspectul matricei se vor schimba, ceea ce poate cauza probleme cu indexarea sau alte operațiuni care se bazează pe dimensiunea și aspectul consecvent al structurii.

Similar cu alte modele de proxy, fiecare variabilă ar trebui să aibă un slot de stocare unic. În caz contrar, două structuri diferite din aceeași locație s-ar suprascrie reciproc.

Funcția initialize() este de obicei folosită pentru a seta variabile importante, cum ar fi adresa unui rol privilegiat. Dacă contractul nu este inițializat atunci când este implementat, un actor rău intenționat poate suna și prelua controlul asupra contractului.

Este recomandat să adăugați un control de acces adecvat la funcția de inițializare/setare sau să vă asigurați că funcția este apelată atunci când contractul este implementat și nu poate fi apelată din nou.

Dacă orice aspect din contract poate apela funcția selfdestruct(), aceasta poate distruge întregul contract, ducând la pierderea de fonduri sau date. Acest lucru este extrem de periculos în modelul de proxy diamant, deoarece mai multe aspecte pot accesa stocarea și datele contractului de proxy.

În prezent, vedem din ce în ce mai multe proiecte care adoptă modelul agentului de diamant în contractele lor inteligente. Oferă flexibilitate și alte avantaje față de agenții tradiționali.

Cu toate acestea, o flexibilitate suplimentară poate însemna și oferirea atacatorilor o suprafață de atac mai largă. Sperăm că acest articol este util comunității de dezvoltatori pentru a înțelege mecanismele modelului proxy diamant și considerentele sale de securitate.

În același timp, echipa de proiect ar trebui să efectueze teste riguroase și audituri terțe pentru a reduce riscul de vulnerabilități legate de implementarea contractului de agenție de diamante.

CertiK va continua să publice astfel de articole tehnice pentru a ajuta mai mulți dezvoltatori să se dezvolte în siguranță. Urmărește-ne pentru a obține mai multe informații și informații similare!