Podrobný průvodce kontraktem ERC-20
Úvod
Jedním z nejčastějších způsobů využití Etherea je vytvoření obchodovatelného tokenu pro skupinu, v podstatě jejich vlastní měny. Tyto tokeny obvykle dodržují standard ERC-20. Tento standard umožňuje psát nástroje, jako jsou pooly likvidity a peněženky, které fungují se všemi tokeny ERC-20. V tomto článku budeme analyzovat implementaci OpenZeppelin Solidity ERC20opens in a new tab a také definici rozhraníopens in a new tab.
Jedná se o anotovaný zdrojový kód. Chcete-li implementovat ERC-20, přečtěte si tento návodopens in a new tab.
Rozhraní
Účelem standardu, jako je ERC-20, je umožnit mnoho implementací tokenů, které jsou interoperabilní napříč aplikacemi, jako jsou peněženky a decentralizované burzy. K tomu vytvoříme rozhraníopens in a new tab. Jakýkoli kód, který potřebuje použít kontrakt tokenu, může použít stejné definice v rozhraní a být kompatibilní se všemi kontrakty tokenu, které jej používají, ať už je to peněženka, jako je MetaMask, dapp, jako je etherscan.io, nebo jiný kontrakt, jako je pool likvidity.
Pokud jste zkušený programátor, pravděpodobně si pamatujete, že jste podobné konstrukce viděli v Javěopens in a new tab nebo dokonce v hlavičkových souborech Copens in a new tab.
Toto je definice rozhraní ERC-20opens in a new tab od OpenZeppelin. Jedná se o překlad lidsky čitelného standarduopens in a new tab do kódu v Solidity. Samozřejmě samotné rozhraní nedefinuje, jak se má něco dělat. To je vysvětleno ve zdrojovém kódu kontraktu níže.
1// SPDX-License-Identifier: MITSoubory v Solidity by měly obsahovat identifikátor licence. Seznam licencí naleznete zdeopens in a new tab. Pokud potřebujete jinou licenci, jednoduše to vysvětlete v komentářích.
1pragma solidity >=0.6.0 <0.8.0;Jazyk Solidity se stále rychle vyvíjí a nové verze nemusí být kompatibilní se starým kódem (viz zdeopens in a new tab). Proto je dobré specifikovat nejen minimální verzi jazyka, ale také maximální verzi, nejnovější, se kterou jste kód testovali.
1/**2 * @dev Rozhraní standardu ERC20, jak je definováno v EIP.3 */Značka @dev v komentáři je součástí formátu NatSpecopens in a new tab, který se používá k vytváření dokumentace ze zdrojového kódu.
1interface IERC20 {Podle konvence začínají názvy rozhraní písmenem I.
1 /**2 * @dev Vrací množství existujících tokenů.3 */4 function totalSupply() external view returns (uint256);Tato funkce je externí (external), což znamená, že ji lze volat pouze z vnějšku kontraktuopens in a new tab.
Vrací celkovou zásobu tokenů v kontraktu. Tato hodnota je vrácena pomocí nejběžnějšího typu v Ethereu, 256bitového celého čísla bez znaménka (256 bitů je
nativní velikost slova EVM). Tato funkce je také view, což znamená, že nemění stav, takže ji lze provést na jednom uzlu, místo aby ji musel
spouštět každý uzel v blockchainu. Tento druh funkce negeneruje transakci a nestojí žádné palivo.
Poznámka: Teoreticky by se mohlo zdát, že tvůrce kontraktu by mohl podvádět vrácením menší celkové zásoby, než je skutečná hodnota, takže by se každý token zdál cennější, než ve skutečnosti je. Tato obava však ignoruje skutečnou podstatu blockchainu. Vše, co se děje na blockchainu, může být ověřeno každým uzlem. Aby toho bylo dosaženo, je na každém uzlu k dispozici strojový kód a úložiště každého kontraktu. I když nejste povinni zveřejnit kód vašeho kontraktu v Solidity, nikdo by vás nebral vážně, pokud nezveřejníte zdrojový kód a verzi Solidity, se kterou byl zkompilován, aby mohl být ověřen oproti strojovému kódu, který jste poskytli. Podívejte se například na tento kontraktopens in a new tab.
1 /**2 * @dev Vrací množství tokenů, které vlastní `account`.3 */4 function balanceOf(address account) external view returns (uint256);Jak název napovídá, funkce balanceOf vrací zůstatek účtu. Účty Etherea jsou v Solidity identifikovány pomocí typu address, který obsahuje 160 bitů.
Je také external a view.
1 /**2 * @dev Přesune tokeny v množství `amount` z účtu volajícího na `recipient`.3 *4 * Vrací booleovskou hodnotu, která udává, zda operace proběhla úspěšně.5 *6 * Vyvolá událost {Transfer}.7 */8 function transfer(address recipient, uint256 amount) external returns (bool);Funkce transfer převádí tokeny od volajícího na jinou adresu. To zahrnuje změnu stavu, takže to není view.
Když uživatel zavolá tuto funkci, vytvoří se transakce, která stojí palivo. Rovněž vyvolá událost Transfer, aby informovala všechny na
blockchainu o této události.
Funkce má dva typy výstupu pro dva různé typy volajících:
- Uživatelé, kteří volají funkci přímo z uživatelského rozhraní. Uživatel obvykle odešle transakci
a nečeká na odpověď, což může trvat neurčitou dobu. Uživatel může zjistit, co se stalo,
vyhledáním potvrzení o transakci (které je identifikováno hašem transakce) nebo vyhledáním
události
Transfer. - Jiné kontrakty, které volají funkci jako součást celkové transakce. Tyto kontrakty dostanou výsledek okamžitě, protože běží ve stejné transakci, takže mohou použít návratovou hodnotu funkce.
Stejný typ výstupu je vytvořen ostatními funkcemi, které mění stav kontraktu.
Povolení umožňují účtu utratit některé tokeny, které patří jinému vlastníkovi. To je užitečné například pro kontrakty, které fungují jako prodejci. Kontrakty nemohou sledovat události, takže pokud by kupující převedl tokeny přímo na kontrakt prodejce, tento kontrakt by nevěděl, že mu bylo zaplaceno. Místo toho kupující povolí kontraktu prodejce utratit určitou částku a prodejce tuto částku převede. To se děje prostřednictvím funkce, kterou kontrakt prodejce volá, takže kontrakt prodejce může vědět, zda byl úspěšný.
1 /**2 * @dev Vrací zbývající počet tokenů, které bude moci `spender`3 * utratit jménem `owner` prostřednictvím {transferFrom}. Ve výchozím nastavení je4 * tato hodnota nulová.5 *6 * Tato hodnota se mění při volání funkcí {approve} nebo {transferFrom}.7 */8 function allowance(address owner, address spender) external view returns (uint256);Funkce allowance umožňuje komukoli dotázat se, jaké je povolení, které jedna
adresa (owner) umožňuje utratit jiné adrese (spender).
1 /**2 * @dev Nastaví `amount` jako povolenou částku pro `spender` pro tokeny volajícího.3 *4 * Vrací booleovskou hodnotu, která udává, zda operace proběhla úspěšně.5 *6 * DŮLEŽITÉ: Dejte si pozor, že změna povolení touto metodou s sebou nese riziko,7 * že někdo může nešťastným pořadím transakcí využít jak staré, tak i nové povolení.8 * Jedním z možných řešení, jak tento souběh zmírnit,9 * je nejprve snížit povolení pro utrácejícího na 0 a poté nastavit10 * požadovanou hodnotu:11 * https://github.com/ethereum/EIPs/issues/20#issuecomment-26352472912 *13 * Vyvolá událost {Approval}.14 */15 function approve(address spender, uint256 amount) external returns (bool);Zobrazit všeFunkce approve vytváří povolení. Nezapomeňte si přečíst zprávu o tom,
jak může být zneužita. V Ethereu ovládáte pořadí svých vlastních transakcí,
ale nemůžete ovládat pořadí, v jakém budou provedeny transakce
jiných lidí, pokud neodešlete vlastní transakci až poté, co uvidíte,
že transakce druhé strany proběhla.
1 /**2 * @dev Přesune tokeny v množství `amount` z adresy `sender` na `recipient` pomocí3 * mechanismu povolení. `amount` se poté odečte od povolení volajícího4 * .5 *6 * Vrací booleovskou hodnotu, která udává, zda operace proběhla úspěšně.7 *8 * Vyvolá událost {Transfer}.9 */10 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);Zobrazit všeNakonec transferFrom použije utrácející k samotnému utracení povolené částky.
12 /**3 * @dev Vyvolá se, když se tokeny v hodnotě `value` přesunou z jednoho účtu (`from`) na4 * jiný (`to`).5 *6 * Všimněte si, že `value` může být nula.7 */8 event Transfer(address indexed from, address indexed to, uint256 value);910 /**11 * @dev Vyvolá se, když je povolení `spendera` pro `ownera` nastaveno12 * voláním {approve}. `value` je nové povolení.13 */14 event Approval(address indexed owner, address indexed spender, uint256 value);15}Zobrazit všeTyto události jsou emitovány, když se změní stav kontraktu ERC-20.
Skutečný kontrakt
Toto je skutečný kontrakt, který implementuje standard ERC-20, převzato odtudopens in a new tab. Není určen k použití tak, jak je, ale můžete z něj děditopens in a new tab a rozšířit jej na něco použitelného.
1// SPDX-License-Identifier: MIT2pragma solidity >=0.6.0 <0.8.0;
Importní příkazy
Kromě výše uvedených definic rozhraní importuje definice kontraktu dva další soubory:
12import "../../GSN/Context.sol";3import "./IERC20.sol";4import "../../math/SafeMath.sol";GSN/Context.soljsou definice potřebné k použití OpenGSNopens in a new tab, systému, který umožňuje uživatelům bez etheru používat blockchain. Všimněte si, že se jedná o starou verzi, pokud chcete integrovat s OpenGSN, použijte tento návodopens in a new tab.- Knihovna SafeMathopens in a new tab, která zabraňuje aritmetickému přetečení/podtečení pro verze Solidity <0.8.0. V Solidity ≥0.8.0 aritmetické operace automaticky vracejí při přetečení/podtečení, takže SafeMath je zbytečná. Tento kontrakt používá SafeMath pro zpětnou kompatibilitu se staršími verzemi kompilátoru.
Tento komentář vysvětluje účel kontraktu.
1/**2 * @dev Implementace rozhraní {IERC20}.3 *4 * Tato implementace je agnostická vůči způsobu vytváření tokenů. To znamená,5 * že mechanismus dodávání musí být přidán v odvozeném kontraktu pomocí {_mint}.6 * Obecný mechanismus viz {ERC20PresetMinterPauser}.7 *8 * TIP: Podrobný popis naleznete v našem průvodci9 * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[Jak10 * implementovat mechanismy dodávání].11 *12 * Dodržovali jsme obecné pokyny OpenZeppelin: funkce se při selhání vracejí, místo aby13 * vracely `false`. Toto chování je nicméně konvenční14 * a není v rozporu s očekáváními aplikací ERC20.15 *16 * Navíc se při volání {transferFrom} emituje událost {Approval}.17 * To umožňuje aplikacím rekonstruovat povolení pro všechny účty jen18 * nasloucháním zmíněných událostí. Jiné implementace EIP nemusí tyto události19 * emitovat, protože to není vyžadováno specifikací.20 *21 * Nakonec byly přidány nestandardní funkce {decreaseAllowance} a {increaseAllowance},22 * aby se zmírnily známé problémy kolem nastavování23 * povolení. Viz {IERC20-approve}.24 */25Zobrazit všeDefinice kontraktu
1contract ERC20 is Context, IERC20 {Tento řádek specifikuje dědičnost, v tomto případě z IERC20 z výše uvedeného a Context, pro OpenGSN.
12 using SafeMath for uint256;3Tento řádek připojuje knihovnu SafeMath k typu uint256. Tuto knihovnu najdete
zdeopens in a new tab.
Definice proměnných
Tyto definice specifikují stavové proměnné kontraktu. Tyto proměnné jsou deklarovány jako soukromé (private), ale
to pouze znamená, že je nemohou číst jiné kontrakty na blockchainu. Na blockchainu neexistují žádná
tajemství, software na každém uzlu má stav každého kontraktu
v každém bloku. Podle konvence se stavové proměnné pojmenovávají _<něco>.
První dvě proměnné jsou mapováníopens in a new tab, což znamená, že se chovají zhruba stejně jako asociativní poleopens in a new tab, s výjimkou toho, že klíče jsou číselné hodnoty. Úložiště je přiděleno pouze pro položky, které mají hodnoty odlišné od výchozí (nula).
1 mapping (address => uint256) private _balances;První mapování, _balances, jsou adresy a jejich příslušné zůstatky tohoto tokenu. Pro přístup
k zůstatku použijte tuto syntaxi: _balances[<adresa>].
1 mapping (address => mapping (address => uint256)) private _allowances;Tato proměnná, _allowances, ukládá povolení vysvětlená dříve. První index je vlastníkem
tokenů a druhý je kontrakt s povolením. Pro přístup k částce, kterou může adresa A
utratit z účtu adresy B, použijte _allowances[B][A].
1 uint256 private _totalSupply;Jak název napovídá, tato proměnná sleduje celkovou zásobu tokenů.
1 string private _name;2 string private _symbol;3 uint8 private _decimals;Tyto tři proměnné se používají ke zlepšení čitelnosti. První dvě jsou samovysvětlující, ale _decimals
není.
Na jedné straně Ethereum nemá proměnné s plovoucí desetinnou čárkou nebo zlomkové proměnné. Na druhé straně, lidé rádi dělí tokeny. Jedním z důvodů, proč se lidé usadili na zlatě jako měně, bylo to, že bylo těžké vrátit drobné, když si někdo chtěl koupit hodnotu kachny v kravě.
Řešením je sledovat celá čísla, ale počítat místo skutečného tokenu zlomkový token, který je téměř bezcenný. V případě etheru se zlomkový token nazývá wei a 10^18 wei se rovná jednomu ETH. V době psaní tohoto článku je 10 000 000 000 000 wei přibližně jeden americký nebo eurový cent.
Aplikace potřebují vědět, jak zobrazit zůstatek tokenu. Pokud má uživatel 3 141 000 000 000 000 000 wei, je to
3,14 ETH? 31,41 ETH? 3 141 ETH? V případě etheru je definováno 10^18 wei na ETH, ale pro váš
token můžete zvolit jinou hodnotu. Pokud dělení tokenu nemá smysl, můžete použít
hodnotu _decimals nula. Chcete-li použít stejný standard jako ETH, použijte hodnotu 18.
Konstruktor
1 /**2 * @dev Nastaví hodnoty pro {name} a {symbol}, inicializuje {decimals} s3 * výchozí hodnotou 18.4 *5 * Chcete-li vybrat jinou hodnotu pro {decimals}, použijte {_setupDecimals}.6 *7 * Všechny tři z těchto hodnot jsou neměnné: lze je nastavit pouze jednou během8 * konstrukce.9 */10 constructor (string memory name_, string memory symbol_) public {11 // V Solidity ≥0.7.0, 'public' je implicitní a může být vynechán.1213 _name = name_;14 _symbol = symbol_;15 _decimals = 18;16 }Zobrazit všeKonstruktor se volá při prvním vytvoření kontraktu. Podle konvence jsou parametry funkce pojmenovány <něco>_.
Funkce uživatelského rozhraní
1 /**2 * @dev Vrací název tokenu.3 */4 function name() public view returns (string memory) {5 return _name;6 }78 /**9 * @dev Vrací symbol tokenu, obvykle kratší verzi10 * názvu.11 */12 function symbol() public view returns (string memory) {13 return _symbol;14 }1516 /**17 * @dev Vrací počet desetinných míst použitých k získání jeho uživatelské reprezentace.18 * Například, pokud se `decimals` rovná `2`, měl by být zůstatek `505` tokenů19 * zobrazen uživateli jako `5,05` (`505 / 10 ** 2`).20 *21 * Tokeny obvykle volí hodnotu 18, napodobující vztah mezi22 * etherem a wei. Toto je hodnota, kterou {ERC20} používá, pokud není23 * volána {_setupDecimals}.24 *25 * POZNÁMKA: Tato informace se používá pouze pro účely _zobrazení_: v26 * žádném případě neovlivňuje žádnou aritmetiku kontraktu, včetně27 * {IERC20-balanceOf} a {IERC20-transfer}.28 */29 function decimals() public view returns (uint8) {30 return _decimals;31 }Zobrazit všeTyto funkce, name, symbol a decimals, pomáhají uživatelským rozhraním poznat váš kontrakt, aby ho mohly správně zobrazit.
Návratový typ je string memory, což znamená návrat řetězce, který je uložen v paměti. Proměnné, jako jsou
řetězce, mohou být uloženy na třech místech:
| Životnost | Přístup ke kontraktu | Náklady na palivo | |
|---|---|---|---|
| Paměť | Volání funkce | Čtení/zápis | Desítky nebo stovky (vyšší pro vyšší umístění) |
| Calldata | Volání funkce | Pouze pro čtení | Nelze použít jako návratový typ, pouze jako typ parametru funkce |
| Úložiště | Dokud se nezmění | Čtení/zápis | Vysoké (800 pro čtení, 20k pro zápis) |
V tomto případě je memory nejlepší volbou.
Čtení informací o tokenu
Jedná se o funkce, které poskytují informace o tokenu, buď o celkové zásobě, nebo o zůstatku na účtu.
1 /**2 * @dev Viz {IERC20-totalSupply}.3 */4 function totalSupply() public view override returns (uint256) {5 return _totalSupply;6 }Funkce totalSupply vrací celkovou zásobu tokenů.
1 /**2 * @dev Viz {IERC20-balanceOf}.3 */4 function balanceOf(address account) public view override returns (uint256) {5 return _balances[account];6 }Přečtěte si zůstatek na účtu. Všimněte si, že kdokoli může získat zůstatek na účtu kohokoli jiného. Nemá smysl se snažit tuto informaci skrývat, protože je stejně dostupná na každém uzlu. Na blockchainu neexistují žádná tajemství.
Převod tokenů
1 /**2 * @dev Viz {IERC20-transfer}.3 *4 * Požadavky:5 *6 * - `recipient` nemůže být nulová adresa.7 * - volající musí mít zůstatek alespoň `amount`.8 */9 function transfer(address recipient, uint256 amount) public virtual override returns (bool) {Zobrazit všeFunkce transfer se volá k převodu tokenů z účtu odesílatele na jiný. Všimněte si,
že i když vrací booleovskou hodnotu, tato hodnota je vždy true. Pokud se převod
nepodaří, kontrakt vrátí volání zpět.
1 _transfer(_msgSender(), recipient, amount);2 return true;3 }Funkce _transfer dělá skutečnou práci. Jedná se o soukromou funkci, kterou mohou volat pouze
jiné funkce kontraktu. Podle konvence jsou soukromé funkce pojmenovány _<něco>, stejně jako stavové
proměnné.
Normálně v Solidity používáme msg.sender pro odesílatele zprávy. To však narušuje
OpenGSNopens in a new tab. Chceme-li s naším tokenem povolit transakce bez etheru, musíme
použít _msgSender(). Pro běžné transakce vrací msg.sender, ale pro transakce bez etheru
vrací původního podepisujícího a ne kontrakt, který zprávu předal.
Funkce povolení
Jedná se o funkce, které implementují funkcionalitu povolení: allowance, approve, transferFrom
a _approve. Kromě toho implementace OpenZeppelin jde nad rámec základního standardu a obsahuje některé funkce, které zlepšují
bezpečnost: increaseAllowance a decreaseAllowance.
Funkce povolení
1 /**2 * @dev Viz {IERC20-allowance}.3 */4 function allowance(address owner, address spender) public view virtual override returns (uint256) {5 return _allowances[owner][spender];6 }Funkce allowance umožňuje každému zkontrolovat jakékoli povolení.
Funkce schválení
1 /**2 * @dev Viz {IERC20-approve}.3 *4 * Požadavky:5 *6 * - `spender` nemůže být nulová adresa.7 */8 function approve(address spender, uint256 amount) public virtual override returns (bool) {Tato funkce se volá pro vytvoření povolení. Je podobná výše uvedené funkci transfer:
- Funkce pouze volá interní funkci (v tomto případě
_approve), která provádí skutečnou práci. - Funkce buď vrátí
true(pokud je úspěšná), nebo se vrátí (pokud ne).
1 _approve(_msgSender(), spender, amount);2 return true;3 }Používáme interní funkce, abychom minimalizovali počet míst, kde dochází ke změnám stavu. Každá funkce, která mění stav, je potenciálním bezpečnostním rizikem, které je třeba zkontrolovat z hlediska bezpečnosti. Tímto způsobem máme menší šanci, že se spleteme.
Funkce transferFrom
Toto je funkce, kterou volá utrácející, aby utratil povolenou částku. To vyžaduje dvě operace: převést utracenou částku a snížit povolenou částku o tuto částku.
1 /**2 * @dev Viz {IERC20-transferFrom}.3 *4 * Vyvolá událost {Approval} udávající aktualizované povolení. To není5 * vyžadováno EIP. Viz poznámka na začátku {ERC20}.6 *7 * Požadavky:8 *9 * - `sender` a `recipient` nemohou být nulová adresa.10 * - `sender` musí mít zůstatek alespoň `amount`.11 * - volající musí mít povolení pro tokeny ``sender`` alespoň12 * `amount`.13 */14 function transferFrom(address sender, address recipient, uint256 amount) public virtual15 override returns (bool) {16 _transfer(sender, recipient, amount);Zobrazit vše
Volání funkce a.sub(b, "zpráva") provádí dvě věci. Nejprve vypočítá a-b, což je nové povolení.
Zadruhé zkontroluje, zda tento výsledek není záporný. Pokud je záporný, volání se vrátí s poskytnutou zprávou. Všimněte si, že když se volání vrátí, veškeré předchozí zpracování během tohoto volání se ignoruje, takže nemusíme
vracet _transfer.
1 _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,2 "ERC20: transfer amount exceeds allowance"));3 return true;4 }Bezpečnostní doplňky OpenZeppelin
Je nebezpečné nastavit nenulové povolení na jinou nenulovou hodnotu, protože ovládáte pouze pořadí svých vlastních transakcí, nikoli transakcí kohokoli jiného. Představte si, že máte dva uživatele, Alici, která je naivní, a Billa, který je nečestný. Alice chce od Billa nějakou službu, která si myslí, že stojí pět tokenů – takže dává Billovi povolení na pět tokenů.
Pak se něco změní a Billova cena stoupne na deset tokenů. Alice, která stále chce službu, pošle transakci, která nastaví Billovo povolení na deset. V okamžiku, kdy Bill uvidí tuto novou transakci v poolu transakcí, pošle transakci, která utratí Aliciných pět tokenů a má mnohem vyšší cenu paliva, takže bude vytěžena rychleji. Tímto způsobem může Bill nejprve utratit pět tokenů a poté, jakmile je vytěženo nové povolení Alice, utratit dalších deset za celkovou cenu patnácti tokenů, což je více, než Alice zamýšlela autorizovat. Tato technika se nazývá front-runningopens in a new tab
| Transakce Alice | Nonce Alice | Transakce Billa | Nonce Billa | Billovo povolení | Billův celkový příjem od Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| approve(Bill, 10) | 11 | 10 | 5 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 15 |
Abyste se tomuto problému vyhnuli, tyto dvě funkce (increaseAllowance a decreaseAllowance) vám umožňují
upravit povolenou částku o určitou částku. Takže pokud Bill už utratil pět tokenů, bude moci utratit
jen dalších pět. V závislosti na načasování existují dva způsoby, jak to může fungovat, oba z
nichž končí tím, že Bill dostane pouze deset tokenů:
A:
| Transakce Alice | Nonce Alice | Transakce Billa | Nonce Billa | Billovo povolení | Billův celkový příjem od Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| transferFrom(Alice, Bill, 5) | 10,123 | 0 | 5 | ||
| increaseAllowance(Bill, 5) | 11 | 0+5 = 5 | 5 | ||
| transferFrom(Alice, Bill, 5) | 10,124 | 0 | 10 |
B:
| Transakce Alice | Nonce Alice | Transakce Billa | Nonce Billa | Billovo povolení | Billův celkový příjem od Alice |
|---|---|---|---|---|---|
| approve(Bill, 5) | 10 | 5 | 0 | ||
| increaseAllowance(Bill, 5) | 11 | 5+5 = 10 | 0 | ||
| transferFrom(Alice, Bill, 10) | 10,124 | 0 | 10 |
1 /**2 * @dev Atomicky zvyšuje povolení udělené `spenderu` volajícím.3 *4 * Toto je alternativa k {approve}, kterou lze použít jako zmírnění pro5 * problémy popsané v {IERC20-approve}.6 *7 * Vyvolá událost {Approval} udávající aktualizované povolení.8 *9 * Požadavky:10 *11 * - `spender` nemůže být nulová adresa.12 */13 function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {14 _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));15 return true;16 }Zobrazit všeFunkce a.add(b) je bezpečné sčítání. V nepravděpodobném případě, že a+b>=2^256, se neobtočí
jako normální sčítání.
12 /**3 * @dev Atomicky snižuje povolení udělené `spenderu` volajícím.4 *5 * Toto je alternativa k {approve}, kterou lze použít jako zmírnění pro6 * problémy popsané v {IERC20-approve}.7 *8 * Vyvolá událost {Approval} udávající aktualizované povolení.9 *10 * Požadavky:11 *12 * - `spender` nemůže být nulová adresa.13 * - `spender` musí mít povolení pro volajícího alespoň14 * `subtractedValue`.15 */16 function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {17 _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue,18 "ERC20: decreased allowance below zero"));19 return true;20 }Zobrazit všeFunkce, které upravují informace o tokenu
Toto jsou čtyři funkce, které provádějí skutečnou práci: _transfer, _mint, _burn a _approve.
Funkce _transfer
1 /**2 * @dev Přesune tokeny v množství `amount` od `odesílatele` k `příjemci`.3 *4 * Tato interní funkce je ekvivalentní {transfer} a může být použita5 * například k implementaci automatických poplatků za tokeny, mechanismů slashing atd.6 *7 * Vyvolá událost {Transfer}.8 *9 * Požadavky:10 *11 * - `sender` nemůže být nulová adresa.12 * - `recipient` nemůže být nulová adresa.13 * - `sender` musí mít zůstatek alespoň `amount`.14 */15 function _transfer(address sender, address recipient, uint256 amount) internal virtual {Zobrazit všeTato funkce, _transfer, převádí tokeny z jednoho účtu na druhý. Volá se jak
transfer (pro převody z vlastního účtu odesílatele), tak transferFrom (pro použití povolení
k převodu z cizího účtu).
1 require(sender != address(0), "ERC20: transfer from the zero address");2 require(recipient != address(0), "ERC20: transfer to the zero address");Nikdo ve skutečnosti nevlastní adresu nula v Ethereu (to znamená, že nikdo nezná soukromý klíč, jehož odpovídající veřejný klíč je transformován na nulovou adresu). Když lidé používají tuto adresu, obvykle se jedná o softwarovou chybu – takže selžeme, pokud je jako odesílatel nebo příjemce použita nulová adresa.
1 _beforeTokenTransfer(sender, recipient, amount);2Existují dva způsoby, jak použít tento kontrakt:
- Použijte jej jako šablonu pro svůj vlastní kód
- Dědit z nějopens in a new tab a přepsat pouze ty funkce, které potřebujete upravit
Druhá metoda je mnohem lepší, protože kód OpenZeppelin ERC-20 již byl auditován a prokázal se jako bezpečný. Když používáte dědičnost, je jasné, jaké funkce upravujete, a aby lidé důvěřovali vašemu kontraktu, stačí jim auditovat pouze tyto konkrétní funkce.
Často je užitečné provést funkci pokaždé, když tokeny změní majitele. Nicméně _transfer je velmi důležitá funkce a je
možné ji napsat nezabezpečeně (viz níže), takže je nejlepší ji nepřepisovat. Řešením je _beforeTokenTransfer, funkce hookopens in a new tab. Tuto funkci můžete přepsat a bude volána při každém převodu.
1 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");2 _balances[recipient] = _balances[recipient].add(amount);Toto jsou řádky, které skutečně provádějí převod. Všimněte si, že mezi nimi nic není a že odečítáme převedenou částku od odesílatele před jejím přičtením k příjemci. To je důležité, protože kdyby uprostřed bylo volání jiného kontraktu, mohlo by být použito k podvádění tohoto kontraktu. Tímto způsobem je převod atomický, uprostřed se nemůže nic stát.
1 emit Transfer(sender, recipient, amount);2 }Nakonec vyvoláme událost Transfer. Události nejsou přístupné chytrým kontraktům, ale kód spuštěný mimo blockchain
může naslouchat událostem a reagovat na ně. Například peněženka může sledovat, kdy majitel získá více tokenů.
Funkce _mint a _burn
Tyto dvě funkce (_mint a _burn) upravují celkovou zásobu tokenů.
Jsou interní a v tomto kontraktu je nevolá žádná funkce,
takže jsou užitečné pouze v případě, že z kontraktu dědíte a přidáte si vlastní
logiku k rozhodnutí, za jakých podmínek se mají nové tokeny razit nebo stávající
pálit.
POZNÁMKA: Každý token ERC-20 má svou vlastní obchodní logiku, která určuje správu tokenů.
Například kontrakt s pevnou zásobou může volat _mint pouze
v konstruktoru a nikdy nevolat _burn. Kontrakt, který prodává tokeny,
volá _mint, když je zaplaceno, a pravděpodobně v určitém okamžiku volá _burn,
aby se zabránilo nekontrolovatelné inflaci.
1 /** @dev Vytvoří `amount` tokenů a přiřadí je `účtu`, čímž se zvýší2 * celková zásoba.3 *4 * Vyvolá událost {Transfer} s `from` nastaveným na nulovou adresu.5 *6 * Požadavky:7 *8 * - `to` nemůže být nulová adresa.9 */10 function _mint(address account, uint256 amount) internal virtual {11 require(account != address(0), "ERC20: mint to the zero address");12 _beforeTokenTransfer(address(0), account, amount);13 _totalSupply = _totalSupply.add(amount);14 _balances[account] = _balances[account].add(amount);15 emit Transfer(address(0), account, amount);16 }Zobrazit všeNezapomeňte aktualizovat _totalSupply, když se změní celkový počet tokenů.
1 /**2 * @dev Zničí `amount` tokenů z `účtu`, čímž se sníží3 * celková zásoba.4 *5 * Vyvolá událost {Transfer} s `to` nastaveným na nulovou adresu.6 *7 * Požadavky:8 *9 * - `účet` nemůže být nulová adresa.10 * - `účet` musí mít alespoň `amount` tokenů.11 */12 function _burn(address account, uint256 amount) internal virtual {13 require(account != address(0), "ERC20: burn from the zero address");1415 _beforeTokenTransfer(account, address(0), amount);1617 _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");18 _totalSupply = _totalSupply.sub(amount);19 emit Transfer(account, address(0), amount);20 }Zobrazit všeFunkce _burn je téměř identická s _mint, jen jde opačným směrem.
Funkce _approve
Toto je funkce, která skutečně specifikuje povolení. Všimněte si, že umožňuje vlastníkovi specifikovat povolení, které je vyšší než aktuální zůstatek vlastníka. To je v pořádku, protože zůstatek se kontroluje v okamžiku převodu, kdy se může lišit od zůstatku v době vytvoření povolení .
1 /**2 * @dev Nastaví `amount` jako povolení `spenderu` pro tokeny `vlastníka`.3 *4 * Tato interní funkce je ekvivalentní `approve` a může být použita5 * například k nastavení automatických povolení pro určité subsystémy atd.6 *7 * Vyvolá událost {Approval}.8 *9 * Požadavky:10 *11 * - `owner` nemůže být nulová adresa.12 * - `spender` nemůže být nulová adresa.13 */14 function _approve(address owner, address spender, uint256 amount) internal virtual {15 require(owner != address(0), "ERC20: approve from the zero address");16 require(spender != address(0), "ERC20: approve to the zero address");1718 _allowances[owner][spender] = amount;Zobrazit vše
Vyvolat událost Approval. V závislosti na tom, jak je aplikace napsána, může být kontraktu utrácejícího o
schválení sděleno buď vlastníkem, nebo serverem, který naslouchá těmto událostem.
1 emit Approval(owner, spender, amount);2 }3Upravit proměnnou Decimals
123 /**4 * @dev Nastaví {decimals} na jinou hodnotu než výchozí 18.5 *6 * VAROVÁNÍ: Tuto funkci byste měli volat pouze z konstruktoru. Většina7 * aplikací, které interagují s tokenovými kontrakty, neočekává,8 * že se {decimals} někdy změní, a mohou fungovat nesprávně, pokud ano.9 */10 function _setupDecimals(uint8 decimals_) internal {11 _decimals = decimals_;12 }Zobrazit všeTato funkce upravuje proměnnou _decimals, která se používá k tomu, aby uživatelským rozhraním sdělila, jak interpretovat částku.
Měli byste ji volat z konstruktoru. Bylo by nečestné volat ji v jakémkoli následujícím bodě a aplikace
nejsou navrženy tak, aby to zvládly.
Háčky
12 /**3 * @dev Záchytný bod (hook), který se volá před jakýmkoli převodem tokenů. To zahrnuje4 * ražbu a pálení.5 *6 * Podmínky volání:7 *8 * – když `from` a `to` jsou obě nenulové, bude převeden `amount` tokenů z adresy ``from``9 * na adresu `to`.10 * – když je `from` nulové, bude pro `to` vyraženo `amount` tokenů.11 * – když je `to` nulové, `amount` tokenů z adresy ``from`` bude spáleno.12 * – `from` a `to` nejsou nikdy obě nulové.13 *14 * Více informací o záchytných bodech (hooks) najdete v xref:ROOT:extending-contracts.adoc#using-hooks[Používání záchytných bodů].15 */16 function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }17}Zobrazit všeToto je funkce háku, která se má volat během převodů. Zde je prázdná, ale pokud potřebujete, aby něco dělala, stačí ji přepsat.
Závěr
Pro přehled, zde jsou některé z nejdůležitějších myšlenek v tomto kontraktu (podle mého názoru se váš může lišit):
- Na blockchainu neexistují žádná tajemství. Jakékoli informace, ke kterým má chytrý kontrakt přístup, jsou dostupné celému světu.
- Můžete ovládat pořadí svých vlastních transakcí, ale ne to, kdy se uskuteční transakce jiných lidí. To je důvod, proč může být změna povolení nebezpečná, protože umožňuje utrácejícímu utratit součet obou povolení.
- Hodnoty typu
uint256se obtáčejí. Jinými slovy, 0-1=2^256-1. Pokud to není požadované chování, musíte to zkontrolovat (nebo použít knihovnu SafeMath, která to udělá za vás). Všimněte si, že se to změnilo v Solidity 0.8.0opens in a new tab. - Provádějte všechny změny stavu určitého typu na určitém místě, protože to usnadňuje auditování.
To je důvod, proč máme například
_approve, které je volánoapprove,transferFrom,increaseAllowanceadecreaseAllowance - Změny stavu by měly být atomické, bez jakékoli jiné akce uprostřed (jak můžete vidět
v
_transfer). Je to proto, že během změny stavu máte nekonzistentní stav. Například mezi dobou, kdy odečtete ze zůstatku odesílatele, a dobou, kdy přičtete k zůstatku příjemce, existuje méně tokenů, než by mělo být. To by mohlo být potenciálně zneužito, pokud mezi nimi probíhají operace, zejména volání jiného kontraktu.
Nyní, když jste viděli, jak je napsán kontrakt OpenZeppelin ERC-20 a zejména jak je zabezpečen, jděte a pište své vlastní bezpečné kontrakty a aplikace.
Více z mé práce najdete zdeopens in a new tab.
Stránka naposledy aktualizována: 22. října 2025
