Najboljše prakse Solidity za varnost pametnih pogodb

blog 1NewsDevelopersEnterpriseBlockchain ExplainedEvent and ConferencesPressGlasila

Contents

Naročite se na naše novice.

Email naslov

Spoštujemo vašo zasebnost

DomovBlogBlockchain razvoj

Najboljše prakse Solidity za varnost pametnih pogodb

Tukaj je nekaj profesionalnih nasvetov, od spremljanja do premislekov o časovnem žigu, da zagotovite, da so vaše pametne pogodbe Ethereum okrepljene. avtor ConsenSys 21. avgust 2020 Objavljeno 21. avgusta 2020

solidnost najboljše prakse junak

S strani ConsenSys Diligence, naše ekipe strokovnjakov za varnost veriženja blokov.

Če ste si pametno pogodbeno varnostno miselnost vzeli k srcu in se spopadate s svojimi posebnostmi EVM, je čas, da razmislite o nekaterih varnostnih vzorcih, ki so značilni za programski jezik Solidity. V tem poročilu se bomo osredotočili na varna razvojna priporočila za Solidity, ki so lahko tudi poučna za razvoj pametnih pogodb v drugih jezikih. 

V redu, vstopimo.

Uporabite assert (), zahtevaj (), razveljavi () pravilno

Priročne funkcije trditi in zahtevajo se lahko uporablja za preverjanje pogojev in vrnitev izjeme, če pogoj ni izpolnjen.

The trditi Funkcija naj se uporablja samo za preskušanje notranjih napak in za preverjanje invariantov.

The zahtevajo funkcijo je treba uporabiti za zagotovitev veljavnih pogojev, kot so vhodi ali spremenljivke stanja pogodbe, ali za potrditev povratnih vrednosti iz klicev na zunanje pogodbe. 

Sledenje tej paradigmi omogoča, da orodja za formalno analizo preverijo, da neveljavne kode optike ni mogoče nikoli doseči: kar pomeni, da v kodi niso kršene nobene invariante in je koda formalno preverjena.

trdnost pragme ^ 0,5,0; pogodba Sharer {funkcija sendHalf (naslov plačljiv addr) javni plačljivi donos (uint stanje) {zahteva (msg.value% 2 == 0, "Potrebna je celo vrednost."); // Require () ima lahko neobvezen niz sporočil uint balanceBeforeTransfer = address (this) .balance; (uspeh bool,) = addr.call.value (msg.value / 2) (""); zahtevati (uspeh); // Ker smo se vrnili, če prenos ni uspel, ne bi smeli // imeti nobene možnosti, da bi še vedno imeli polovico denarja. assert (naslov (ta) .balance == balanceBeforeTransfer – msg.value / 2); // uporablja se za notranje preverjanje napak povratnega naslova (this) .balance; }} Jezik kode: JavaScript (javascript)


Glej SWC-110 & SWC-123

Uporabite modifikatorje samo za preverjanje

Koda znotraj modifikatorja se običajno izvede pred telesom funkcije, zato bodo kakršne koli spremembe stanja ali zunanji klici kršili Preverjanja-učinki-interakcije vzorec. Poleg tega lahko te izjave razvijalci ostanejo tudi neopaženi, saj je koda za modifikator daleč od izjave funkcije. Na primer, zunanji klic v modifikatorju lahko privede do ponovnega napada:

pogodbeni register {lastnik naslova; funkcija isVoter (naslov _addr) zunanji vrnitve (bool) {// koda}} pogodba volitve {register registra; modifikator jeEligible (naslov _addr) {zahteva (register.isVoter (_addr)); _; } funkcija voice () isEligibly (msg.sender) public {// Code}} Jezik kode: JavaScript (javascript)

V tem primeru lahko pogodba Registra izvede napad na ponovni vstop s klicem Election.vote () znotraj isVoter ().

Opomba: Uporaba modifikatorji za nadomestitev podvojenih preverjanj pogojev v več funkcijah, kot je isOwner (), sicer uporabite funkcijo Zahtevaj ali Vrni znotraj funkcije. Tako je koda pametne pogodbe bolj berljiva in enostavnejša za revizijo.

Pazite na zaokroževanje s celoštevilčno delitvijo

Vsa celoštevilčna delitev se zaokroži na najbližje celo število. Če potrebujete večjo natančnost, razmislite o uporabi množitelja ali shranite števec in imenovalec.

(V prihodnosti bo Solidity imel s fiksno točko vrsta, ki bo to olajšala.)

// slab uint x = 5/2; // Rezultat je 2, vsa celoštevilska delitev zaokroži DOL do najbližjega jezika celoštevilčne kode: JavaScript (javascript)

Uporaba multiplikatorja preprečuje zaokroževanje navzdol, ta multiplikator je treba upoštevati pri prihodnjem delu z x:

// dober množitelj uint = 10; uint x = (množitelj 5 *) / 2; Jezik kode: JavaScript (javascript)

Shranjevanje števca in imenovalca pomeni, da lahko izračunate rezultat števca / imenovalca zunaj verige:

// dober števec uint = 5; imenovalec uint = 2; Jezik kode: JavaScript (javascript)

Zavedajte se kompromisov med abstraktne pogodbe in vmesniki

Tako vmesniki kot abstraktne pogodbe nudijo prilagodljiv in ponovno uporabljiv pristop za pametne pogodbe. Vmesniki, ki so bili predstavljeni v Solidity 0.4.11, so podobni abstraktnim pogodbam, vendar ne morejo izvajati nobenih funkcij. Vmesniki imajo tudi omejitve, na primer nezmožnost dostopa do pomnilnika ali podedovanja iz drugih vmesnikov, zaradi česar so abstraktne pogodbe na splošno bolj praktične. Čeprav so vmesniki zagotovo koristni za oblikovanje pogodb pred izvedbo. Poleg tega je pomembno upoštevati, da mora pogodba, če podeduje abstraktno pogodbo, izvajati vse neizvedene funkcije s preglasitvijo, sicer pa bo tudi abstraktna.

Nadomestne funkcije

Naj bodo preproste nadomestne funkcije

Nadomestne funkcije se pokličejo, ko se pogodbi pošlje sporočilo brez argumentov (ali kadar se nobena funkcija ne ujema) in ima dostop do 2.300 plinov le, če ga pokličete iz .send () ali .transfer (). Če želite prejemati eter iz .send () ali .transfer (), lahko največ, kar lahko storite v nadomestni funkciji, zabeleži dogodek. Če je potrebno izračunati več plina, uporabite pravilno funkcijo.

// slabo delovanje () plačljivo {salda [msg.sender] + = msg.value; } // dobro depozit funkcije () plačljivo zunanje {saldo [msg.sender] + = msg.value; } funkcija () plačljivo {zahteva (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Jezik kode: JavaScript (javascript)

Preverite dolžino podatkov v nadomestnih funkcijah

Ker je nadomestne funkcije ni potreben samo za navadne eter prenose (brez podatkov), ampak tudi, če se nobena druga funkcija ne ujema, preverite, ali so podatki prazni, če je nadomestna funkcija namenjena samo beleženju prejetega etra. V nasprotnem primeru kličoči ne bodo opazili, če se vaša pogodba uporablja nepravilno in se pokličejo funkcije, ki ne obstajajo.

// slaba funkcija () plačljivo {emit LogDepositReceived (msg.sender); } // dobra funkcija () plačljivo {require (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Jezik kode: JavaScript (javascript)

Izrecno označite plačljive funkcije in spremenljivke stanja

Od Solidity 0.4.0 mora vsaka funkcija, ki prejema eter, uporabiti plačljivi modifikator, sicer če ima transakcija vrednost msg.value > 0 se bo vrnilo (razen kadar je prisiljena).

Opomba: Nekaj, kar morda ni očitno: Plačljivi modifikator velja samo za klice iz zunanjih pogodb. Če v isti pogodbi pokličem neplačljivo funkcijo v plačljivi funkciji, neplačljiva funkcija ne bo uspela, čeprav je vrednost msg.value še vedno nastavljena.

Izrecno označite vidnost v funkcijah in spremenljivkah stanja

Izrecno označite vidnost funkcij in spremenljivk stanja. Funkcije lahko določite kot zunanje, javne, notranje ali zasebne. Prosimo, razumejte razlike med njimi, na primer zunanja morda zadostuje namesto javne. Za spremenljivke stanja zunanja ni mogoča. Če izrecno označite vidnost, boste lažje ujeli napačne predpostavke o tem, kdo lahko pokliče funkcijo ali dostopa do spremenljivke.

  • Zunanje funkcije so del pogodbenega vmesnika. Zunanje funkcije f ni mogoče poklicati znotraj (tj. F () ne deluje, toda this.f () deluje). Zunanje funkcije so včasih učinkovitejše, ko prejemajo velike nize podatkov.
  • Javne funkcije so del pogodbenega vmesnika in jih je mogoče poklicati interno ali prek sporočil. Za spremenljivke javnega stanja se ustvari funkcija samodejnega pridobivanja (glej spodaj).
  • Do internih funkcij in spremenljivk stanja je mogoče dostopati samo interno, ne da bi to uporabljali.
  • Zasebne funkcije in državne spremenljivke so vidne samo za pogodbo, v kateri so opredeljene, in ne v izpeljanih pogodbah. Opomba: Vse, kar je znotraj pogodbe, je vidno vsem opazovalcem zunaj verige blokov, tudi zasebnim spremenljivkam.

// slab uint x; // privzeta vrednost je notranja za državne spremenljivke, vendar mora biti eksplicitna funkcija buy () {// privzeta vrednost je javna // javna koda} // dobra uint private y; function buy () external {// samo klicno zunaj ali z uporabo this.buy ()} pripomoček za funkcije () public {// callable zunaj, pa tudi znotraj: za spremembo te kode je treba razmisliti o obeh primerih. } function internalAction () internal {// Internal code} Jezik kode: PHP (php)

Glej SWC-100 in SWC-108

Zaklenite pragme na določeno različico prevajalnika

Pogodbe je treba razviti z isto različico prevajalnika in zastavicami, s katerimi so bile najbolj preizkušene. Zaklepanje pragme pomaga zagotoviti, da se pogodbe nenamerno uvedejo z uporabo, na primer, najnovejšega prevajalnika, ki ima lahko večje tveganje za neodkrite napake. Pogodbe lahko oddajajo tudi drugi, pragma pa označuje različico prevajalnika, ki so jo predvideli izvirni avtorji.

// slaba trdnost pragme ^ 0,4,4; // dobra pragma trdnost 0.4.4; Jezik kode: JavaScript (javascript)

Opomba: plavajoča različica pragme (tj. ^ 0.4.25) bo dobro prevedena z 0.4.26-nightly.2018.9.25, vendar nočnih zgradb nikoli ne bi smeli uporabljati za prevajanje kode za produkcijo.

Opozorilo: Izjave Pragma lahko plujejo, kadar je pogodba namenjena porabi drugih razvijalcev, kot v primeru pogodb v knjižnici ali paketu EthPM. V nasprotnem primeru bi moral razvijalec ročno posodobiti pragmo, da bi lahko lokalno prevedel.

Glej SWC-103

Uporabite dogodke za spremljanje pogodbenih dejavnosti

Koristno je imeti način za spremljanje dejavnosti pogodbe po uvedbi. Eden od načinov, kako to doseči, je pregled vseh transakcij pogodbe, vendar to morda ne bo zadostovalo, saj klici sporočil med pogodbami niso zabeleženi v verigi blokov. Poleg tega prikazuje le vhodne parametre, ne pa tudi dejanskih sprememb stanja. Tudi dogodke bi lahko uporabili za sprožanje funkcij v uporabniškem vmesniku.

pogodba Dobrodelnost {preslikava (naslov => uint) ravnotežja; funkcija doniraj () plačljivo javno {stanja [msg.sender] + = msg.value; }} pogodbena igra {funkcija buyCoins () plačljivo javno {// 5% gre v dobrodelne namene charity.donate.value (msg.value / 20) (); }} Jezik kode: JavaScript (javascript)

Tu bo pogodba o igrah izvedla interni klic na Charity.donate (). Ta transakcija ne bo prikazana na zunanjem seznamu dobrodelnih organizacij, ampak bo vidna samo v notranjih transakcijah.

Dogodek je priročen način za prijavo nečesa, kar se je zgodilo v pogodbi. Izpuščeni dogodki ostanejo v verigi blokov skupaj z drugimi podatki o pogodbi in so na voljo za prihodnjo revizijo. Tu je izboljšava zgornjega primera z uporabo dogodkov za prikaz zgodovine donacij dobrodelne organizacije.

pogodba Dobrodelna organizacija {// definiraj dogodek dogodka LogDonate (uint _amount); preslikava (naslov => uint) ravnotežja; funkcija doniraj () plačljivo javno {stanja [msg.sender] + = msg.value; // oddajanje dogodka emit LogDonate (msg.value); }} pogodbena igra {funkcija buyCoins () plačljivo javno {// 5% gre v dobrodelne namene charity.donate.value (msg.value / 20) (); }} Jezik kode: JavaScript (javascript)

Tu bodo vse transakcije, ki gredo skozi dobrodelno pogodbo, bodisi neposredno bodisi ne, na seznamu te pogodbe prikazane skupaj z zneskom darovanega denarja..

Opomba: raje novejše konstrukcije Solidity. Dajte prednost konstruktom / vzdevkom, kot sta samouničenje (nad samomor) in keccak256 (nad sha3). Vzorce, kot je require (msg.sender.send (1 eter)), je mogoče tudi poenostaviti na uporabo transfer (), kot v msg.sender.transfer (1 eter). Preveri Dnevnik sprememb trdnosti za več podobnih sprememb.

Zavedajte se, da lahko “Vgrajene” zasenčite

Trenutno je mogoče senca vgrajeni globali v Solidity. To omogoča pogodbam, da preglasijo funkcionalnost vgrajenih datotek, kot so sporočila in vrnitev (). Čeprav to je namenjeno, lahko zavede uporabnike pogodbe glede resničnega vedenja pogodbe.

pogodba PretendingToRevert {funkcija revert () notranja konstanta {}} pogodba ExampleContract je PretendingToRevert {funkcija somethingBad () public {revert (); }}

Pogodbeni uporabniki (in revizorji) bi se morali zavedati celotne izvorne kode pametne pogodbe katere koli aplikacije, ki jo nameravajo uporabiti.

Izogibajte se uporabi tx.origin

Nikoli ne uporabljajte tx.origin za avtorizacijo, druga pogodba ima lahko metodo, ki bo poklicala vašo pogodbo (kjer ima uporabnik na primer nekaj sredstev) in vaša pogodba bo dovolila to transakcijo, saj je vaš naslov v tx.origin.

pogodba MyContract {naslov lastnika; funkcija MyContract () public {owner = msg.sender; } funkcija sendTo (prejemnik naslova, uint znesek) public {require (tx.origin == owner); (uspeh bool,) = sprejemnik.call.value (znesek) (""); zahtevati (uspeh); }} pogodba AttackingContract {MyContract myContract; napadalec naslovov; funkcija AttackingContract (naslov myContractAddress) public {myContract = MyContract (myContractAddress); napadalec = msg.sender; } function () public {myContract.sendTo (napadalec, msg.sender.balance); }} Jezik kode: JavaScript (javascript)

Za avtorizacijo uporabite msg.sender (če druga pogodba pokliče vašo pogodbo, bo msg.sender naslov pogodbe in ne naslov uporabnika, ki je poklical pogodbo)..

Več o tem si lahko preberete tukaj: Dokumenti o trdnosti

Opozorilo: Poleg težave z avtorizacijo obstaja možnost, da bo tx.origin v prihodnosti odstranjen iz protokola Ethereum, zato koda, ki uporablja tx.origin, ne bo združljiva s prihodnjimi izdajami Vitalik: “NE domnevajte, da bo tx.origin še naprej uporaben ali smiseln.”

Omeniti velja tudi, da z uporabo tx.origin omejujete interoperabilnost med pogodbami, ker pogodbe, ki uporablja tx.origin, druga pogodba ne more uporabiti, saj pogodba ne more biti tx.origin.

Glej SWC-115

Odvisnost od časovnega žiga

Pri uporabi časovnega žiga za izvajanje ključne funkcije v pogodbi obstajajo trije glavni premisleki, zlasti kadar ukrepi vključujejo prenos sredstev.

Manipulacija s časovnim žigom

Zavedajte se, da lahko časovni žig bloka upravlja rudar. Razmislite o tem pogodbe:

uint256 konstantna zasebna sol = block.timestamp; funkcija random (uint Max) konstantni zasebni donosi (rezultat uint256) {// dobimo najboljše seme za naključnost uint256 x = sol * 100 / Max; uint256 y = sol * blok.številka / (sol% 5); uint256 seme = block.number / 3 + (sol% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (seme)); vrnitev uint256 ((h / x))% Max + 1; // naključno število med 1 in Max} Jezik kode: PHP (php)

Ko pogodba uporablja časovni žig za naselitev naključne številke, lahko rudar dejansko objavi časovni žig v 15 sekundah po potrditvi bloka, kar dejansko omogoča rudarju, da vnaprej izračuna možnost, ugodnejšo za njihove možnosti na loteriji. Časovni žigi niso naključni in se jih v tem okviru ne sme uporabljati.

15-sekundno pravilo

The Rumeni papir (Ethereumova referenčna specifikacija) ne določa omejitve glede tega, koliko blokov lahko sčasoma odnese, ampak natančno določa da mora biti vsak časovni žig večji od časovnega žiga svojega nadrejenega. Priljubljene izvedbe protokola Ethereum Geth in Parnost oba zavrneta bloke s časovnim žigom več kot 15 sekund v prihodnosti. Zato je dobro pravilo pri ocenjevanju uporabe časovnega žiga: če se obseg vašega časovno odvisnega dogodka razlikuje za 15 sekund in ohranja integriteto, je varno uporabiti blok.timestamp.

Izogibajte se uporabi block.number kot časovnega žiga

Časovno delto lahko ocenite z uporabo lastnosti block.number in povprečni čas bloka, vendar to ni prihodnji dokaz, saj se lahko časi blokade spremenijo (npr reorganizacije vilic in težavna bomba). V dnevih razprodaje pravilo 15 sekund omogoča natančnejšo oceno časa.

Glej SWC-116

Previdnost pri več dedovanju

Pri uporabi večkratnega dedovanja v Solidity je pomembno razumeti, kako prevajalnik sestavi graf dedovanja.

pogodba Končna {uint public a; funkcija Končna (uint f) public {a = f; }} pogodba B je dokončna {int javna taksa; funkcija B (uint f) Končna (f) javna {} funkcija setFee () javna {provizija = 3; }} pogodba C je dokončna {int javna taksa; funkcija C (uint f) Končna (f) javna {} funkcija setFee () javna {provizija = 5; }} pogodba A je B, C {funkcija A () javna B (3) C (5) {setFee (); }} Jezik kode: PHP (php)

Ko je pogodba razporejena, bo prevajalnik dedoval dediščino od desne proti levi (po ključni besedi je starši so navedeni od najbolj baznega do najbolj izpeljanega). Tu je linearizacija pogodbe A:

Končno <- B <- C <- A

Posledica linearizacije bo prinesla vrednost provizije 5, saj je C najbolj izpeljana pogodba. To se morda zdi očitno, vendar si predstavljajte scenarije, v katerih lahko C zasenči ključne funkcije, preuredi logične klavzule in povzroči, da razvijalec piše pogodbe, ki jih je mogoče izkoristiti. Statična analiza trenutno ne sproža težav s zasenčenimi funkcijami, zato jo je treba pregledati ročno.

Kot pomoč pri prispevanju ima Solidity’s Github a projekt z vsemi vprašanji, povezanimi z dedovanjem.

Glej SWC-125

Namesto naslova za varnost tipa uporabite vrsto vmesnika

Ko funkcija vzame naslov pogodbe kot argument, je bolje, da posreduje vmesnik ali vrsto pogodbe in ne surovega naslova. Če funkcijo pokličete drugam znotraj izvorne kode, bo prevajalnik zagotovil dodatna jamstva za varnost tipa.

Tu vidimo dve možnosti:

pogodba Validator {funkcija validira (uint) zunanje donose (bool); } pogodba TypeSafeAuction {// dobra funkcija validateBet (Validator _validator, uint _value) notranji vrnitve (bool) {bool velja = _validator.validate (_value); vrnitev veljavna; }} pogodba TypeUnsafeAuction {// slaba funkcija validateBet (naslov _addr, uint _value) notranji vrnitve (bool) {Validator validator = Validator (_addr); bool veljavno = validator.validate (_value); vrnitev veljavna; }} Jezik kode: JavaScript (javascript)

Prednosti uporabe zgoraj navedene pogodbe TypeSafeAuction so razvidne iz naslednjega primera. Če validateBet () pokličete z naslovnim argumentom ali vrsto pogodbe, ki ni Validator, bo prevajalnik vrgel to napako:

pogodba NonValidator {} pogodba Dražba je TypeSafeAuction {NonValidator nonValidator; funkcijska stava (uint _value) {bool velja = validateBet (nonValidator, _value); // TypeError: Neveljaven tip za argument v klicu funkcije. // Neveljavna implicitna pretvorba iz pogodbe NonValidator // v pogodbo Validator zahteva. }} Jezik kode: JavaScript (javascript)

Izogibajte se uporabi extcodesize za preverjanje računov v zunanji lasti

Naslednji modifikator (ali podobno preverjanje) se pogosto uporablja za preverjanje, ali je bil klic opravljen iz računa zunaj lasti (EOA) ali pogodbenega računa:

// slab modifikator jeNotContract (naslov _a) {uint size; sklop {size: = extcodesize (_a)} require (size == 0); _; } Jezik kode: JavaScript (javascript)

Ideja je naravnost: če naslov vsebuje kodo, to ni EOA, temveč pogodbeni račun. Vendar, pogodba med gradnjo nima na voljo izvorne kode. To pomeni, da lahko med delovanjem konstruktorja pokliče druge pogodbe, vendar extcodesize za svoj naslov vrne nič. Spodaj je minimalni primer, ki prikazuje, kako se je mogoče izogniti temu preverjanju:

pogodba OnlyForEOA {uint javna zastava; // slab modifikator jeNotContract (naslov _a) {uint len; montaža {len: = extcodesize (_a)} require (len == 0); _; } funkcija setFlag (uint i) public isNotContract (msg.sender) {zastava = i; }} pogodba FakeEOA {konstruktor (naslov _a) public {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Jezik kode: JavaScript (javascript)

Ker je mogoče naslove pogodbe izračunati vnaprej, lahko to preverjanje tudi ne uspe, če preveri naslov, ki je prazen v bloku n, vendar ima pogodbo razporejeno v nekem bloku, večjem od n.

Opozorilo: Ta številka je niansirana. Če je vaš cilj preprečiti, da bi druge pogodbe lahko poklicale vašo pogodbo, verjetno zadošča preverjanje extcodesize. Alternativni pristop je preveriti vrednost (tx.origin == msg.sender), čeprav tudi to ima pomanjkljivosti.

Obstajajo lahko tudi druge situacije, v katerih preverjanje extcodesize služi vašemu namenu. Opis vseh tukaj je zunaj obsega. Razumejte osnovno vedenje EVM in uporabite svojo presojo.

Ali je vaša koda Blockchain varna?

Rezervirajte enodnevni naključni pregled pri naših varnostnih strokovnjakih. Rezervirajte svoje danes Skrb za varnostSmart ContractsSolidityNewsletterNaročite se na naše novice o najnovejših novostih v Ethereumu, podjetniških rešitvah, virih za razvijalce in še več. E-poštni naslovEkskluzivna vsebinaKako zgraditi uspešen izdelek BlockchainSpletni seminar

Kako zgraditi uspešen izdelek Blockchain

Kako nastaviti in zagnati vozlišče EthereumSpletni seminar

Kako nastaviti in zagnati vozlišče Ethereum

Kako zgraditi lasten API za EthereumSpletni seminar

Kako zgraditi lasten API za Ethereum

Kako ustvariti socialni žetonSpletni seminar

Kako ustvariti socialni žeton

Uporaba varnostnih orodij pri razvoju pametnih pogodbSpletni seminar

Uporaba varnostnih orodij pri razvoju pametnih pogodb

Prihodnost financ Digitalna sredstva in DeFiSpletni seminar

Prihodnost financ: digitalna sredstva in DeFi

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