Покроковий опис контракту Vyper ERC-721
Вступ
Стандарт 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 ERC7212
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 змінюється будь-яким механізмом. Ця подія генерується, коли NFT2# створюються (`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 для ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a76
7# @dev ID інтерфейсу ERC165 для ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdERC-165 (opens in a new tab) визначає механізм, за допомогою якого контракт може розкривати, як додатки можуть з ним взаємодіяти, і яким стандартам ERC він відповідає. У цьому випадку контракт відповідає стандартам ERC-165 та ERC-721.
Функції
Це функції, які фактично реалізують ERC-721.
Конструктор
1@external2def __init__():У Vyper, як і в Python, функція-конструктор називається __init__.
1 """2 @dev Конструктор контракту.3 """У Python і Vyper ви також можете створити коментар, вказавши багаторядковий рядок (який починається і закінчується
"""), і не використовувати його жодним чином. Ці коментарі також можуть містити
NatSpec (opens in a new tab).
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderЩоб отримати доступ до змінних стану, використовуйте self.<ім’я змінної> (знову ж таки, як і в Python).
Функції перегляду
Це функції, які не змінюють стан блокчейну, і тому можуть виконуватися безплатно, якщо вони викликаються ззовні. Якщо функції перегляду викликаються контрактом, вони все одно повинні виконуватися на кожному вузлі, отже, коштуватимуть газ.
1@view2@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@view2@external3def 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@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Повертає адресу власника NFT.8 Видає помилку, якщо `_tokenId` не є дійсним NFT.9 @param _tokenId Ідентифікатор NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Видає помилку, якщо `_tokenId` не є дійсним NFT13 assert owner != ZERO_ADDRESS14 return ownerУ віртуальній машині Ethereum (EVM) будь-яке сховище, в якому не зберігається значення, дорівнює нулю.
Якщо за адресою _tokenId немає токена, то значення self.idToOwner[_tokenId] дорівнює нулю. У такому
випадку функція скасовується.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Отримати затверджену адресу для одного NFT.6 Видає помилку, якщо `_tokenId` не є дійсним NFT.7 @param _tokenId ID NFT, для якого потрібно запитати затвердження.8 """9 # Видає помилку, якщо `_tokenId` не є дійсним NFT10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Зверніть увагу, що getApproved може повертати нуль. Якщо токен дійсний, він повертає self.idToApprovals[_tokenId].
Якщо немає затверджувача, це значення дорівнює нулю.
1@view2@external3def 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@view5@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 == _spender11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAllІснує три способи, за допомогою яких адреса може отримати дозвіл на передачу токена:
- Адреса є власником токена
- Адреса затверджена для витрачання цього токена
- Адреса є оператором для власника токена
Наведена вище функція може бути функцією перегляду, тому що вона не змінює стан. Щоб зменшити операційні витрати, будь-яка функція, яка може бути функцією перегляду, повинна нею бути.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Додати NFT до даної адреси5 Видає помилку, якщо `_tokenId` належить комусь.6 """7 # Видає помилку, якщо `_tokenId` належить комусь8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Змінити власника10 self.idToOwner[_tokenId] = _to11 # Змінити відстеження кількості12 self.ownerToNFTokenCount[_to] += 113
14
15@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Видалити NFT з даної адреси19 Видає помилку, якщо `_from` не є поточним власником.20 """21 # Видає помилку, якщо `_from` не є поточним власником22 assert self.idToOwner[_tokenId] == _from23 # Змінити власника24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Змінити відстеження кількості26 self.ownerToNFTokenCount[_from] -= 1Коли виникає проблема з переказом, ми скасовуємо виклик.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Очистити затвердження для даної адреси5 Видає помилку, якщо `_owner` не є поточним власником.6 """7 # Видає помилку, якщо `_owner` не є поточним власником8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Скинути затвердження11 self.idToApprovals[_tokenId] = ZERO_ADDRESSЗмінюйте значення тільки в разі необхідності. Змінні стану зберігаються в сховищі. Запис у сховище — одна з найдорожчих операцій, які виконує EVM (віртуальна машина Ethereum) (з точки зору газу). Тому рекомендується звести його до мінімуму, оскільки навіть запис наявного значення має високу вартість.
1@internal2def _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_ADDRESS5 # Очистити затвердження. Видає помилку, якщо `_from` не є поточним власником6 self._clearApproval(_from, _tokenId)7 # Видалити NFT. Видає помилку, якщо `_tokenId` не є дійсним NFT8 self._removeTokenFrom(_from, _tokenId)9 # Додати NFT10 self._addTokenTo(_to, _tokenId)11 # Зареєструвати переказ12 log Transfer(_from, _to, _tokenId)Щоб згенерувати подію у Vyper, використовуйте оператор log (докладніше тут (opens in a new tab)).
Функції переказу
1
2### ФУНКЦІЇ ПЕРЕКАЗУ ###3
4@external5def 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@external2def 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@external2def 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` не є дійсним NFT13 assert owner != ZERO_ADDRESS14 # Видає помилку, якщо `_approved` є поточним власником15 assert _approved != ownerЗа угодою, якщо ви не хочете мати затверджувача, ви призначаєте нульову адресу, а не свою.
1 # Перевірка вимог2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Щоб встановити схвалення, ви можете бути або власником, або оператором, уповноваженим власником.
1 # Встановити затвердження2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)4
5
6@external7def 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.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Карбування нових токенів і знищення наявних
Обліковий запис, який створив контракт, є minter — суперкористувачем, якому дозволено карбувати
нові NFT. Однак, навіть йому не дозволяється знищувати наявні токени. Це може зробити тільки власник або уповноважена власником
особа.
1### ФУНКЦІЇ КАРБУВАННЯ ТА СПАЛЮВАННЯ ###2
3@external4def 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_ADDRESS3 # Додати NFT. Видає помилку, якщо `_tokenId` належить комусь4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TrueЗа угодою, карбування нових токенів вважається переказом із нульової адреси.
1
2@external3def 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` не є дійсним NFT15 assert owner != ZERO_ADDRESS16 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 р.