Solidity najbolji primjeri za zaštitu pametnih ugovora

blog 1NewsDevelopersEnterpriseBlockchain ObjašnjeniDogađaji i konferencijePressBilteni

Contents

Pretplatite se na naše obavijesti.

Email adresa

Poštujemo vašu privatnost

Razvoj HomeBlogBlockchaina

Solidity Best Practices za zaštitu pametnih ugovora

Od praćenja do razmatranja vremenskog žiga, evo nekoliko profesionalnih savjeta kako biste osigurali da vaši Ethereum pametni ugovori budu ojačani. od ConsenSys 21. kolovoza 2020. Objavljeno 21. kolovoza 2020

solidnost junak najbolje prakse

ConsenSys Diligence, naš tim stručnjaka za blockchain sigurnost.

Ako ste srcu priuštili sigurnosni način razmišljanja o pametnom ugovoru i pomalo se snalazite u osobinama EVM-a, vrijeme je da razmotrite neke sigurnosne obrasce koji su specifični za programski jezik Solidity. U ovom pregledu usredotočit ćemo se na sigurne razvojne preporuke za Solidity koje također mogu biti poučne za razvoj pametnih ugovora na drugim jezicima. 

Ok, uskočimo.

Koristite assert (), zahtijevajte (), vratite () pravilno

Pogodnosti funkcije tvrditi i zahtijevati može se koristiti za provjeru uvjeta i izbacivanje iznimke ako uvjet nije zadovoljen.

The tvrditi Funkcija se smije koristiti samo za testiranje unutarnjih pogrešaka i provjeru invarijanata.

The zahtijevati funkcija bi se trebala koristiti za osiguravanje valjanih uvjeta, poput ulaza ili varijabli stanja ugovora, ili za provjeru povratnih vrijednosti iz poziva na vanjske ugovore. 

Slijeđenje ove paradigme omogućuje formalne alate za analizu da provjere da li se nevažeći opcode nikad ne može postići: što znači da se ne krše invarijante u kodu i da je kôd formalno provjeren.

čvrstoća pragme ^ 0,5,0; ugovor dijelilac {funkcija sendHalf (adresa koja se plaća) javni povrat koji se plaća (uint stanje) {zahtijeva (msg.value% 2 == 0, "Potrebna čak i vrijednost."); // Require () može imati neobavezni niz poruka uint balanceBeforeTransfer = address (this) .balance; (uspjeh bool-a,) = addr.call.value (msg.value / 2) (""); zahtijevati (uspjeh); // Budući da smo se vratili ako prijenos nije uspio, ne bi trebao postojati // način da i dalje imamo pola novca. assert (adresa (ovo) .balance == balanceBeforeTransfer – msg.value / 2); // koristi se za internu provjeru pogreške povratne adrese (this) .balance; }} Jezik koda: JavaScript (javascript)

Vidjeti SWC-110 & SWC-123

Koristite modifikatore samo za provjere

Kôd unutar modifikatora obično se izvršava prije tijela funkcije, pa će sve promjene stanja ili vanjski pozivi kršiti Provjere-Učinci-Interakcije uzorak. Štoviše, ove izjave programer također mogu ostati neprimijećeni, jer kôd modifikatora može biti daleko od deklaracije funkcije. Na primjer, vanjski poziv u modifikatoru može dovesti do ponovnog napada:

registar ugovora {adresa vlasnika; funkcija isVoter (adresa _addr) eksterni povrat (bool) {// Kod}} ugovor Izbor {Registar registra; modifikator jeEligible (adresa _addr) {zahtijeva (registry.isVoter (_addr)); _; } function vote () isEligible (msg.sender) public {// Code}} Jezik koda: JavaScript (javascript)

U ovom slučaju, ugovor o Registru može izvršiti napad na ponovni pristup pozivom Election.vote () unutar isVoter ().

Bilješka: Koristiti modifikatori za zamjenu dvostrukih provjera stanja u više funkcija, kao što je isOwner (), u suprotnom upotrijebite require ili return unutar funkcije. To čini vaš kôd pametnog ugovora čitljivijim i lakšim za reviziju.

Pazite na zaokruživanje s cijelom podjelom

Sva cijela podjela zaokružuje se na najbliži cijeli broj. Ako vam je potrebna veća preciznost, razmislite o upotrebi množitelja ili spremite brojnik i nazivnik.

(U budućnosti će Solidity imati fiksna točka tip, što će to olakšati.)

// loša uint x = 5/2; // Rezultat je 2, sva cjelovita podjela zaokružuje DOLJE do najbližeg jezika cijelog broja: JavaScript (javascript)

Korištenje množitelja sprječava zaokruživanje prema dolje, ovaj množitelj treba uzeti u obzir pri radu s x u budućnosti:

// dobar uint multiplikator = 10; uint x = (5 * množitelj) / 2; Jezik koda: JavaScript (javascript)

Pohranjivanje brojnika i nazivnika znači da možete izračunati rezultat brojača / nazivnika izvan lanca:

// dobar uint brojnik = 5; nazivnik uint = 2; Jezik koda: JavaScript (javascript)

Budite svjesni kompromisa između apstraktni ugovori i sučelja

I sučelja i apstraktni ugovori pružaju prilagodljiv i ponovljiv pristup pametnim ugovorima. Sučelja koja su uvedena u Solidity 0.4.11 slična su apstraktnim ugovorima, ali ne mogu imati implementirane funkcije. Sučelja također imaju ograničenja, poput nemogućnosti pristupa pohrani ili nasljeđivanja s drugih sučelja, što apstraktne ugovore obično čini praktičnijima. Iako su sučelja zasigurno korisna za dizajniranje ugovora prije implementacije. Uz to, važno je imati na umu da ako ugovor nasljeđuje apstraktni ugovor, mora implementirati sve neprovedene funkcije preko nadjačavanja ili će također biti apstraktan.

Rezervne funkcije

Neka jednostavne zamjenske funkcije budu jednostavne

Rezervne funkcije pozivaju se kada se ugovoru pošalje poruka bez argumenata (ili kad se nijedna funkcija ne podudara) i ima pristup 2.300 benzina samo kada se pozove iz .send () ili .transfer (). Ako želite primati eter iz .send () ili .transfer (), najviše što možete učiniti u zamjenskoj funkciji je bilježenje događaja. Koristite odgovarajuću funkciju ako je potrebno izračunavanje više plina.

// loša funkcija () plativo {salda [msg.sender] + = msg.value; } // // depozit dobre funkcije () koji se plaća vanjski {salda [msg.sender] + = msg.value; } function () plativo {zahtijeva (msg.data.length == 0); emitirati LogDepositReceived (msg.sender); } Jezik koda: JavaScript (javascript)

Provjerite duljinu podataka u zamjenskim funkcijama

Budući da je zamjenske funkcije nije pozvan samo za prijenos običnog etera (bez podataka), već i kada se nijedna druga funkcija ne podudara, trebali biste provjeriti jesu li podaci prazni ako je zamjenska funkcija namijenjena upotrebi samo u svrhu evidentiranja primljenog etera. U suprotnom, pozivatelji neće primijetiti ako se vaš ugovor koristi pogrešno i pozivaju li se funkcije koje ne postoje.

// loša funkcija () plativo {emit LogDepositReceived (msg.sender); } // dobra funkcija () plativo {zahtijeva (msg.data.length == 0); emitirati LogDepositReceived (msg.sender); } Jezik koda: JavaScript (javascript)

Izričito označite funkcije koje se plaćaju i varijable stanja

Polazeći od Solidity 0.4.0, svaka funkcija koja prima eter mora koristiti modifikator koji se plaća, u suprotnom ako transakcija ima msg.value > 0 će se vratiti (osim kad je prisiljen).

Bilješka: Nešto što možda nije očito: Modifikator koji se plaća odnosi se samo na pozive iz vanjskih ugovora. Ako u istom ugovoru pozovem funkciju koja se ne plaća u funkciji koja se plaća, funkcija koja se ne plaća neće uspjeti, iako je msg.value i dalje postavljena.

Eksplicitno označite vidljivost u funkcijama i varijablama stanja

Izričito označite vidljivost funkcija i varijabli stanja. Funkcije se mogu odrediti kao vanjske, javne, unutarnje ili privatne. Molimo vas da razumijete razlike među njima, na primjer, vanjske mogu biti dovoljne umjesto javnih. Za varijable stanja eksterno nije moguće. Izričito označavanje vidljivosti olakšat će uhvatiti netočne pretpostavke o tome tko može pozvati funkciju ili pristupiti varijabli.

  • Vanjske funkcije dio su ugovornog sučelja. Vanjska funkcija f ne može se interno pozvati (tj. F () ne radi, ali ovo.f () radi). Vanjske su funkcije ponekad učinkovitije kad primaju velike nizove podataka.
  • Javne funkcije dio su ugovornog sučelja i mogu se pozivati ​​interno ili putem poruka. Za varijable javnog stanja generira se funkcija automatskog dobivanja (vidi dolje).
  • Internim funkcijama i varijablama stanja može se pristupiti samo interno, bez upotrebe ove funkcije.
  • Privatne funkcije i državne varijable vidljive su samo za ugovor u kojem su definirane, a ne u izvedenim ugovorima. Bilješka: Sve što je unutar ugovora vidljivo je svim promatračima izvan blockchaina, čak i privatnim varijablama.

// loša uint x; // zadana vrijednost je interna za varijable stanja, ali to treba učiniti eksplicitnom funkcijom buy () {// zadana vrijednost je javna // javni kod} // // dobra uint privatna y; function buy () external {// samo se može pozivati ​​izvana ili pomoću this.buy ()} uslužni program function () public {// poziva se izvana, kao i iznutra: promjena ovog koda zahtijeva razmišljanje o oba slučaja. } function internalAction () interni {// interni kod} Jezik koda: PHP (php)

Vidjeti SWC-100 i SWC-108

Zaključajte pragme za određenu verziju kompajlera

Ugovori bi trebali biti raspoređeni s istom verzijom prevoditelja i zastavicama s kojima su najviše testirani. Zaključavanje pragme osigurava da se ugovori slučajno ne primene pomoću, na primjer, najnovijeg prevoditelja koji može imati veći rizik od neotkrivenih bugova. Ugovore mogu razmještati i drugi, a pragma ukazuje na verziju kompajlera koju su namijenili originalni autori.

// loša čvrstoća pragme ^ 0,4,4; // dobra pragma solidnost 0.4.4; Jezik koda: JavaScript (javascript)

Napomena: plutajuća verzija pragme (tj. ^ 0.4.25) kompajlirat će se u redu s 0.4.26-nightly.2018.9.25, međutim noćne gradnje nikada ne bi trebale biti korištene za kompajliranje koda za produkciju.

Upozorenje: Izjave Pragme mogu se plivati ​​kada je ugovor namijenjen za konzumiranje drugim programerima, kao u slučaju s ugovorima u knjižnici ili EthPM paketu. U suprotnom, programer bi trebao ručno ažurirati pragmu kako bi lokalno kompajlirao.

Vidjeti SWC-103

Koristite događaje za praćenje ugovornih aktivnosti

Može biti korisno imati način praćenja aktivnosti ugovora nakon njegove primjene. Jedan od načina da se to postigne je sagledavanje svih transakcija ugovora, međutim to može biti nedovoljno, jer pozivi poruka između ugovora nisu zabilježeni u blockchainu. Štoviše, prikazuje samo ulazne parametre, a ne stvarne promjene koje se vrše u stanju. Također bi se događaji mogli koristiti za pokretanje funkcija u korisničkom sučelju.

ugovor Dobrotvorna organizacija {mapiranje (adresa => uint) ravnoteže; funkcija doniraj () plativo javno {salda [msg.sender] + = msg.value; }} ugovorna igra {funkcija buyCoins () plativo javno {// 5% ide u dobrotvorne svrhe charity.donate.value (msg.value / 20) (); }} Jezik koda: JavaScript (javascript)

Ovdje će ugovor o igri uputiti interni poziv na Charity.donate (). Ova se transakcija neće pojaviti na vanjskom popisu dobrotvornih organizacija, već će biti vidljiva samo u internim transakcijama.

Događaj je prikladan način da se zabilježi nešto što se dogodilo u ugovoru. Emitirani događaji ostaju u blockchainu zajedno s ostalim podacima o ugovoru i dostupni su za buduću reviziju. Evo poboljšanja u gornjem primjeru, koristeći događaje kako bismo pružili povijest donacija dobrotvorne organizacije.

ugovor Dobrotvorna organizacija {// definiranje događaja događaja LogDonate (uint _amount); mapiranje (adresa => uint) ravnoteže; funkcija doniraj () plativo javno {salda [msg.sender] + = msg.value; // emitiranje događaja emitiranje LogDonate (msg.value); }} ugovorna igra {funkcija buyCoins () plativo javno {// 5% ide u dobrotvorne svrhe.donate.value (msg.value / 20) (); }} Jezik koda: JavaScript (javascript)

Ovdje će se sve transakcije koje prođu kroz dobrotvorni ugovor, izravno ili ne, prikazati na popisu događaja tog ugovora zajedno s iznosom doniranog novca.

Napomena: Dajte prednost novijim konstrukcijama Solidity. Preferirajte konstrukte / pseudonime kao što su samouništenje (zbog samoubojstva) i keccak256 (preko sha3). Obrasci poput require (msg.sender.send (1 eter)) također se mogu pojednostaviti korištenjem transfer (), kao u msg.sender.transfer (1 eter). Provjeri Dnevnik promjene čvrstoće za više sličnih promjena.

Imajte na umu da ‘Ugrađeni’ mogu biti zasjenjeni

Trenutno je moguće sjena ugrađeni globalni u Solidity. To omogućuje ugovorima da nadjačaju funkcionalnost ugrađenih datoteka, poput msg i revert (). Iako ovo je namijenjen, može zavarati korisnike ugovora u pogledu stvarnog ponašanja ugovora.

ugovor PretendingToRevert {funkcija revert () interna konstanta {}} ugovor ExampleContract je PretendingToRevert {funkcija somethingBad () public {revert (); }}

Korisnici ugovora (i revizori) trebaju biti svjesni punog izvornog koda pametnog ugovora bilo koje aplikacije koju namjeravaju koristiti.

Izbjegavajte upotrebu tx.origin

Nikada nemojte koristiti tx.origin za autorizaciju, drugi ugovor može imati metodu koja će pozvati vaš ugovor (gdje korisnik na primjer ima neka sredstva), a vaš će ugovor odobriti tu transakciju jer je vaša adresa u tx.origin.

ugovor MyContract {adresa vlasnika; funkcija MyContract () public {owner = msg.sender; } funkcija sendTo (primatelj adrese, uint iznos) public {zahtijeva (tx.origin == vlasnik); (uspjeh bool-a,) = primatelj.call.value (iznos) (""); zahtijevati (uspjeh); }} ugovor AttackingContract {MyContract myContract; adresa napadač; funkcija AttackingContract (adresa myContractAddress) public {myContract = MyContract (myContractAddress); napadač = msg.sender; } function () public {myContract.sendTo (napadač, msg.sender.balance); }} Jezik koda: JavaScript (javascript)

Za autorizaciju biste trebali koristiti msg.sender (ako drugi ugovor poziva vaš ugovor, msg.sender će biti adresa ugovora, a ne adresa korisnika koji je pozvao ugovor).

Više o tome možete pročitati ovdje: Dokumenti o solidnosti

Upozorenje: Osim problema s autorizacijom, postoji vjerojatnost da će tx.origin u budućnosti biti uklonjen iz protokola Ethereum, tako da kôd koji koristi tx.origin neće biti kompatibilan s budućim izdanjima Vitalik: ‘NEMOJTE pretpostavljati da će tx.origin i dalje biti upotrebljiv ili smislen.’

Također je vrijedno spomenuti da upotrebom tx.origin ograničavate interoperabilnost između ugovora jer ugovor koji koristi tx.origin ne može se koristiti drugim ugovorom jer ugovor ne može biti tx.origin.

Vidjeti SWC-115

Ovisnost o vremenskoj oznaci

Tri su glavna razmatranja pri korištenju vremenske oznake za izvršavanje kritične funkcije u ugovoru, posebno kada radnje uključuju prijenos sredstava.

Manipulacija vremenskom oznakom

Imajte na umu da rudar može upravljati vremenskom oznakom bloka. Razmislite o ovome ugovor:

uint256 konstantna privatna sol = block.timestamp; funkcija random (uint Max) konstantni privatni povrat (rezultat uint256) {// dobiti najbolje sjeme za slučajnost uint256 x = sol * 100 / Max; uint256 y = sol * blok.broj / (sol% 5); uint256 sjeme = blok.broj / 3 + (sol% 300) + Last_Payout + y; uint256 h = uint256 (blok.blockhash (sjeme)); povrat uint256 ((h / x))% Max + 1; // nasumični broj između 1 i Max} Jezik koda: PHP (php)

Kada ugovor koristi vremensku oznaku za postavljanje slučajnog broja, rudar zapravo može objaviti vremensku oznaku u roku od 15 sekundi od validacije bloka, čime učinkovito omogućuje rudaru da preračuna mogućnost povoljniju za njihove šanse u lutriji. Vremenske oznake nisu slučajne i ne bi se trebale koristiti u tom kontekstu.

Pravilo od 15 sekundi

The Žuti papir (Ethereumova referentna specifikacija) ne navodi ograničenje koliko blokova može odnijeti u vremenu, ali to precizira da bi svaka vremenska oznaka trebala biti veća od vremenske oznake svog roditelja. Popularne implementacije protokola Ethereum Geth i Paritet obojica u budućnosti odbijaju blokove s vremenskom oznakom duljom od 15 sekundi. Stoga je dobro pravilo za procjenu upotrebe vremenske oznake sljedeće: ako se razmjera vašeg vremenski ovisnog događaja može razlikovati za 15 sekundi i održava integritet, sigurno je koristiti block.timestamp.

Izbjegavajte koristiti block.number kao vremensku oznaku

Moguće je procijeniti vremensku deltu pomoću svojstva block.number i prosječno vrijeme bloka, međutim to nije budući dokaz jer se blok vremena mogu mijenjati (kao npr reorganizacije vilica i poteškoća bomba). U danima prodaje, pravilo od 15 sekundi omogućuje postizanje pouzdanije procjene vremena.

Vidjeti SWC-116

Oprez višestrukog nasljeđivanja

Kada koristite višestruko nasljeđivanje u Solidity, važno je razumjeti kako kompajler sastavlja graf nasljeđivanja.

ugovor konačan {uint public a; funkcija Final (uint f) public {a = f; }} ugovor B je Konačan {int javna naknada; funkcija B (uint f) Konačna (f) javna {} funkcija setFee () javna {naknada = 3; }} ugovor C je Konačan {int javna naknada; funkcija C (uint f) Konačna (f) javna {} funkcija setFee () javna {naknada = 5; }} ugovor A je B, C {funkcija A () javna B (3) C (5) {setFee (); }} Jezik koda: PHP (php)

Kada se ugovor primijeni, sastavljač će nasljedstvo linearizirati s desna na lijevo (nakon što je ključna riječ roditelji su navedeni od najobičnijih do najizvedenijih). Evo linearizacije ugovora A:

Konačno <- B <- C <- A

Posljedica linearizacije rezultirat će vrijednošću naknade od 5, jer je C najviše izvedeni ugovor. To se može činiti očitim, ali zamislite scenarije u kojima C može zasjeniti ključne funkcije, preurediti logičke klauzule i natjerati programera da napiše ugovore koji se mogu iskoristiti. Statička analiza trenutno ne pokreće problem zasjenjenih funkcija, pa se mora ručno pregledati.

Kao pomoć u doprinosu, Solidity’s Github ima projekt sa svim pitanjima vezanim uz nasljedstvo.

Vidjeti SWC-125

Za sigurnost tipa koristite vrstu sučelja umjesto adrese

Kada funkcija uzima adresu ugovora kao argument, bolje je proslijediti sučelje ili vrstu ugovora, a ne sirovu adresu. Ako se funkcija poziva negdje drugdje unutar izvornog koda, kompajler će pružiti dodatna sigurnosna jamstva tipa.

Ovdje vidimo dvije alternative:

ugovor za provjeru valjanosti {funkcija potvrđuje (uint) vanjske povratke (bool); } ugovor TypeSafeAuction {// dobra funkcija validateBet (Validator _validator, uint _value) interni povrat (bool) {bool valid = _validator.validate (_value); povratak valjan; }} ugovor TypeUnsafeAuction {// loša funkcija validateBet (adresa _addr, uint _value) interni povrat (bool) {Validator validator = Validator (_addr); bool vrijedi = validator.validate (_value); povratak valjan; }} Jezik koda: JavaScript (javascript)

Prednosti korištenja gore navedenog ugovora TypeSafeAuction mogu se vidjeti iz sljedećeg primjera. Ako se validateBet () pozove s argumentom adrese ili tipom ugovora koji nije Validator, kompajler će izbaciti ovu pogrešku:

ugovor NonValidator {} ugovor Aukcija je TypeSafeAuction {NonValidator nonValidator; ulog funkcije (uint _value) {bool valid = validateBet (nonValidator, _value); // TypeError: Neispravan tip za argument u pozivu funkcije. // Nevažeća implicitna pretvorba iz ugovora NonValidator // u ugovorni zahtjev za provjeru valjanosti. }} Jezik koda: JavaScript (javascript)

Izbjegavajte upotrebu extcodesize za provjeru računa u vanjskom vlasništvu

Sljedeći modifikator (ili slična provjera) često se koristi za provjeru je li poziv upućen s računa u vanjskom vlasništvu (EOA) ili ugovornog računa:

// loši modifikator isNotContract (adresa _a) {veličina uinta; sklop {size: = extcodesize (_a)} require (size == 0); _; } Jezik koda: JavaScript (javascript)

Ideja je jasna: ako adresa sadrži kôd, to nije EOA, već ugovorni račun. Međutim, ugovor nema izvorni kod dostupan tijekom gradnje. To znači da dok konstruktor radi, može upućivati ​​pozive drugim ugovorima, ali extcodesize za svoju adresu vraća nulu. Ispod je minimalni primjer koji pokazuje kako se ovu provjeru može zaobići:

ugovor OnlyForEOA {uint javna zastava; // loši modifikator jeNotContract (adresa _a) {uint len; montaža {len: = extcodesize (_a)} require (len == 0); _; } funkcija setFlag (uint i) public isNotContract (msg.sender) {flag = i; }} ugovor FakeEOA {konstruktor (adresa _a) public {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Jezik koda: JavaScript (javascript)

Budući da se adrese ugovora mogu unaprijed izračunati, ova provjera može propasti i ako provjeri adresu koja je prazna u bloku n, ali koja ima ugovor raspoređen na nekom bloku većem od n.

Upozorenje: Ovo je pitanje iznijansirano. Ako je vaš cilj spriječiti da drugi ugovori mogu nazvati vaš ugovor, provjera extcodesize vjerojatno je dovoljna. Alternativni pristup je provjera vrijednosti (tx.origin == msg.sender), iako i ovo ima nedostataka.

Mogu biti i druge situacije u kojima provjera extcodesize služi vašoj svrsi. Opis svih njih ovdje je izvan opsega. Shvatite osnovno ponašanje EVM-a i upotrijebite svoju prosudbu.

Je li vaš Blockchain kôd siguran?

Rezervirajte jednodnevnu provjeru na licu mjesta kod naših sigurnosnih stručnjaka. Rezervirajte svoje danas DiligenceSecuritySmart ContractsSolidityNewsletter Pretplatite se na naš bilten za najnovije vijesti o Ethereumu, rješenja za poduzeća, resurse za programere i još mnogo toga. Adresa e-pošte Ekskluzivni sadržajKako izraditi uspješan blockchain proizvodWebinar

Kako izraditi uspješan blockchain proizvod

Kako postaviti i pokrenuti Ethereum čvorWebinar

Kako postaviti i pokrenuti Ethereum čvor

Kako izraditi vlastiti Ethereum APIWebinar

Kako izraditi vlastiti Ethereum API

Kako stvoriti društveni žetonWebinar

Kako stvoriti društveni žeton

Korištenje sigurnosnih alata u razvoju pametnih ugovoraWebinar

Korištenje sigurnosnih alata u razvoju pametnih ugovora

Budućnost financija Digitalna imovina i DeFiWebinar

Budućnost financija: digitalna imovina i DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
map