Poplatky za Gas na hlavní síti Ethereum byly vždy velkým problémem, zejména v době přetížení sítě. V období špičky uživatelé často musí platit extrémně vysoké transakční poplatky. Proto je optimalizace poplatků za Gas během vývoje smart kontraktů obzvlášť důležitá. Optimalizace spotřeby Gas nejenže efektivně snižuje transakční náklady, ale také zvyšuje efektivitu transakcí a přináší uživatelům ekonomičtější a efektivnější zkušenost s používáním blockchainu.
Tento článek shrnuje mechanismus poplatků za Gas v Ethereum virtuálním stroji (EVM), klíčové koncepty související s optimalizací poplatků za Gas a nejlepší praktiky pro optimalizaci poplatků za Gas během vývoje smart kontraktů. Doufáme, že tyto informace poskytnou vývojářům inspiraci a praktickou pomoc, a zároveň pomohou běžným uživatelům lépe porozumět tomu, jak fungují poplatky za Gas v EVM, a společně čelit výzvám v ekosystému blockchainu.
Úvod do mechanismu poplatků za Gas v EVM
V sítích kompatibilních s EVM se "Gas" používá k měření jednotky výpočetní síly potřebné k provedení konkrétní operace.
Následující obrázek ilustruje strukturu EVM. Na obrázku jsou náklady na Gas rozděleny na tři části: provádění operací, externí volání zpráv a čtení a zápis paměti a úložiště.
Zdroj: Oficiální stránky Etherea[1]
Protože každá transakce vyžaduje výpočetní prostředky, účtuje se poplatek, aby se zabránilo nekonečným smyčkám a útokům typu odmítnutí služby (DoS). Poplatek potřebný k dokončení transakce se nazývá "poplatek za Gas".
Od doby, co je EIP-1559 (hard fork Londýn) v platnosti, se poplatky za Gas počítají podle následujícího vzorce:
Poplatek za Gas = jednotky použitého plynu * (základní poplatek + prioritní poplatek)
Základní poplatek se zničí, zatímco prioritní poplatek slouží jako pobídka k tomu, aby validátoři přidali transakci do blockchainu. Nastavením vyššího prioritního poplatku při odesílání transakce lze zvýšit pravděpodobnost, že transakce bude zahrnuta do dalšího bloku. To je podobné tomu, jako by uživatel dával validátorovi "dýško".
1. Porozumění optimalizaci Gas v EVM
Když se smart kontrakt kompiluje pomocí Solidity, je převeden na sérii „opkódů“, tedy opkódů.
Každý kód opkódu (například vytváření kontraktu, provádění volání zpráv, přístup k úložným účtům a provádění operací na virtuálním stroji) má uznávané náklady na spotřebu Gas, které jsou zaznamenány v žluté knize Etherea[2].
Po několika úpravách EIP byly náklady na Gas některých opkódů upraveny, což může být v rozporu s údaji v žluté knize. Pro nejnovější informace o nákladech na opkódy se podívejte zde[3].
2. Základní koncepty optimalizace Gas
Hlavní myšlenkou optimalizace Gas je prioritizace nákladově efektivních operací na blockchainu EVM a vyhýbání se nákladným operacím za Gas.
V EVM jsou následující operace levnější:
Čtení a zápis proměnných v paměti
Čtení konstant a neměnných proměnných
Čtení a zápis místních proměnných
Čtení proměnných calldata, například polí a struktur
Volání interních funkcí
K nákladnějším operacím patří:
Čtení a zápis stavových proměnných ve smluvním úložišti
Externí volání funkcí
Cyklické operace
Nejlepší praktiky pro optimalizaci poplatků za Gas v EVM
Na základě výše uvedených základních konceptů jsme pro komunitu vývojářů sestavili seznam nejlepších praktik pro optimalizaci poplatků za Gas. Dodržováním těchto praktik mohou vývojáři snížit spotřebu poplatků za Gas u svých smart kontraktů, snížit transakční náklady a vytvořit efektivnější a uživatelsky přívětivější aplikace.
1. Snižte využití úložiště, jak je to možné
Ve Solidity je úložiště (Storage) omezený zdroj, jehož spotřeba Gas je mnohem vyšší než u paměti (Memory). Každé čtení nebo zápis dat ze skladování ve smart kontraktu generuje vysoké náklady na Gas.
Podle definice žluté knihy Etherea jsou náklady na operace úložiště více než 100krát vyšší než náklady na operace paměti. Například instrukce OPcodesmload a mstore spotřebovávají pouze 3 jednotky Gas, zatímco operace úložiště jako sload a sstore vyžadují alespoň 100 jednotek, i za ideálních podmínek.
Metody omezující využití úložiště zahrnují:
Uložení nepermanentních dat do paměti
Snížení počtu úprav úložiště: uchovávejte meziprodukty v paměti a po dokončení všech výpočtů přiřaďte výsledek proměnné úložiště.
2. Balíkování proměnných
Počet úložných slotů používaných ve smart kontraktu a způsob, jakým vývojáři reprezentují data, má značný vliv na spotřebu poplatků za Gas.
Kompilátor Solidity během kompilace zabalí kontinuální úložné proměnné a používá 32-bytové úložné sloty jako základní jednotku pro ukládání proměnných. Balíkování proměnných znamená, že rozumným uspořádáním proměnných mohou být více proměnných umístěny do jednoho úložného slotu.
Na levé straně je méně efektivní implementace, která spotřebovává 3 úložné sloty; na pravé straně je efektivnější implementace.
Tímto drobným přizpůsobením mohou vývojáři ušetřit 20 000 jednotek Gas (uchování nepoužívaného úložného slotu stojí 20 000 Gas), ale nyní potřebují pouze dva úložné sloty.
Protože každý úložný slot spotřebovává Gas, balení proměnných optimalizuje využití Gas tím, že snižuje počet požadovaných úložných slotů.
3. Optimalizace datových typů
Proměnná může být reprezentována různými datovými typy, ale náklady na operace spojené s různými datovými typy se také liší. Výběr správného datového typu pomáhá optimalizovat využití Gas.
Například v Solidity mohou být celá čísla rozdělena do různých velikostí: uint8, uint16, uint32 atd. Protože EVM provádí operace po 256 bitech, použití uint8 znamená, že EVM jej musí nejprve převést na uint256, což dále spotřebovává Gas.
Můžeme porovnat náklady na Gas pro uint8 a uint256 pomocí kódu z obrázku. Funkce UseUint() spotřebovává 120 382 jednotek Gas, zatímco funkce UseUInt8() spotřebovává 166 111 jednotek Gas.
Samostatně se ukazuje, že použití uint256 je levnější než uint8. Pokud však použijeme optimalizaci balení proměnných, jak jsme doporučili dříve, bude to jiné. Pokud se vývojářům podaří zabalit čtyři uint8 proměnné do jednoho úložného slotu, celkové náklady na iteraci přes ně budou nižší než náklady na čtyři uint256 proměnné. Takto může smart kontrakt číst a zapisovat jednou do úložného slotu a vložit čtyři uint8 proměnné do paměti/úložiště v jedné operaci.
4. Použití proměnných s pevnou velikostí místo proměnných s dynamickou velikostí
Pokud lze data udržet v rámci 32 bytů, doporučuje se použít datový typ bytes32 místo bytes nebo strings. Obecně platí, že proměnné s pevnou velikostí spotřebovávají méně Gas než proměnné s proměnlivou velikostí. Pokud lze délku bajtu omezit, snažte se vybrat nejmenší možnou délku od bytes1 do bytes32.
5. Mapování a pole
Seznam dat Solidity může být reprezentován dvěma datovými typy: pole (Arrays) a mapování (Mappings), ale jejich syntaxe a struktura se výrazně liší.
Mapování je ve většině případů efektivnější a levnější, ale pole mají možnost iterace a podporují balení datových typů. Proto se doporučuje upřednostnit mapování při správě datových seznamů, pokud není třeba iterovat nebo pokud může být optimalizace spotřeby Gas provedena pomocí balení datových typů.
6. Použití calldata místo paměti
Proměnné deklarované v parametrech funkce mohou být uloženy v calldata nebo paměti. Hlavní rozdíl mezi nimi je, že paměť může být funkci modifikována, zatímco calldata je neměnná.
Pamatujte na toto pravidlo: pokud jsou parametry funkce pouze pro čtení, měli byste upřednostnit použití calldata místo paměti. Tím se vyhnete zbytečným kopiím z calldata do paměti.
Příklad 1: Použití paměti
Při použití klíčového slova paměti se hodnoty pole zkopírují z kódovaného calldata do paměti během procesu ABI dekódování. Náklady na provedení tohoto kódu činí 3 694 jednotek Gas.
Příklad 2: Použití calldata
Při čtení hodnot přímo z calldata se vynechávají prostřední operace paměti. Tato optimalizace snižuje náklady na provedení na pouhých 2 413 jednotek Gas, což zlepšuje efektivitu Gas o 35%.
7. Používejte klíčová slova Constant/Immutable, kdykoli je to možné
Proměnné Constant/Immutable se neukládají v úložišti kontraktu. Tyto proměnné se vypočítávají při kompilaci a ukládají se v bajtovém kódu kontraktu. Proto je jejich přístupní náklady mnohem nižší než u úložiště, doporučuje se používat klíčová slova Constant nebo Immutable co nejvíce.
8. Používání Unchecked, pokud je zajištěno, že nedojde k přetečení/podtečení
Když vývojáři mohou určit, že aritmetické operace nepovedou k přetečení nebo podtečení, mohou použít klíčové slovo unchecked zavedené ve Solidity v0.8.0, aby se vyhnuli zbytečným kontrolám přetečení nebo podtečení, čímž ušetří náklady na Gas.
Na následujícím obrázku, omezeném podmínkami i
Kromě toho verze 0.8.0 a vyšší již nevyžadují použití knihovny SafeMath, protože kompilátor sám o sobě již má vestavěnou ochranu proti přetečení a podtečení.
9. Optimalizace modifikátorů
Kód modifikátorů je vložen do funkcí, které byly modifikovány, a při každém použití modifikátoru se jeho kód zkopíruje. To zvyšuje velikost bajtového kódu a zvyšuje spotřebu Gas. Následuje jedna z metod, jak optimalizovat náklady na Gas modifikátoru:
Před optimalizací:
Optimalizováno:
V tomto příkladu, tím, že se logika přestaví na interní funkci _checkOwner(), se umožní opětovné použití této interní funkce v modifikátoru, což snižuje velikost bajtového kódu a snižuje náklady na Gas.
10. Optimalizace zkrácení
U operátorů || a && se logické operace provádějí pomocí zkráceného hodnocení, což znamená, že pokud první podmínka již určuje výsledek logického výrazu, nebude hodnocena druhá podmínka.
Aby bylo možné optimalizovat spotřebu Gas, měly by být podmínky s nízkými náklady na výpočet umístěny na začátek, což může umožnit přeskočení nákladných výpočtů.
Obecná doporučení
1. Odstranit zbytečný kód
Pokud ve smlouvě existují nepoužívané funkce nebo proměnné, doporučuje se je odstranit. To je nejpřímější způsob, jak snížit náklady na nasazení kontraktu a udržet objem kontraktu malý.
Zde je několik praktických doporučení:
Provádějte výpočty s nejefektivnějšími algoritmy. Pokud se výsledky některých výpočtů používají přímo ve smlouvě, měly by být tyto redundantní výpočty odstraněny. V podstatě by měly být odstraněny jakékoli nepoužité výpočty.
Na Ethereum mohou vývojáři získat odměny za Gas uvolněním úložného prostoru. Pokud již není proměnná potřebná, měla by být odstraněna pomocí klíčového slova delete nebo nastavena na výchozí hodnotu.
Optimalizace smyček: vyhněte se nákladným cyklickým operacím, co nejvíce slučujte smyčky a vyjměte opakované výpočty z těla smyčky.
2. Použití předkompilovaných kontraktů
Předkompilované kontrakty poskytují složité knihovní funkce, jako jsou šifrovací a hashovací operace. Protože kód neběží na EVM, ale na místním klientovi, je třeba méně Gas. Použití předkompilovaných kontraktů může ušetřit Gas snížením výpočetní práce potřebné k provedení smart kontraktů.
Příklady předkompilovaných kontraktů zahrnují algoritmus digitálního podpisu eliptické křivky (ECDSA) a hashovací algoritmus SHA2-256. Použitím těchto předkompilovaných kontraktů ve smart kontraktech mohou vývojáři snížit náklady na Gas a zvýšit efektivitu aplikací.
Pro úplný seznam předkompilovaných kontraktů podporovaných sítí Ethereum se podívejte zde[4].
3. Použití in-line assemblerového kódu
In-line assembler umožňuje vývojářům psát nízkoúrovňový, ale efektivní kód, který může být přímo prováděn EVM, aniž by bylo třeba používat nákladné Solidity opkódy. In-line assembler také umožňuje přesněji řídit využívání paměti a úložiště, což dále snižuje náklady na Gas. Kromě toho může in-line assembler provádět složité operace, které by bylo obtížné realizovat pouze pomocí Solidity, což poskytuje větší flexibilitu pro optimalizaci spotřeby Gas.
Následuje příklad kódu, který šetří Gas pomocí in-line assembleru:
Z výše uvedeného obrázku je zřejmé, že druhý případ použití, který využívá technologii in-line assembleru, má vyšší účinnost Gas ve srovnání se standardním případem.
Nicméně použití in-line assembleru také může přinést rizika a být náchylné k chybám. Proto by mělo být používáno opatrně a pouze zkušenými vývojáři.
4. Používání řešení Layer 2
Použití řešení Layer 2 může snížit množství dat, které je třeba uložit a zpracovat na hlavním řetězci Etherea.
Řešení Layer 2, jako jsou rollupy, vedlejší řetězce a stavové kanály, umožňují zpracování transakcí mimo hlavní řetězec Etherea, což vede k rychlejším a levnějším transakcím.
Snížením počtu transakcí na řetězci tímto způsobem tyto řešení snižují poplatky za Gas. Použití řešení Layer 2 také zvyšuje škálovatelnost Etherea, což umožňuje více uživatelům a aplikacím účastnit se sítě, aniž by došlo k přetížení sítě.
5. Používání optimalizačních nástrojů a knihoven
Existuje několik optimalizačních nástrojů, které lze použít, například solc optimalizátor, optimalizátor sestavení Truffle a kompilátor Solidity Remix.
Tyto nástroje mohou pomoci minimalizovat velikost bajtového kódu, odstranit zbytečný kód a snížit počet operací potřebných k provedení smart kontraktů. V kombinaci s dalšími knihovnami pro optimalizaci Gas, jako je "solmate", mohou vývojáři efektivně snížit náklady na Gas a zvýšit efektivitu smart kontraktů.
Závěr
Optimalizace spotřeby Gas je důležitým krokem pro vývojáře, protože umožňuje minimalizovat transakční náklady a zvyšovat efektivitu smart kontraktů na EVM kompatibilních sítích. Prioritizací provádění nákladově efektivních operací, snižováním využití úložiště, využíváním in-line assembleru a dodržováním dalších nejlepších praktik diskutovaných v tomto článku mohou vývojáři účinně snížit spotřebu Gas svých kontraktů.
Je však důležité poznamenat, že během optimalizace musí vývojáři jednat opatrně, aby se zabránilo zavedení bezpečnostních zranitelností. Optimalizace kódu a snižování spotřeby Gas by nikdy nemělo obětovat inherentní bezpečnost smart kontraktů.
[1] :https://ethereum.org/en/developers/docs/gas/
[2] :https://ethereum.github.io/yellowpaper/paper.pdf
[3] :https://www.evm.codes/
[4] :https://www.evm.codes/precompiled