Перейти до основного контенту

Покроковий опис контракту Vyper ERC-721

Vyper
erc-721
Python
Початківець
Ori Pomerantz
1 квітня 2021 р.
18 читається за хвилину

Вступ

Стандарт ERC-721 використовується для володіння невзаємозамінними токенами (NFT). Токени ERC-20 поводяться як товар, оскільки між окремими токенами немає різниці. На відміну від цього, токени ERC-721 призначені для активів, які є подібними, але не ідентичними, як-от різні котячі мультики або права власності на різні об’єкти нерухомості.

У цій статті ми проаналізуємо контракт ERC-721 від Рюї Накамури (opens in a new tab). Цей контракт написано мовою Vyper (opens in a new tab), контрактною мовою, подібною до Python, яка розроблена, щоб ускладнити написання незахищеного коду, ніж у Solidity.

Контракт

1# @dev Реалізація стандарту невзаємозамінних токенів ERC-721.
2# @author Рюя Накамура (@nrryuya)
3# Змінено з: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Коментарі у Vyper, як і в Python, починаються з гешу (#) і продовжуються до кінця рядка. Коментарі, що містять @<keyword>, використовуються NatSpec (opens in a new tab) для створення зручної для читання документації.

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

Інтерфейс ERC-721 вбудовано в мову Vyper. Ви можете побачити визначення коду тут (opens in a new tab). Визначення інтерфейсу написано мовою Python, а не Vyper, оскільки інтерфейси використовуються не тільки в блокчейні, але й під час надсилання транзакції в блокчейн із зовнішнього клієнта, який може бути написаний мовою Python.

Перший рядок імпортує інтерфейс, а другий вказує, що ми реалізуємо його тут.

Інтерфейс ERC721Receiver

1# Інтерфейс для контракту, що викликається safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(

ERC-721 підтримує два типи переказів:

  • transferFrom, який дозволяє відправнику вказати будь-яку адресу призначення та покладає відповідальність за переказ на відправника. Це означає, що ви можете зробити переказ на недійсну адресу, і в цьому випадку NFT буде втрачено назавжди.
  • safeTransferFrom, який перевіряє, чи є адреса призначення контрактом. Якщо так, контракт ERC-721 запитує контракт-одержувач, чи хоче він отримати NFT.

Щоб відповідати на запити safeTransferFrom, контракт-одержувач має реалізувати ERC721Receiver.

1 _operator: address,
2 _from: address,

Адреса _from є поточним власником токена. Адреса _operator — це адреса, яка запросила переказ (вони можуть не збігатися через дозволи).

1 _tokenId: uint256,

Ідентифікатори токенів ERC-721 мають розмір 256 біт. Зазвичай вони створюються шляхом гешування опису того, що представляє токен.

1 _data: Bytes[1024]

Запит може містити до 1024 байт даних користувача.

1 ) -> bytes32: view

Щоб запобігти випадкам, коли контракт випадково приймає переказ, значення, що повертається, — це не булеве значення, а 256-бітне значення з певним вмістом.

Ця функція є функцією view, що означає, що вона може читати стан блокчейну, але не змінювати його.

Події

Події (opens in a new tab) генеруються для інформування користувачів і серверів за межами блокчейну про події. Зверніть увагу, що вміст подій недоступний для контрактів у блокчейні.

1# @dev Генерується, коли право власності на будь-який NFT змінюється будь-яким механізмом. Ця подія генерується, коли NFT
2# створюються (`from` == 0) і знищуються (`to` == 0). Виняток: під час створення контракту будь-яка
3# кількість NFT може бути створена та призначена без генерації події Transfer. Під час будь-якого
4# переказу затверджена адреса для цього NFT (якщо така є) скидається на «немає».
5# @param _from Відправник NFT (якщо адреса нульова, це вказує на створення токена).
6# @param _to Одержувач NFT (якщо адреса нульова, це вказує на знищення токена).
7# @param _tokenId NFT, який було передано.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)

Це схоже на подію Transfer в ERC-20, за винятком того, що ми повідомляємо tokenId замість суми. Ніхто не є власником нульової адреси, тому за угодою ми використовуємо її для повідомлення про створення й знищення токенів.

1# @dev Генерується, коли затверджена адреса для NFT змінюється або підтверджується. Нульова
2# адреса вказує на відсутність затвердженої адреси. Коли генерується подія Transfer, це також
3# вказує, що затверджена адреса для цього NFT (якщо така є) скидається на «немає».
4# @param _owner Власник NFT.
5# @param _approved Адреса, яку ми затверджуємо.
6# @param _tokenId NFT, який ми затверджуємо.
7event Approval:
8 owner: indexed(address)
9 approved: indexed(address)
10 tokenId: indexed(uint256)

Затвердження ERC-721 схоже на дозвіл ERC-20. Певна адреса має дозвіл на переказ певного токена. Це дає контрактам механізм для реагування, коли вони приймають токен. Контракти не можуть прослуховувати події, тому, якщо ви просто перекажете їм токен, вони про це не «дізнаються». Таким чином, власник спочатку подає затвердження, а потім надсилає запит до контракту: «Я затвердив для вас переказ токена X, будь ласка, виконайте...».

Це архітектурне рішення, яке робить стандарт ERC-721 схожим на стандарт ERC-20. Оскільки токени ERC-721 не є взаємозамінними, контракт також може визначити, що він отримав певний токен, перевіривши право власності на нього.

1# @dev Генерується, коли оператора вмикають або вимикають для власника. Оператор може керувати
2# всіма NFT власника.
3# @param _owner Власник NFT.
4# @param _operator Адреса, для якої ми встановлюємо права оператора.
5# @param _approved Статус прав оператора (true, якщо права оператора надано, і false, якщо
6# їх скасовано).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool

Іноді корисно мати оператора, який може керувати всіма токенами облікового запису певного типу (тими, якими керує певний контракт), подібно до довіреності. Наприклад, я можу захотіти надати такі повноваження контракту, який перевіряє, чи я не контактував з ним протягом шести місяців, і якщо так, розподіляє мої активи між моїми спадкоємцями (якщо один з них попросить про це, контракти нічого не можуть зробити, поки їх не викличе транзакція). У ERC-20 ми можемо просто надати великий дозвіл контракту на успадкування, але це не працює для ERC-721, оскільки токени не є взаємозамінними. Це еквівалент.

Значення approved говорить нам, чи є подія затвердженням, чи скасуванням затвердження.

Змінні стану

Ці змінні містять поточний стан токенів: які з них доступні й кому вони належать. Більшість з них — це об’єкти HashMap, односпрямовані зіставлення, які існують між двома типами (opens in a new tab).

1# @dev Зіставлення ID NFT з адресою його власника.
2idToOwner: HashMap[uint256, address]
3
4# @dev Зіставлення ID NFT із затвердженою адресою.
5idToApprovals: HashMap[uint256, address]

Ідентифікатори користувачів і контрактів в Ethereum представлені 160-бітними адресами. Ці дві змінні зіставляють ID токенів з їхніми власниками та тими, хто має дозвіл на їх переказ (максимум один на кожен токен). В Ethereum неініціалізовані дані завжди дорівнюють нулю, тому, якщо немає власника або затвердженого відправника, значення для цього токена дорівнює нулю.

1# @dev Зіставлення адреси власника з кількістю його токенів.
2ownerToNFTokenCount: HashMap[address, uint256]

Ця змінна містить кількість токенів для кожного власника. Немає зіставлення власників із токенами, тому єдиний спосіб ідентифікувати токени, якими володіє певний власник, — це переглянути історію подій блокчейну та знайти відповідні події Transfer. Ми можемо використовувати цю змінну, щоб знати, коли ми знайшли всі NFT, і нам не потрібно дивитися ще далі в минуле.

Зверніть увагу, що цей алгоритм працює тільки для інтерфейсів користувачів і зовнішніх серверів. Код, що виконується в самому блокчейні, не може читати минулі події.

1# @dev Зіставлення адреси власника із зіставленням адрес операторів.
2ownerToOperators: HashMap[address, HashMap[address, bool]]

Обліковий запис може мати більше одного оператора. Простого HashMap недостатньо, щоб відстежувати їх, оскільки кожен ключ веде до одного значення. Натомість можна використовувати HashMap[address, bool] як значення. За замовчуванням значення для кожної адреси — False, що означає, що вона не є оператором. За потреби можна встановити значення на True.

1# @dev Адреса карбувальника, який може карбувати токени
2minter: address

Нові токени мають бути створені якимось чином. У цьому контракті є одна сутність, якій дозволено це робити, — minter. Цього, ймовірно, буде достатньо, наприклад, для гри. Для інших цілей може знадобитися створити складнішу бізнес-логіку.

1# @dev Зіставлення ID інтерфейсу з булевим значенням, що вказує на його підтримку
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ID інтерфейсу ERC165 для ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ID інтерфейсу ERC165 для ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

ERC-165 (opens in a new tab) визначає механізм, за допомогою якого контракт може розкривати, як додатки можуть з ним взаємодіяти, і яким стандартам ERC він відповідає. У цьому випадку контракт відповідає стандартам ERC-165 та ERC-721.

Функції

Це функції, які фактично реалізують ERC-721.

Конструктор

1@external
2def __init__():

У Vyper, як і в Python, функція-конструктор називається __init__.

1 """
2 @dev Конструктор контракту.
3 """

У Python і Vyper ви також можете створити коментар, вказавши багаторядковий рядок (який починається і закінчується """), і не використовувати його жодним чином. Ці коментарі також можуть містити NatSpec (opens in a new tab).

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

Щоб отримати доступ до змінних стану, використовуйте self.<ім’я змінної> (знову ж таки, як і в Python).

Функції перегляду

Це функції, які не змінюють стан блокчейну, і тому можуть виконуватися безплатно, якщо вони викликаються ззовні. Якщо функції перегляду викликаються контрактом, вони все одно повинні виконуватися на кожному вузлі, отже, коштуватимуть газ.

1@view
2@external

Ці ключові слова перед визначенням функції, які починаються зі знака «собачка» (@), називаються декораторами. Вони визначають обставини, за яких можна викликати функцію.

  • @view вказує, що ця функція є функцією перегляду.
  • @external вказує, що ця конкретна функція може викликатися транзакціями та іншими контрактами.
1def supportsInterface(_interfaceID: bytes32) -> bool:

На відміну від Python, Vyper є мовою зі статичною типізацією (opens in a new tab). Ви не можете оголосити змінну або параметр функції без визначення типу даних. У цьому випадку вхідним параметром є bytes32, 256-бітне значення (256 біт — це розмір нативного слова віртуальної машини Ethereum). Вихідне значення — це булеве значення. За угодою, імена параметрів функції починаються з символу підкреслення (_).

1 """
2 @dev Ідентифікація інтерфейсу вказана в ERC-165.
3 @param _interfaceID ID інтерфейсу
4 """
5 return self.supportedInterfaces[_interfaceID]

Повертає значення з HashMap self.supportedInterfaces, яке встановлюється в конструкторі (__init__).

1### ФУНКЦІЇ ПЕРЕГЛЯДУ ###
2

Існують функції перегляду, які роблять інформацію про токени доступною для користувачів та інших контрактів.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev Повертає кількість NFT, що належать `_owner`.
6 Видає помилку, якщо `_owner` є нульовою адресою. NFT, призначені нульовій адресі, вважаються недійсними.
7 @param _owner Адреса, для якої потрібно запитати баланс.
8 """
9 assert _owner != ZERO_ADDRESS

Цей рядок стверджує (opens in a new tab), що _owner не є нульовим. Якщо це так, то виникає помилка, і операція скасовується.

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 """
7 @dev Повертає адресу власника NFT.
8 Видає помилку, якщо `_tokenId` не є дійсним NFT.
9 @param _tokenId Ідентифікатор NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Видає помилку, якщо `_tokenId` не є дійсним NFT
13 assert owner != ZERO_ADDRESS
14 return owner

У віртуальній машині Ethereum (EVM) будь-яке сховище, в якому не зберігається значення, дорівнює нулю. Якщо за адресою _tokenId немає токена, то значення self.idToOwner[_tokenId] дорівнює нулю. У такому випадку функція скасовується.

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev Отримати затверджену адресу для одного NFT.
6 Видає помилку, якщо `_tokenId` не є дійсним NFT.
7 @param _tokenId ID NFT, для якого потрібно запитати затвердження.
8 """
9 # Видає помилку, якщо `_tokenId` не є дійсним NFT
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]

Зверніть увагу, що getApproved може повертати нуль. Якщо токен дійсний, він повертає self.idToApprovals[_tokenId]. Якщо немає затверджувача, це значення дорівнює нулю.

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev Перевіряє, чи є `_operator` затвердженим оператором для `_owner`.
6 @param _owner Адреса, яка володіє NFT.
7 @param _operator Адреса, яка діє від імені власника.
8 """
9 return (self.ownerToOperators[_owner])[_operator]

Ця функція перевіряє, чи дозволено _operator керувати всіма токенами _owner у цьому контракті. Оскільки може бути декілька операторів, це дворівнева HashMap.

Допоміжні функції переказу

Ці функції реалізують операції, які є частиною передачі або управління токенами.

1
2### ДОПОМІЖНІ ФУНКЦІЇ ПЕРЕКАЗУ ###
3
4@view
5@internal

Цей декоратор, @internal, означає, що функція доступна тільки з інших функцій у межах того ж контракту. За угодою, назви цих функцій також починаються з символу підкреслення (_).

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev Повертає, чи може даний витрачальник передати даний ID токена
4 @param spender адреса витрачальника для запиту
5 @param tokenId uint256 ID токена, який потрібно передати
6 @return bool чи затверджено msg.sender для даного ID токена,
7 чи є він оператором власника, чи є власником токена
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

Існує три способи, за допомогою яких адреса може отримати дозвіл на передачу токена:

  1. Адреса є власником токена
  2. Адреса затверджена для витрачання цього токена
  3. Адреса є оператором для власника токена

Наведена вище функція може бути функцією перегляду, тому що вона не змінює стан. Щоб зменшити операційні витрати, будь-яка функція, яка може бути функцією перегляду, повинна нею бути.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Додати NFT до даної адреси
5 Видає помилку, якщо `_tokenId` належить комусь.
6 """
7 # Видає помилку, якщо `_tokenId` належить комусь
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Змінити власника
10 self.idToOwner[_tokenId] = _to
11 # Змінити відстеження кількості
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Видалити NFT з даної адреси
19 Видає помилку, якщо `_from` не є поточним власником.
20 """
21 # Видає помилку, якщо `_from` не є поточним власником
22 assert self.idToOwner[_tokenId] == _from
23 # Змінити власника
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Змінити відстеження кількості
26 self.ownerToNFTokenCount[_from] -= 1

Коли виникає проблема з переказом, ми скасовуємо виклик.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Очистити затвердження для даної адреси
5 Видає помилку, якщо `_owner` не є поточним власником.
6 """
7 # Видає помилку, якщо `_owner` не є поточним власником
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Скинути затвердження
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS

Змінюйте значення тільки в разі необхідності. Змінні стану зберігаються в сховищі. Запис у сховище — одна з найдорожчих операцій, які виконує EVM (віртуальна машина Ethereum) (з точки зору газу). Тому рекомендується звести його до мінімуму, оскільки навіть запис наявного значення має високу вартість.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Виконати переказ NFT.
5 Видає помилку, якщо `msg.sender` не є поточним власником, авторизованим оператором або затвердженою
6 адресою для цього NFT. (ПРИМІТКА: `msg.sender` не дозволено у приватній функції, тому передайте `_sender`.)
7 Видає помилку, якщо `_to` є нульовою адресою.
8 Видає помилку, якщо `_from` не є поточним власником.
9 Видає помилку, якщо `_tokenId` не є дійсним NFT.
10 """

Ми маємо цю внутрішню функцію, оскільки існує два способи передачі токенів (звичайний і безпечний), але ми хочемо мати лише одне місце в коді, де ми це робимо, щоб полегшити аудит.

1 # Перевірка вимог
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Видає помилку, якщо `_to` є нульовою адресою
4 assert _to != ZERO_ADDRESS
5 # Очистити затвердження. Видає помилку, якщо `_from` не є поточним власником
6 self._clearApproval(_from, _tokenId)
7 # Видалити NFT. Видає помилку, якщо `_tokenId` не є дійсним NFT
8 self._removeTokenFrom(_from, _tokenId)
9 # Додати NFT
10 self._addTokenTo(_to, _tokenId)
11 # Зареєструвати переказ
12 log Transfer(_from, _to, _tokenId)

Щоб згенерувати подію у Vyper, використовуйте оператор log (докладніше тут (opens in a new tab)).

Функції переказу

1
2### ФУНКЦІЇ ПЕРЕКАЗУ ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Видає помилку, якщо `msg.sender` не є поточним власником, авторизованим оператором або затвердженою
8 адресою для цього NFT.
9 Видає помилку, якщо `_from` не є поточним власником.
10 Видає помилку, якщо `_to` є нульовою адресою.
11 Видає помилку, якщо `_tokenId` не є дійсним NFT.
12 @notice Той, хто викликає, несе відповідальність за підтвердження того, що `_to` здатний отримувати NFT, інакше
13 вони можуть бути назавжди втрачені.
14 @param _from Поточний власник NFT.
15 @param _to Новий власник.
16 @param _tokenId NFT, який потрібно переказати.
17 """
18 self._transferFrom(_from, _to, _tokenId, msg.sender)

Ця функція дозволяє здійснювати переказ на довільну адресу. Якщо адреса не є адресою користувача або контракту, який знає, як передавати токени, будь-який переданий вами токен застрягне на цій адресі й стане марним.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev Передає право власності на NFT з однієї адреси на іншу.
10 Видає помилку, якщо `msg.sender` не є поточним власником, авторизованим оператором або
11 затвердженою адресою для цього NFT.
12 Видає помилку, якщо `_from` не є поточним власником.
13 Видає помилку, якщо `_to` є нульовою адресою.
14 Видає помилку, якщо `_tokenId` не є дійсним NFT.
15 Якщо `_to` — це смарт-контракт, він викликає `onERC721Received` для `_to` і видає помилку, якщо
16 повернуте значення не дорівнює `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 ПРИМІТКА: bytes4 представлено як bytes32 із доповненням
18 @param _from Поточний власник NFT.
19 @param _to Новий власник.
20 @param _tokenId NFT, який потрібно переказати.
21 @param _data Додаткові дані без зазначеного формату, що надсилаються у виклику до `_to`.
22 """
23 self._transferFrom(_from, _to, _tokenId, msg.sender)

Можна спочатку виконати переказ, тому що, якщо виникне проблема, ми все одно скасуємо операцію, тому все, що було зроблено під час виклику, буде скасовано.

1 if _to.is_contract: # перевірка, чи є `_to` адресою контракту

Спочатку перевірте, чи є адреса контрактом (тобто чи має вона код). Якщо ні, припустімо, що це адреса користувача, і користувач зможе використовувати токен або передати його. Але не дозволяйте цьому заколисувати вас хибним почуттям безпеки. Ви можете втратити токени, навіть із safeTransferFrom, якщо ви перекажете їх на адресу, до якої ніхто не знає приватного ключа.

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

Зверніться до цільового контракту, щоб дізнатися, чи може він отримувати токени ERC-721.

1 # Видає помилку, якщо адресат переказу є контрактом, який не реалізує 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Якщо адресатом є контракт, але той, який не приймає токени ERC-721 (або який вирішив не приймати цей конкретний переказ), скасуйте його.

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev Встановити або підтвердити затверджену адресу для NFT. Нульова адреса вказує на відсутність затвердженої адреси.
5 Видає помилку, якщо `msg.sender` не є поточним власником NFT або авторизованим оператором поточного власника.
6 Видає помилку, якщо `_tokenId` не є дійсним NFT. (ПРИМІТКА: це не описано в EIP)
7 Видає помилку, якщо `_approved` є поточним власником. (ПРИМІТКА: це не описано в EIP)
8 @param _approved Адреса, яка буде затверджена для даного ID NFT.
9 @param _tokenId ID токена, який потрібно затвердити.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Видає помилку, якщо `_tokenId` не є дійсним NFT
13 assert owner != ZERO_ADDRESS
14 # Видає помилку, якщо `_approved` є поточним власником
15 assert _approved != owner

За угодою, якщо ви не хочете мати затверджувача, ви призначаєте нульову адресу, а не свою.

1 # Перевірка вимог
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)

Щоб встановити схвалення, ви можете бути або власником, або оператором, уповноваженим власником.

1 # Встановити затвердження
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Вмикає або вимикає затвердження для третьої сторони («оператора») для управління всіма
10 активами `msg.sender`. Також генерує подію ApprovalForAll.
11 Видає помилку, якщо `_operator` є `msg.sender`. (ПРИМІТКА: це не описано в EIP)
12 @notice Це працює, навіть якщо відправник не володіє жодними токенами на даний момент.
13 @param _operator Адреса для додавання до набору авторизованих операторів.
14 @param _approved True, якщо оператор затверджений, false, щоб скасувати затвердження.
15 """
16 # Видає помилку, якщо `_operator` є `msg.sender`
17 assert _operator != msg.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)

Карбування нових токенів і знищення наявних

Обліковий запис, який створив контракт, є minter — суперкористувачем, якому дозволено карбувати нові NFT. Однак, навіть йому не дозволяється знищувати наявні токени. Це може зробити тільки власник або уповноважена власником особа.

1### ФУНКЦІЇ КАРБУВАННЯ ТА СПАЛЮВАННЯ ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:

Ця функція завжди повертає True, тому що в разі збою операція скасовується.

1 """
2 @dev Функція для карбування токенів
3 Видає помилку, якщо `msg.sender` не є карбувальником.
4 Видає помилку, якщо `_to` є нульовою адресою.
5 Видає помилку, якщо `_tokenId` належить комусь.
6 @param _to Адреса, яка отримає викарбувані токени.
7 @param _tokenId Ідентифікатор токена для карбування.
8 @return Булеве значення, яке вказує, чи була операція успішною.
9 """
10 # Видає помилку, якщо `msg.sender` не є карбувальником
11 assert msg.sender == self.minter

Тільки карбувальник (обліковий запис, що створив контракт ERC-721) може карбувати нові токени. Це може стати проблемою в майбутньому, якщо ми захочемо змінити особу карбувальника. У робочому контракті вам, ймовірно, знадобиться функція, що дозволяє карбувальнику передавати його привілеї комусь іншому.

1 # Видає помилку, якщо `_to` є нульовою адресою
2 assert _to != ZERO_ADDRESS
3 # Додати NFT. Видає помилку, якщо `_tokenId` належить комусь
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

За угодою, карбування нових токенів вважається переказом із нульової адреси.

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev Спалює певний токен ERC721.
6 Видає помилку, якщо `msg.sender` не є поточним власником, авторизованим оператором або затвердженою
7 адресою для цього NFT.
8 Видає помилку, якщо `_tokenId` не є дійсним NFT.
9 @param _tokenId uint256 id токена ERC721, який потрібно спалити.
10 """
11 # Перевірка вимог
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Видає помилку, якщо `_tokenId` не є дійсним NFT
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)

Будь-хто, кому дозволено передавати токен, може його спалити. Хоча спалювання здається еквівалентним переказу на нульову адресу, нульова адреса фактично не отримує токен. Це дозволяє нам звільнити все сховище, яке використовувалося для токена, що може знизити вартість транзакції.

Використання цього контракту

На відміну від Solidity, Vyper не має успадкування. Це навмисний вибір архітектури, щоб зробити код більш зрозумілим і, отже, більш безпечним. Отже, щоб створити свій власний контракт Vyper ERC-721, ви берете цей контракт і змінюєте його для реалізації потрібної вам бізнес-логіки.

Висновок

Для повторення, ось деякі з найважливіших ідей цього контракту:

  • Щоб отримувати токени ERC-721 за допомогою безпечного переказу, контракти повинні реалізувати інтерфейс ERC721Receiver.
  • Навіть якщо ви використовуєте безпечний переказ, токени все одно можуть застрягти, якщо ви відправите їх на адресу, приватний ключ до якої невідомий.
  • Коли виникає проблема з операцією, краще скасувати виклик, а не просто повертати значення збою.
  • Токени ERC-721 існують, коли у них є власник.
  • Існує три способи отримати дозвіл на передачу NFT. Ви можете бути власником, бути затвердженим для певного токена або бути оператором для всіх токенів власника.
  • Минулі події видно тільки за межами блокчейну. Код, що виконується всередині блокчейну, не може їх переглядати.

Тепер переходьте до реалізації безпечних контрактів Vyper.

Більше моїх робіт дивіться тут (opens in a new tab).

Останні оновлення сторінки: 3 березня 2026 р.

Чи була ця інструкція корисною?