Přeskočit na hlavní obsah

Průchod kontraktem Vyper ERC-721

vyper
erc-721
python
Začátečník
Ori Pomerantz
1. dubna 2021
17 minuta čtení

Úvod

Standard ERC-721 se používá k držení vlastnictví nezaměnitelných tokenů (NFT). Tokeny ERC-20 se chovají jako komodita, protože mezi jednotlivými tokeny není žádný rozdíl. Naproti tomu tokeny ERC-721 jsou navrženy pro aktiva, která jsou si podobná, ale ne totožná, jako jsou například různé kreslené kočkyopens in a new tab nebo vlastnická práva k různým nemovitostem.

V tomto článku budeme analyzovat kontrakt ERC-721 od Ryuyi Nakamuryopens in a new tab. Tento kontrakt je napsán v jazyce Vyperopens in a new tab, kontraktovém jazyce podobném Pythonu, který je navržen tak, aby bylo psaní nezabezpečeného kódu obtížnější než v Solidity.

Kontrakt

1# @dev Implementace standardu nezaměnitelného tokenu ERC-721.
2# @author Ryuya Nakamura (@nrryuya)
3# Upraveno z: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Komentáře ve Vyperu, stejně jako v Pythonu, začínají mřížkou (#) a pokračují až do konce řádku. Komentáře, které obsahují @<klíčové slovo>, se používají v NatSpecuopens in a new tab k vytvoření lidsky čitelné dokumentace.

1from vyper.interfaces import ERC721
2
3implements: ERC721

Rozhraní ERC-721 je zabudováno do jazyka Vyper. Definici kódu naleznete zdeopens in a new tab. Definice rozhraní je napsána v Pythonu, nikoli ve Vyperu, protože rozhraní se používají nejen v rámci blockchainu, ale také při odesílání transakce do blockchainu z externího klienta, který může být napsán v Pythonu.

První řádek importuje rozhraní a druhý určuje, že ho zde implementujeme.

Rozhraní ERC721Receiver

1# Rozhraní pro kontrakt volaný funkcí safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(

ERC-721 podporuje dva typy převodů:

  • transferFrom, který umožňuje odesílateli zadat jakoukoli cílovou adresu a přenáší odpovědnost za převod na odesílatele. To znamená, že můžete provést převod na neplatnou adresu, v takovém případě je NFT navždy ztraceno.
  • safeTransferFrom, který kontroluje, zda je cílová adresa kontrakt. Pokud ano, kontrakt ERC-721 se zeptá přijímajícího kontraktu, zda chce NFT přijmout.

Aby mohl přijímající kontrakt odpovídat na požadavky safeTransferFrom, musí implementovat ERC721Receiver.

1 _operator: address,
2 _from: address,

Adresa _from je aktuální vlastník tokenu. Adresa _operator je ta, která požadovala převod (tyto dvě adresy se mohou lišit z důvodu povolenek).

1 _tokenId: uint256,

ID tokenů ERC-721 jsou 256bitové. Obvykle se vytvářejí hašováním popisu toho, co token představuje.

1 _data: Bytes[1024]

Požadavek může obsahovat až 1024 bajtů uživatelských dat.

1 ) -> bytes32: view

Aby se předešlo případům, kdy kontrakt omylem přijme převod, není návratová hodnota booleovská, ale 256 bitů s konkrétní hodnotou.

Tato funkce je view, což znamená, že může číst stav blockchainu, ale nemůže ho měnit.

Události

Událostiopens in a new tab se vysílají za účelem informování uživatelů a serverů mimo blockchain o událostech. Všimněte si, že obsah událostí není dostupný pro kontrakty na blockchainu.

1# @dev Vysílá se, když se jakýmkoli mechanismem změní vlastnictví jakéhokoli NFT. Tato událost se vysílá, když jsou NFT
2# vytvořeny (`from` == 0) a zničeny (`to` == 0). Výjimka: během vytváření kontraktu může být
3# vytvořen a přiřazen libovolný počet NFT bez vyslání události Transfer. V okamžiku jakéhokoli
4# převodu se schválená adresa pro dané NFT (pokud existuje) vynuluje.
5# @param _from Odesílatel NFT (pokud je adresa nulová, značí to vytvoření tokenu).
6# @param _to Příjemce NFT (pokud je adresa nulová, značí to zničení tokenu).
7# @param _tokenId NFT, které bylo převedeno.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
Zobrazit vše

Je to podobné události Transfer v ERC-20 s tím rozdílem, že místo částky hlásíme tokenId. Nikdo nevlastní nulovou adresu, takže ji zvykově používáme k hlášení o vytvoření a zničení tokenů.

1# @dev Vysílá se, když je schválená adresa pro NFT změněna nebo znovu potvrzena. Nulová
2# adresa značí, že neexistuje žádná schválená adresa. Když se vyšle událost Transfer, značí to
3# také, že schválená adresa pro dané NFT (pokud existuje) se vynuluje.
4# @param _owner Vlastník NFT.
5# @param _approved Adresa, kterou schvalujeme.
6# @param _tokenId NFT, které schvalujeme.
7event Approval:
8 owner: indexed(address)
9 approved: indexed(address)
10 tokenId: indexed(uint256)
Zobrazit vše

Schválení ERC-721 je podobné povolence v ERC-20. Konkrétní adresa má povoleno převést konkrétní token. To dává kontraktům mechanismus, jak reagovat na přijetí tokenu. Kontrakty nemohou naslouchat událostem, takže pokud jim token pouze převedete, "neví" o tom. Tímto způsobem vlastník nejprve podá schválení a poté zašle kontraktu žádost: "Schválil jsem vám převod tokenu X, prosím, proveďte...".

Jedná se o návrhové rozhodnutí, aby byl standard ERC-721 podobný standardu ERC-20. Protože tokeny ERC-721 nejsou zaměnitelné, může kontrakt také identifikovat, že získal konkrétní token, pohledem na vlastnictví tokenu.

1# @dev Vysílá se, když je operátor pro vlastníka povolen nebo zakázán. Operátor může spravovat
2# všechny NFT vlastníka.
3# @param _owner Vlastník NFT.
4# @param _operator Adresa, které nastavujeme práva operátora.
5# @param _approved Stav práv operátora (true, pokud jsou práva udělena, a false, pokud jsou
6# odvolána).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Zobrazit vše

Někdy je užitečné mít operátora, který může spravovat všechny tokeny určitého typu na účtu (ty, které jsou spravovány konkrétním kontraktem), podobně jako plná moc. Například bych mohl chtít takovou pravomoc udělit kontraktu, který kontroluje, zda jsem ho nekontaktoval po dobu šesti měsíců, a pokud ano, rozdělí můj majetek mým dědicům (pokud o to některý z nich požádá; kontrakty nemohou dělat nic, aniž by byly volány transakcí). V ERC-20 můžeme dědickému kontraktu dát vysokou povolenku, ale to u ERC-721 nefunguje, protože tokeny nejsou zaměnitelné. Toto je ekvivalent.

Hodnota approved nám říká, zda se událost týká schválení, nebo jeho odvolání.

Stavové proměnné

Tyto proměnné obsahují aktuální stav tokenů: které jsou dostupné a kdo je vlastní. Většina z nich jsou objekty HashMap, jednosměrná mapování, která existují mezi dvěma typyopens in a new tab.

1# @dev Mapování z ID NFT na adresu, která jej vlastní.
2idToOwner: HashMap[uint256, address]
3
4# @dev Mapování z ID NFT na schválenou adresu.
5idToApprovals: HashMap[uint256, address]

Identity uživatelů a kontraktů v Ethereu jsou reprezentovány 160bitovými adresami. Tyto dvě proměnné mapují ID tokenů na jejich vlastníky a ty, kteří mají schváleno je převést (maximálně jeden pro každý token). V Ethereu jsou neinicializovaná data vždy nulová, takže pokud pro daný token neexistuje vlastník nebo schválený převodce, je jeho hodnota nulová.

1# @dev Mapování z adresy vlastníka na počet jeho tokenů.
2ownerToNFTokenCount: HashMap[address, uint256]

Tato proměnná uchovává počet tokenů pro každého vlastníka. Neexistuje žádné mapování od vlastníků k tokenům, takže jediný způsob, jak identifikovat tokeny, které konkrétní vlastník vlastní, je podívat se zpět do historie událostí blockchainu a najít příslušné události Transfer. Tuto proměnnou můžeme použít k tomu, abychom věděli, kdy máme všechny NFT a nemusíme se dívat ještě dále do minulosti.

Všimněte si, že tento algoritmus funguje pouze pro uživatelská rozhraní a externí servery. Kód běžící na samotném blockchainu nemůže číst minulé události.

1# @dev Mapování z adresy vlastníka na mapování adres operátorů.
2ownerToOperators: HashMap[address, HashMap[address, bool]]

Účet může mít více než jednoho operátora. Jednoduchá HashMap je pro jejich sledování nedostatečná, protože každý klíč vede k jedné hodnotě. Místo toho můžete jako hodnotu použít HashMap[address, bool]. Standardně je hodnota pro každou adresu False, což znamená, že není operátorem. Podle potřeby můžete nastavit hodnoty na True.

1# @dev Adresa mintera, který může razit tokeny
2minter: address

Nové tokeny musí být nějakým způsobem vytvořeny. V tomto kontraktu existuje jediná entita, která to má povoleno, a to minter. To je například pravděpodobně dostačující pro hru. Pro jiné účely může být nutné vytvořit složitější obchodní logiku.

1# @dev Mapování ID rozhraní na booleovskou hodnotu, zda je či není podporováno
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ID rozhraní ERC165 standardu ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ID rozhraní ERC165 standardu ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

ERC-165opens in a new tab specifikuje mechanismus, jak může kontrakt zveřejnit, jak s ním mohou aplikace komunikovat a kterým standardům ERC odpovídá. V tomto případě kontrakt odpovídá standardům ERC-165 a ERC-721.

Funkce

Toto jsou funkce, které skutečně implementují ERC-721.

Konstruktor

1@external
2def __init__():

Ve Vyperu, stejně jako v Pythonu, se funkce konstruktoru nazývá __init__.

1 """
2 @dev Konstruktor kontraktu.
3 """

V Pythonu a ve Vyperu můžete také vytvořit komentář tak, že zadáte víceřádkový řetězec (který začíná a končí """) a nijak ho nepoužijete. Tyto komentáře mohou obsahovat také NatSpecopens in a new tab.

1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender

Pro přístup ke stavovým proměnným použijte self.<název proměnné>(opět, stejně jako v Pythonu).

Funkce view

Jedná se o funkce, které nemění stav blockchainu, a proto mohou být provedeny zdarma, pokud jsou volány externě. Pokud jsou funkce view volány kontraktem, stále musí být provedeny na každém uzlu, a proto stojí palivo.

1@view
2@external

Tato klíčová slova před definicí funkce, která začínají zavináčem (@), se nazývají dekorace. Určují okolnosti, za kterých lze funkci volat.

  • @view určuje, že tato funkce je view.
  • @external určuje, že tato konkrétní funkce může být volána transakcemi a jinými kontrakty.
1def supportsInterface(_interfaceID: bytes32) -> bool:

Na rozdíl od Pythonu je Vyper jazyk se statickým typovánímopens in a new tab. Nelze deklarovat proměnnou nebo parametr funkce bez identifikace datového typuopens in a new tab. V tomto případě je vstupní parametr bytes32, 256bitová hodnota (256 bitů je nativní velikost slova Ethereum Virtual Machine (EVM)). Výstupem je booleovská hodnota. Názvy parametrů funkcí zvykově začínají podtržítkem (_).

1 """
2 @dev Identifikace rozhraní je specifikována v ERC-165.
3 @param _interfaceID ID rozhraní
4 """
5 return self.supportedInterfaces[_interfaceID]

Vrátí hodnotu z self.supportedInterfaces HashMap, která je nastavena v konstruktoru (__init__).

1### FUNKCE VIEW ###
2

Toto jsou funkce view, které zpřístupňují informace o tokenech uživatelům a jiným kontraktům.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev Vrátí počet NFT vlastněných `_owner`.
6 Vrátí chybu, pokud je `_owner` nulová adresa. NFT přiřazené k nulové adrese jsou považovány za neplatné.
7 @param _owner Adresa, pro kterou se má dotazovat na zůstatek.
8 """
9 assert _owner != ZERO_ADDRESS
Zobrazit vše

Tento řádek zajišťujeopens in a new tab, že _owner není nulová adresa. Pokud ano, dojde k chybě a operace se vrátí zpět.

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 """
7 @dev Vrátí adresu vlastníka NFT.
8 Vrátí chybu, pokud `_tokenId` není platné NFT.
9 @param _tokenId Identifikátor NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Vrátí chybu, pokud `_tokenId` není platné NFT
13 assert owner != ZERO_ADDRESS
14 return owner
Zobrazit vše

V Ethereum Virtual Machine (EVM) je jakékoli úložiště, které v sobě nemá uloženou hodnotu, nulové. Pokud na _tokenId není žádný token, pak je hodnota self.idToOwner[_tokenId] nulová. V takovém případě se funkce vrátí zpět.

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev Získá schválenou adresu pro jedno NFT.
6 Vrátí chybu, pokud `_tokenId` není platné NFT.
7 @param _tokenId ID NFT, pro které se má dotazovat na schválení.
8 """
9 # Vrátí chybu, pokud `_tokenId` není platné NFT
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
Zobrazit vše

Všimněte si, že getApproved může vrátit nulu. Pokud je token platný, vrátí self.idToApprovals[_tokenId]. Pokud neexistuje žádný schvalovatel, tato hodnota je nulová.

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev Zkontroluje, zda je `_operator` schválený operátor pro `_owner`.
6 @param _owner Adresa, která vlastní NFT.
7 @param _operator Adresa, která jedná jménem vlastníka.
8 """
9 return (self.ownerToOperators[_owner])[_operator]
Zobrazit vše

Tato funkce kontroluje, zda má _operator povoleno spravovat všechny tokeny _owner v tomto kontraktu. Protože může existovat více operátorů, jedná se o dvouúrovňovou HashMap.

Pomocné funkce pro převod

Tyto funkce implementují operace, které jsou součástí převodu nebo správy tokenů.

1
2### POMOCNÉ FUNKCE PRO PŘEVOD ###
3
4@view
5@internal

Tato dekorace, @internal, znamená, že funkce je přístupná pouze z jiných funkcí v rámci stejného kontraktu. Názvy těchto funkcí zvykově také začínají podtržítkem (_).

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev Vrátí, zda daný spender může převést dané ID tokenu
4 @param spender adresa spendera, na kterou se dotazujeme
5 @param tokenId uint256 ID tokenu, který má být převeden
6 @return bool zda je msg.sender schválen pro dané ID tokenu,
7 je operátorem vlastníka, nebo je vlastníkem tokenu
8 """
9 owner: address = self.idToOwner[_tokenId]
10 spenderIsOwner: bool = owner == _spender
11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Zobrazit vše

Existují tři způsoby, jak může adresa získat povolení k převodu tokenu:

  1. Adresa je vlastníkem tokenu
  2. Adresa má schváleno utratit tento token
  3. Adresa je operátorem pro vlastníka tokenu

Výše uvedená funkce může být view, protože nemění stav. Pro snížení provozních nákladů by každá funkce, která může být view, měla být view.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Přidá NFT k dané adrese
5 Vrátí chybu, pokud je `_tokenId` vlastněno někým jiným.
6 """
7 # Vrátí chybu, pokud je `_tokenId` vlastněno někým jiným
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Změní vlastníka
10 self.idToOwner[_tokenId] = _to
11 # Změní sledování počtu
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Odebere NFT z dané adresy
19 Vrátí chybu, pokud `_from` není aktuální vlastník.
20 """
21 # Vrátí chybu, pokud `_from` není aktuální vlastník
22 assert self.idToOwner[_tokenId] == _from
23 # Změní vlastníka
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Změní sledování počtu
26 self.ownerToNFTokenCount[_from] -= 1
Zobrazit vše

Pokud se vyskytne problém s převodem, vrátíme volání zpět.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Zruší schválení dané adresy
5 Vrátí chybu, pokud `_owner` není aktuální vlastník.
6 """
7 # Vrátí chybu, pokud `_owner` není aktuální vlastník
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Vynuluje schválení
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Zobrazit vše

Hodnotu změňte pouze v případě nutnosti. Stavové proměnné se nacházejí v úložišti. Zápis do úložiště je jednou z nejdražších operací, které EVM (Ethereum Virtual Machine) provádí (z hlediska paliva). Proto je dobré ji minimalizovat, i zápis existující hodnoty má vysoké náklady.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Provede převod NFT.
5 Vrátí chybu, pokud `msg.sender` není aktuální vlastník, autorizovaný operátor nebo schválená
6 adresa pro toto NFT. (POZNÁMKA: `msg.sender` není povoleno v soukromé funkci, takže se předává `_sender`.)
7 Vrátí chybu, pokud je `_to` nulová adresa.
8 Vrátí chybu, pokud `_from` není aktuální vlastník.
9 Vrátí chybu, pokud `_tokenId` není platné NFT.
10 """
Zobrazit vše

Tuto interní funkci máme proto, že existují dva způsoby převodu tokenů (běžný a bezpečný), ale chceme mít pouze jedno místo v kódu, kde to děláme, abychom usnadnili auditování.

1 # Zkontroluje požadavky
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Vrátí chybu, pokud je `_to` nulová adresa
4 assert _to != ZERO_ADDRESS
5 # Zruší schválení. Vrátí chybu, pokud `_from` není aktuální vlastník
6 self._clearApproval(_from, _tokenId)
7 # Odebere NFT. Vrátí chybu, pokud `_tokenId` není platné NFT
8 self._removeTokenFrom(_from, _tokenId)
9 # Přidá NFT
10 self._addTokenTo(_to, _tokenId)
11 # Zapíše převod do protokolu
12 log Transfer(_from, _to, _tokenId)
Zobrazit vše

Pro vyslání události ve Vyperu použijete příkaz log (více podrobností zdeopens in a new tab).

Funkce pro převod

1
2### FUNKCE PRO PŘEVOD ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Vrátí chybu, pokud `msg.sender` není aktuální vlastník, autorizovaný operátor nebo schválená
8 adresa pro toto NFT.
9 Vrátí chybu, pokud `_from` není aktuální vlastník.
10 Vrátí chybu, pokud je `_to` nulová adresa.
11 Vrátí chybu, pokud `_tokenId` není platné NFT.
12 @notice Volající je odpovědný za potvrzení, že `_to` je schopno přijímat NFT, jinak
13 mohou být trvale ztraceny.
14 @param _from Aktuální vlastník NFT.
15 @param _to Nový vlastník.
16 @param _tokenId NFT k převodu.
17 """
18 self._transferFrom(_from, _to, _tokenId, msg.sender)
Zobrazit vše

Tato funkce umožňuje převod na libovolnou adresu. Pokud adresa není uživatel nebo kontrakt, který ví, jak převádět tokeny, jakýkoli token, který převedete, zůstane na této adrese a bude k ničemu.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev Přenáší vlastnictví NFT z jedné adresy na jinou.
10 Vrátí chybu, pokud `msg.sender` není aktuální vlastník, autorizovaný operátor nebo
11 schválená adresa pro toto NFT.
12 Vrátí chybu, pokud `_from` není aktuální vlastník.
13 Vrátí chybu, pokud je `_to` nulová adresa.
14 Vrátí chybu, pokud `_tokenId` není platné NFT.
15 Pokud je `_to` chytrý kontrakt, volá `onERC721Received` na `_to` a vrátí chybu, pokud
16 návratová hodnota není `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 POZNÁMKA: bytes4 je reprezentováno jako bytes32 s výplní
18 @param _from Aktuální vlastník NFT.
19 @param _to Nový vlastník.
20 @param _tokenId NFT k převodu.
21 @param _data Dodatečná data bez specifikovaného formátu, odeslaná ve volání na `_to`.
22 """
23 self._transferFrom(_from, _to, _tokenId, msg.sender)
Zobrazit vše

Je v pořádku provést převod nejprve, protože pokud se vyskytne problém, stejně se vrátíme zpět, takže vše provedené ve volání bude zrušeno.

1 if _to.is_contract: # zkontroluje, zda je `_to` účet kontraktu

Nejprve zkontrolujte, zda je adresa kontrakt (zda má kód). Pokud ne, předpokládejte, že se jedná o uživatelskou adresu a uživatel bude moci token použít nebo ho převést. Ale nenechte se tím ukolébat do falešného pocitu bezpečí. Můžete ztratit tokeny, i s safeTransferFrom, pokud je převedete na adresu, ke které nikdo nezná privátní klíč.

1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)

Volejte cílový kontrakt, abyste zjistili, zda může přijímat tokeny ERC-721.

1 # Vrátí chybu, pokud je cílem převodu kontrakt, který neimplementuje 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Pokud je cílem kontrakt, ale takový, který nepřijímá tokeny ERC-721 (nebo který se rozhodl nepřijmout tento konkrétní převod), vraťte se zpět.

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev Nastaví nebo znovu potvrdí schválenou adresu pro NFT. Nulová adresa značí, že neexistuje žádná schválená adresa.
5 Vrátí chybu, pokud `msg.sender` není aktuální vlastník NFT nebo autorizovaný operátor aktuálního vlastníka.
6 Vrátí chybu, pokud `_tokenId` není platné NFT. (POZNÁMKA: Toto není uvedeno v EIP)
7 Vrátí chybu, pokud je `_approved` aktuální vlastník. (POZNÁMKA: Toto není uvedeno v EIP)
8 @param _approved Adresa, která má být schválena pro dané ID NFT.
9 @param _tokenId ID tokenu, který má být schválen.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Vrátí chybu, pokud `_tokenId` není platné NFT
13 assert owner != ZERO_ADDRESS
14 # Vrátí chybu, pokud je `_approved` aktuální vlastník
15 assert _approved != owner
Zobrazit vše

Pokud zvykově nechcete mít schvalovatele, jmenujte nulovou adresu, ne sebe.

1 # Zkontroluje požadavky
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)

Pro nastavení schválení můžete být buď vlastníkem, nebo operátorem autorizovaným vlastníkem.

1 # Nastaví schválení
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Povolí nebo zakáže schválení pro třetí stranu („operátora“) ke správě všech
10 aktiv `msg.sender`. Také vysílá událost ApprovalForAll.
11 Vrátí chybu, pokud je `_operator` `msg.sender`. (POZNÁMKA: Toto není uvedeno v EIP)
12 @notice Toto funguje i v případě, že odesílatel v daném okamžiku nevlastní žádné tokeny.
13 @param _operator Adresa, která se má přidat do sady autorizovaných operátorů.
14 @param _approved True, pokud je operátor schválen, false pro odvolání schválení.
15 """
16 # Vrátí chybu, pokud je `_operator` `msg.sender`
17 assert _operator != msg.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
Zobrazit vše

Ražba nových tokenů a zničení stávajících

Účet, který vytvořil kontrakt, je minter, super uživatel, který je oprávněn razit nové NFT. Ani on však nesmí pálit existující tokeny. To může udělat pouze vlastník nebo entita autorizovaná vlastníkem.

1### FUNKCE PRO RAŽBU A PÁLENÍ ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:

Tato funkce vždy vrátí True, protože pokud operace selže, vrátí se zpět.

1 """
2 @dev Funkce pro ražbu tokenů
3 Vrátí chybu, pokud `msg.sender` není minter.
4 Vrátí chybu, pokud je `_to` nulová adresa.
5 Vrátí chybu, pokud je `_tokenId` vlastněno někým jiným.
6 @param _to Adresa, která obdrží ražené tokeny.
7 @param _tokenId ID tokenu k ražbě.
8 @return Booleovská hodnota, která značí, zda byla operace úspěšná.
9 """
Zobrazit vše

Pouze minter (účet, který vytvořil kontrakt ERC-721) může razit nové tokeny. To může být v budoucnu problém, pokud budeme chtít změnit identitu mintera. V produkčním kontraktu byste pravděpodobně chtěli funkci, která minterovi umožní převést práva mintera na někoho jiného.

1 # Vrátí chybu, pokud je `_to` nulová adresa
2 assert _to != ZERO_ADDRESS
3 # Přidá NFT. Vrátí chybu, pokud je `_tokenId` vlastněno někým jiným
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

Ražba nových tokenů se zvykově počítá jako převod z nulové adresy.

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev Spálí konkrétní token ERC721.
6 Vrátí chybu, pokud `msg.sender` není aktuální vlastník, autorizovaný operátor nebo schválená
7 adresa pro toto NFT.
8 Vrátí chybu, pokud `_tokenId` není platné NFT.
9 @param _tokenId uint256 id tokenu ERC721, který má být spálen.
10 """
11 # Zkontroluje požadavky
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Vrátí chybu, pokud `_tokenId` není platné NFT
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Zobrazit vše

Každý, kdo má povoleno převést token, ho smí spálit. Ačkoli se pálení zdá být ekvivalentem převodu na nulovou adresu, nulová adresa ve skutečnosti token neobdrží. To nám umožňuje uvolnit veškeré úložiště, které bylo pro token použito, což může snížit náklady na palivo transakce.

Použití tohoto kontraktu

Na rozdíl od Solidity, Vyper nemá dědičnost. Jedná se o záměrné návrhové rozhodnutí, které má za cíl zpřehlednit kód, a tím usnadnit jeho zabezpečení. Chcete-li tedy vytvořit svůj vlastní kontrakt Vyper ERC-721, vezměte tento kontrakt a upravte ho tak, aby implementoval obchodní logiku, kterou chcete.

Závěr

Pro shrnutí, zde jsou některé z nejdůležitějších myšlenek v tomto kontraktu:

  • Pro příjem tokenů ERC-721 s bezpečným převodem musí kontrakty implementovat rozhraní ERC721Receiver.
  • I když použijete bezpečný převod, tokeny se mohou stále zaseknout, pokud je pošlete na adresu, jejíž privátní klíč je neznámý.
  • Když se vyskytne problém s operací, je dobré volání vrátit, nikoli jen vrátit hodnotu selhání.
  • Tokeny ERC-721 existují, když mají vlastníka.
  • Existují tři způsoby, jak být oprávněn k převodu NFT. Můžete být vlastníkem, být schválen pro konkrétní token, nebo být operátorem pro všechny tokeny vlastníka.
  • Minulé události jsou viditelné pouze mimo blockchain. Kód běžící uvnitř blockchainu je nemůže zobrazit.

Nyní jděte a implementujte bezpečné kontrakty Vyper.

Více z mé práce najdete zdeopens in a new tab.

Stránka naposledy aktualizována: 22. srpna 2025

Byl tento tutoriál užitečný?