blogul 1NewsDevelopersEnterpriseBlockchain Explained Evenimente și conferințe ApăsațiBuletine informative

Contents

Aboneaza-te la newsletter-ul nostru.

Adresa de email

Vă respectăm confidențialitatea

AcasăBlogDezvoltare blockchain

Cele mai bune practici de soliditate pentru securitatea contractelor inteligente

De la monitorizare până la considerente privind marcarea timpului, iată câteva sfaturi pro pentru a vă asigura că contractele dvs. inteligente Ethereum sunt consolidate. de ConsenSys 21 august 2020 Postat pe 21 august 2020

soliditate cele mai bune practici erou

De ConsenSys Diligence, echipa noastră de experți în securitate blockchain.

Dacă ați luat în considerare mentalitatea de securitate a contractului inteligent și primiți un control asupra idiosincraziei EVM, este timpul să luați în considerare câteva modele de securitate specifice limbajului de programare Solidity. În acest rezumat, ne vom concentra asupra recomandărilor de dezvoltare securizate pentru Solidity, care pot fi, de asemenea, instructive pentru dezvoltarea contractelor inteligente în alte limbi. 

Bine, să intrăm.

Folosiți în mod corespunzător assert (), require (), revenire ()

Funcțiile de comoditate afirma și solicita poate fi folosit pentru a verifica condițiile și a arunca o excepție dacă condiția nu este îndeplinită.

 afirma funcția trebuie utilizată numai pentru a testa erorile interne și pentru a verifica invarianții.

 solicita funcția ar trebui utilizată pentru a asigura condiții valide, cum ar fi intrările sau variabilele stării contractului sunt îndeplinite, sau pentru a valida valorile returnate de la apeluri către contracte externe. 

Urmarea acestei paradigme permite instrumentelor de analiză formală să verifice dacă nu se poate ajunge niciodată la codul opțional invalid: ceea ce înseamnă că nu sunt încălcați invarianți din cod și că codul este verificat formal.

soliditatea pragmei ^ 0,5,0; contract Sharer {funcția sendHalf (adresă de plătit addr) returnări publice de plătit (sold uint) {require (msg.value% 2 == 0, "Chiar și valoarea necesară."); // Require () poate avea un șir de mesaje opțional uint balanceBeforeTransfer = address (this) .balance; (succes bool,) = addr.call.value (msg.value / 2) (""); cere (succes); // Întrucât am revenit în cazul în care transferul a eșuat, nu ar trebui să existe // nicio modalitate de a avea încă jumătate din bani. assert (adresa (aceasta) .balance == balanceBeforeTransfer – msg.value / 2); // folosit pentru verificarea erorilor interne la adresa de retur (aceasta) .balance; }} Limba codului: JavaScript (javascript)

Vedea SWC-110 & SWC-123

Folosiți modificatori numai pentru verificări

Codul din interiorul unui modificator este de obicei executat înainte de corpul funcției, astfel încât orice modificare de stare sau apelurile externe vor încălca Verificări-Efecte-Interacțiuni model. Mai mult, aceste declarații pot rămâne neobservate de dezvoltator, deoarece codul modificatorului poate fi departe de declarația funcției. De exemplu, un apel extern în modificator poate duce la atacul de reintrare:

registru contract {proprietarul adresei; funcția isVoter (address _addr) returnează extern (bool) {// Code}} contract Alegere {Registry registry; modificatorul isEligible (address _addr) {require (registry.isVoter (_addr)); _; } function vote () isEligible (msg.sender) public {// Code}} Limba codului: JavaScript (javascript)

În acest caz, contractul de registru poate face un atac de reintroducere apelând Election.vote () în interiorul isVoter ().

Notă: Utilizare modificatori pentru a înlocui verificările de condiții duplicate în mai multe funcții, cum ar fi isOwner (), în caz contrar, utilizați sau reveniți în interiorul funcției. Acest lucru face ca codul dvs. de contract inteligent să fie mai ușor de citit și mai ușor de auditat.

Aveți grijă la rotunjirea cu divizarea numerelor întregi

Toate diviziunile întregi se rotunjesc la cel mai apropiat număr întreg. Dacă aveți nevoie de mai multă precizie, luați în considerare utilizarea unui multiplicator sau stocați atât numeratorul, cât și numitorul.

(În viitor, Solidity va avea un punct fix , ceea ce va ușura acest lucru.)

// uint rău x = 5/2; // Rezultatul este 2, toate diviziunile întregi rotunjesc JOS până la cel mai apropiat număr întreg Limbaj cod: JavaScript (javascript)

Utilizarea unui multiplicator împiedică rotunjirea în jos, acest multiplicator trebuie luat în considerare atunci când lucrați cu x în viitor:

// multiplicator bun uint = 10; uint x = (5 * multiplicator) / 2; Limbajul codului: JavaScript (javascript)

Stocarea numărătorului și numitorului înseamnă că puteți calcula rezultatul din lanț al numărătorului / numitorului:

// numărător bun uint = 5; uint denominator = 2; Limbajul codului: JavaScript (javascript)

Fiți conștienți de compromisurile dintre contracte abstracte și interfețe

Atât interfețele, cât și contractele abstracte oferă unul cu o abordare personalizabilă și reutilizabilă pentru contractele inteligente. Interfețele, care au fost introduse în Solidity 0.4.11, sunt similare contractelor abstracte, dar nu pot avea nicio funcție implementată. Interfețele au, de asemenea, limitări, cum ar fi imposibilitatea de a accesa stocarea sau de a moșteni de la alte interfețe, ceea ce face în general contractele abstracte mai practice. Deși, interfețele sunt cu siguranță utile pentru proiectarea contractelor înainte de implementare. În plus, este important să rețineți că, dacă un contract moștenește dintr-un contract abstract, acesta trebuie să implementeze toate funcțiile neimplementate prin suprasolicitare sau va fi și abstract..

Funcții de rezervă

Păstrați funcțiile de rezervă simple

Funcții de rezervă sunt apelate atunci când unui contract i se trimite un mesaj fără argumente (sau când nu se potrivește nicio funcție) și are acces la 2.300 de gaze numai atunci când este apelat dintr-un .send () sau .transfer (). Dacă doriți să puteți primi Ether de la .send () sau .transfer (), cel mai mult pe care îl puteți face într-o funcție de rezervă este înregistrarea unui eveniment. Folosiți o funcție adecvată dacă este necesar un calcul al mai multor gaze.

// funcție greșită () de plătit {solduri [msg.sender] + = msg.value; } // good function deposit () payable external {solds [msg.sender] + = msg.value; } function () payable {require (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Limba codului: JavaScript (javascript)

Verificați lungimea datelor în funcțiile de rezervă

Din moment ce funcții de rezervă nu este apelat doar pentru transferuri de eter simplu (fără date), ci și atunci când nu se potrivește alte funcții, ar trebui să verificați dacă datele sunt goale dacă funcția de rezervă este destinată a fi utilizată numai în scopul înregistrării Etherului primit. În caz contrar, apelanții nu vor observa dacă contractul dvs. este utilizat incorect și sunt apelate funcții care nu există.

// funcție greșită () de plătit {emit LogDepositReceived (msg.sender); } // good function () payable {require (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Limba codului: JavaScript (javascript)

Marcați în mod explicit funcțiile de plătit și variabilele de stare

Începând de la Solidity 0.4.0, fiecare funcție care primește eter trebuie să utilizeze modificator plătibil, altfel dacă tranzacția are msg.value > 0 va reveni (cu excepția cazului în care este forțat).

Notă: Ceva care s-ar putea să nu fie evident: modificatorul de plată se aplică numai apelurilor din contracte externe. Dacă apelez o funcție neplătibilă în funcția plătibilă din același contract, funcția neplătibilă nu va eșua, deși msg.value este încă setat.

Marcați în mod explicit vizibilitatea în funcții și variabile de stare

Etichetați în mod explicit vizibilitatea funcțiilor și a variabilelor de stare. Funcțiile pot fi specificate ca fiind externe, publice, interne sau private. Vă rugăm să înțelegeți diferențele dintre ele, de exemplu, extern poate fi suficient în loc de public. Pentru variabilele de stare, externul nu este posibil. Etichetarea vizibilității în mod explicit va face mai ușor să prindem ipoteze incorecte despre cine poate apela funcția sau accesa variabila.

  • Funcțiile externe fac parte din interfața contractului. O funcție externă f nu poate fi apelată intern (adică f () nu funcționează, dar aceasta.f () funcționează). Funcțiile externe sunt uneori mai eficiente atunci când primesc matrice mari de date.
  • Funcțiile publice fac parte din interfața contractului și pot fi apelate fie intern, fie prin mesaje. Pentru variabilele de stare publică, se generează o funcție getter automată (vezi mai jos).
  • Funcțiile interne și variabilele de stare pot fi accesate numai intern, fără a utiliza acest lucru.
  • Funcțiile private și variabilele de stare sunt vizibile numai pentru contract în care sunt definite și nu în contracte derivate. Notă: Tot ceea ce este în interiorul unui contract este vizibil pentru toți observatorii externi blockchain-ului, chiar și pentru variabilele private.

// bad uint x; // implicit este intern pentru variabilele de stare, dar ar trebui să fie făcută funcția explicită buy () {// implicit este public // cod public} // bun uint privat y; function buy () external {// apelabil numai extern sau folosind this.buy ()} function utility () public {// apelabil extern, precum și intern: schimbarea acestui cod necesită gândirea la ambele cazuri. } function internalAction () internal {// internal code} Limbaj cod: PHP (php)

Vedea SWC-100 și SWC-108

Blocați pragmele la versiunea specifică a compilatorului

Contractele ar trebui să fie implementate cu aceeași versiune a compilatorului și steaguri cu care au fost testate cel mai mult. Blocarea pragmei ajută la asigurarea faptului că contractele nu se desfășoară accidental folosind, de exemplu, cel mai recent compilator care poate avea riscuri mai mari de erori nedescoperite. Contractele pot fi, de asemenea, desfășurate de alții, iar pragma indică versiunea compilatorului intenționată de autorii originali.

// slabă soliditate a pragmei ^ 0.4.4; // soliditate bună a pragmei 0.4.4; Limbajul codului: JavaScript (javascript)

Notă: o versiune pragmatică flotantă (de exemplu, ^ 0.4.25) va compila bine cu 0.4.26-nightly.2018.9.25, cu toate acestea, build-urile nocturne nu ar trebui utilizate niciodată pentru a compila codul pentru producție.

Avertizare: Declarațiile Pragma pot fi permise să plutească atunci când un contract este destinat consumului de către alți dezvoltatori, ca în cazul contractelor dintr-o bibliotecă sau un pachet EthPM. În caz contrar, dezvoltatorul ar trebui să actualizeze manual pragma pentru a compila local.

Vedea SWC-103

Folosiți evenimente pentru a monitoriza activitatea contractului

Poate fi util să aveți o modalitate de a monitoriza activitatea contractului după ce a fost implementat. O modalitate de a realiza acest lucru este de a analiza toate tranzacțiile contractului, cu toate acestea, acestea pot fi insuficiente, deoarece apelurile de mesaje între contracte nu sunt înregistrate în blockchain. Mai mult decât atât, arată doar parametrii de intrare, nu modificările efective făcute la stare. De asemenea, evenimentele ar putea fi utilizate pentru a declanșa funcții în interfața cu utilizatorul.

contract Caritate {mapare (adresa => uint) solduri; funcție donate () public plătibil {solduri [msg.sender] + = msg.value; }} contract Joc {funcția buyCoins () public plătibil {// 5% merge la caritate charity.donate.value (msg.value / 20) (); }} Limba codului: JavaScript (javascript)

Aici, contractul de joc va efectua un apel intern către Charity.donate (). Această tranzacție nu va apărea în lista de tranzacții externe a Organizației de caritate, ci vizibilă doar în tranzacțiile interne.

Un eveniment este un mod convenabil de a înregistra ceva care s-a întâmplat în contract. Evenimentele emise rămân în blockchain împreună cu celelalte date contractuale și sunt disponibile pentru audit ulterior. Iată o îmbunătățire a exemplului de mai sus, folosind evenimente pentru a oferi un istoric al donațiilor caritabile.

contract Charity {// define event event LogDonate (uint _amount); mapare (adresa => uint) solduri; funcție donate () public plătibil {solduri [msg.sender] + = msg.value; // emit eveniment emit LogDonate (msg.value); }} contract Joc {funcția buyCoins () public plătibil {// 5% merge la caritate charity.donate.value (msg.value / 20) (); }} Limba codului: JavaScript (javascript)

Aici, toate tranzacțiile care trec prin contractul de caritate, fie direct, fie nu, vor apărea în lista de evenimente a acelui contract împreună cu suma de bani donați.

Notă: preferați construcțiile Solidity mai noi. Preferați construcții / pseudonime cum ar fi autodistrugerea (peste sinucidere) și keccak256 (peste sha3). Modele precum require (msg.sender.send (1 eter)) pot fi, de asemenea, simplificate pentru a utiliza transfer (), ca în msg.sender.transfer (1 eter). Verifică Jurnalul de schimbare a solidității pentru mai multe schimbări similare.

Rețineți că „Built-in-uri” pot fi umbrite

În prezent este posibil să umbră globali încorporați în Soliditate. Acest lucru permite contractelor să anuleze funcționalitatea încorporărilor, cum ar fi msg și revert (). Deși aceasta este destinat, poate induce în eroare utilizatorii unui contract cu privire la adevăratul comportament al contractului.

contract PretendingToRevert {function revert () constant intern {}} contract ExampleContract is PretendingToRevert {function somethingBad () public {revert (); }}

Utilizatorii contractului (și auditorii) ar trebui să fie conștienți de codul sursă complet al contractului inteligent al oricărei aplicații pe care intenționează să o utilizeze.

Evitați utilizarea tx.origin

Nu utilizați niciodată tx.origin pentru autorizare, un alt contract poate avea o metodă care vă va solicita contractul (în cazul în care utilizatorul are anumite fonduri, de exemplu), iar contractul dvs. va autoriza acea tranzacție, deoarece adresa dvs. este în tx.origin.

contract MyContract {proprietarul adresei; function MyContract () public {proprietar = msg.sender; } funcție sendTo (destinatar adresă, uint suma) public {require (tx.origin == proprietar); (succes bool,) = receiver.call.value (sumă) (""); cere (succes); }} contract AttackingContract {MyContract myContract; atacator de adresă; funcția AttackingContract (adresa myContractAddress) public {myContract = MyContract (myContractAddress); atacator = msg.sender; } function () public {myContract.sendTo (atacator, msg.sender.balance); }} Limba codului: JavaScript (javascript)

Ar trebui să utilizați msg.sender pentru autorizare (dacă un alt contract solicită contractul msg.sender va fi adresa contractului și nu adresa utilizatorului care a apelat contractul).

Puteți citi mai multe despre asta aici: Documente de soliditate

Avertizare: Pe lângă problema cu autorizare, există șansa ca tx.origin să fie eliminat din protocolul Ethereum în viitor, astfel încât codul care folosește tx.origin nu va fi compatibil cu versiunile viitoare. Vitalik: „NU presupuneți că tx.origin va continua să fie utilizabilă sau semnificativă.”

De asemenea, merită menționat faptul că, folosind tx.origin, limitați interoperabilitatea între contracte, deoarece contractul care folosește tx.origin nu poate fi utilizat de un alt contract, deoarece un contract nu poate fi tx.origin.

Vedea SWC-115

Dependența de timestamp

Există trei considerații principale atunci când se utilizează un timestamp pentru a executa o funcție critică într-un contract, mai ales atunci când acțiunile implică transferul de fonduri.

Manipularea marcajului de timp

Rețineți că marca de timp a blocului poate fi manipulată de un miner. Gandeste-te la asta contracta:

uint256 constant private salt = block.timestamp; function random (uint Max) rentabilități private constante (rezultat uint256) {// obțineți cea mai bună sămânță pentru randomitate uint256 x = sare * 100 / Max; uint256 y = sare * număr.bloc / (sare% 5); uint256 seed = block.number / 3 + (sare% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (seed)); returnează uint256 ((h / x))% Max + 1; // număr aleatoriu între 1 și Max} Limbaj cod: PHP (php)

Atunci când contractul folosește timestamp-ul pentru a însămânța un număr aleatoriu, minerul poate posta de fapt un timestamp în termen de 15 secunde de la validarea blocului, permițând în mod eficient minerului să pre-calculeze o opțiune mai favorabilă șanselor lor la loterie. Marcajele de timp nu sunt aleatorii și nu ar trebui utilizate în acest context.

Regula de 15 secunde

 Hârtie galbenă (Specificația de referință Ethereum) nu specifică o constrângere asupra cantității de blocuri care pot deriva în timp, dar specifică că fiecare marcaj de timp ar trebui să fie mai mare decât marcajul de timp al părintelui său. Implementări populare ale protocolului Ethereum Geth și Paritate ambele resping blocuri cu timestamp mai mult de 15 secunde în viitor. Prin urmare, o regulă bună în evaluarea utilizării marcajului de timp este: dacă scara evenimentului dvs. dependent de timp poate varia cu 15 secunde și poate menține integritatea, este sigur să utilizați un bloc..

Evitați să utilizați block.number ca marcaj de timp

Este posibil să se estimeze o deltă de timp folosind proprietatea block.number și timpul mediu de blocare, cu toate acestea, aceasta nu este o dovadă viitoare, deoarece timpii de blocare se pot modifica (cum ar fi reorganizări furcă si bomba de dificultate). Într-o vânzare care durează zile, regula de 15 secunde permite obținerea unei estimări mai fiabile a timpului.

Vedea SWC-116

Atenție la moștenire multiplă

Atunci când utilizați moștenirea multiplă în Solidity, este important să înțelegeți modul în care compilatorul compune graficul moștenirii.

contract Final {uint public a; function Final (uint f) public {a = f; }} contractul B este final {int taxă publică; function B (uint f) Final (f) public {} function setFee () public {fee = 3; }} contractul C este final {int taxă publică; function C (uint f) Final (f) public {} function setFee () public {fee = 5; }} contractul A este B, C {funcția A () publică B (3) C (5) {setFee (); }} Limbaj cod: PHP (php)

Atunci când se desfășoară un contract, compilatorul va liniaza moștenirea de la dreapta la stânga (după cuvântul cheie este părinții sunt enumerați de la cea mai de bază la cea mai derivată). Iată liniarizarea contractului A:

Final <- B <- C <- A

Consecința liniarizării va produce o taxă de 5, deoarece C este contractul cel mai derivat. Acest lucru poate părea evident, dar imaginați-vă scenarii în care C este capabil să ascundă funcții cruciale, să reordoneze clauze booleene și să determine dezvoltatorul să scrie contracte exploatabile. Analiza statică nu ridică în prezent probleme cu funcțiile umbrite, deci trebuie inspectată manual.

Pentru a contribui, Github Solidity are o proiect cu toate problemele legate de moștenire.

Vedea SWC-125

Utilizați tipul de interfață în locul adresei pentru siguranța tipului

Atunci când o funcție ia ca argument o adresă contractuală, este mai bine să treci o interfață sau un tip contractual decât o adresă brută. Dacă funcția este apelată în altă parte din codul sursă, compilatorul va oferi garanții suplimentare de siguranță de tip.

Aici vedem două alternative:

contract Validator {function validate (uint) returnări externe (bool); } contract TypeSafeAuction {// good function validateBet (Validator _validator, uint _value) returnări interne (bool) {bool valid = _validator.validate (_value); retur valabil; }} contract TypeUnsafeAuction {// funcție greșită validateBet (adresa _addr, uint _value) returnări interne (bool) {Validator validator = Validator (_addr); bool valid = validator.validate (_value); retur valabil; }} Limba codului: JavaScript (javascript)

Avantajele utilizării contractului TypeSafeAuction de mai sus pot fi apoi văzute din exemplul următor. Dacă validateBet () este apelat cu un argument de adresă sau un alt tip de contract decât Validator, compilatorul va arunca această eroare:

contract NonValidator {} Contract Licitație este TypeSafeAuction {NonValidator nonValidator; function bet (uint _value) {bool valid = validateBet (nonValidator, _value); // TypeError: tip nevalid pentru argument în apelul de funcție. // Conversie implicită nevalidă din contract NonValidator // în contract Validator solicitat. }} Limba codului: JavaScript (javascript)

Evitați să utilizați extcodesize pentru a verifica conturile deținute extern

Următorul modificator (sau o verificare similară) este adesea utilizat pentru a verifica dacă un apel a fost efectuat dintr-un cont deținut extern (EOA) sau dintr-un cont contractual:

// modificator rău isNotContract (adresa _a) {uint size; asamblare {size: = extcodesize (_a)} require (size == 0); _; } Limba codului: JavaScript (javascript)

Ideea este directă: dacă o adresă conține cod, nu este un EOA, ci un cont de contract. in orice caz, un contract nu are cod sursă disponibil în timpul construcției. Aceasta înseamnă că, în timp ce constructorul rulează, poate efectua apeluri către alte contracte, dar extcodesize pentru adresa sa returnează zero. Mai jos este un exemplu minim care arată cum poate fi eludată această verificare:

contract OnlyForEOA {uint flag public; // bad modificator isNotContract (address _a) {uint len; asamblarea {len: = extcodesize (_a)} require (len == 0); _; } function setFlag (uint i) public isNotContract (msg.sender) {flag = i; }} contract FakeEOA {constructor (adresa _a) public {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Limba codului: JavaScript (javascript)

Deoarece adresele contractului pot fi precalculate, această verificare ar putea eșua și dacă verifică o adresă care este goală la blocul n, dar care are un contract desfășurat la un bloc mai mare decât n.

Avertizare: Această problemă este nuanțată. Dacă obiectivul dvs. este să împiedicați alte contracte să vă poată apela contractul, verificarea extcodesize este probabil suficientă. O abordare alternativă este de a verifica valoarea lui (tx.origin == msg.sender), deși și aceasta are dezavantaje.

Pot exista și alte situații în care verificarea extcodesize vă servește scopului. Descrierea tuturor aici este în afara domeniului de aplicare. Înțelegeți comportamentele care stau la baza EVM și folosiți-vă judecata.

Codul dvs. Blockchain este sigur?

Rezervați o verificare la fața locului de o zi la experții noștri în securitate. Rezervați-vă astăzi Diligență Securitate Contracte inteligente Soliditate Buletin informativ Abonați-vă la newsletter-ul nostru pentru cele mai recente știri Ethereum, soluții pentru întreprinderi, resurse pentru dezvoltatori și multe altele. Adresa de e-mail Conținut exclusivCum să construiți un produs Blockchain de succesWebinar

Cum să construiți un produs Blockchain de succes

Cum se configurează și se execută un nod EthereumWebinar

Cum se configurează și se execută un nod Ethereum

Cum să vă construiți propriul API EthereumWebinar

Cum să vă construiți propriul API Ethereum

Cum să creați un simbol socialWebinar

Cum să creați un simbol social

Utilizarea instrumentelor de securitate în dezvoltarea contractelor inteligenteWebinar

Utilizarea instrumentelor de securitate în dezvoltarea contractelor inteligente

Viitorul activelor digitale și al DeFi-ului financiarWebinar

Viitorul finanțelor: active digitale și DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me