Огляд контракту ERC-721 на Vyper
Вступ
Стандарт ERC-721 використовується для зберігання права власності на невзаємозамінні токени (NFT). Токени ERC-20 поводяться як товар, оскільки між окремими токенами немає різниці. Натомість токени ERC-721 розроблені для активів, які є схожими, але не ідентичними, наприклад, різні мультяшні коти (opens in a new tab) або права власності на різні об'єкти нерухомості.
У цій статті ми проаналізуємо контракт ERC-721 від Рюї Накамури (opens in a new tab). Цей контракт написано на Vyper (opens in a new tab), мові контрактів, подібній до Python, яка розроблена так, щоб ускладнити написання небезпечного коду порівняно з Solidity.
Контракт
# @dev Реалізація стандарту невзаємозамінного токена ERC-721.
# @author Ryuya Nakamura (@nrryuya)
# Змінено з: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Коментарі у Vyper, як і в Python, починаються з хеша (#) і тривають до кінця рядка. Коментарі, що містять
@<keyword>, використовуються NatSpec (opens in a new tab) для створення зрозумілої для людини
документації.
from vyper.interfaces import ERC721
implements: ERC721
Інтерфейс ERC-721 вбудовано в мову Vyper. Ви можете переглянути визначення коду тут (opens in a new tab). Визначення інтерфейсу написано на Python, а не на Vyper, оскільки інтерфейси використовуються не лише в межах блокчейну, але й під час надсилання транзакції до блокчейну від зовнішнього клієнта, який може бути написаний на Python.
Перший рядок імпортує інтерфейс, а другий вказує, що ми реалізуємо його тут.
Інтерфейс ERC721Receiver
# Інтерфейс для контракту, який викликається safeTransferFrom()
interface ERC721Receiver:
def onERC721Received(
ERC-721 підтримує два типи переказу:
transferFrom, який дозволяє відправнику вказати будь-яку адресу призначення та покладає відповідальність за переказ на відправника. Це означає, що ви можете здійснити переказ на недійсну адресу, і в такому разі NFT буде втрачено назавжди.safeTransferFrom, який перевіряє, чи є адреса призначення контрактом. Якщо так, контракт ERC-721 запитує контракт-отримувач, чи хоче він отримати NFT.
Щоб відповідати на запити safeTransferFrom, контракт-отримувач повинен реалізувати ERC721Receiver.
_operator: address,
_from: address,
Адреса _from — це поточний власник токена. Адреса _operator — це та, що
надіслала запит на переказ (ці дві адреси можуть не збігатися через дозволи).
_tokenId: uint256,
Ідентифікатори токенів ERC-721 мають розмір 256 біт. Зазвичай вони створюються шляхом хешування опису того, що представляє токен.
_data: Bytes[1024]
Запит може містити до 1024 байтів даних користувача.
) -> bytes32: view
Щоб запобігти випадкам, коли контракт випадково приймає переказ, значення, що повертається, є не логічним (boolean), а 256-бітним із певним значенням.
Ця функція є view, що означає, що вона може читати стан блокчейну, але не змінювати його.
Події
Події генеруються для інформування користувачів і серверів за межами блокчейну про події. Зверніть увагу, що вміст подій недоступний для контрактів у блокчейні.
# @dev Генерується, коли право власності на будь-який NFT змінюється будь-яким механізмом. Ця подія генерується, коли NFT
# створюються (`from` == 0) та знищуються (`to` == 0). Виняток: під час створення контракту будь-яка
# кількість NFT може бути створена та призначена без генерування події Transfer. Під час будь-якого
# переказу схвалена адреса для цього NFT (якщо є) скидається.
# @param _from Відправник NFT (якщо адреса — нульова адреса, це вказує на створення токена).
# @param _to Отримувач NFT (якщо адреса — нульова адреса, це вказує на знищення токена).
# @param _tokenId NFT, який був переказаний.
event Transfer:
sender: indexed(address)
receiver: indexed(address)
tokenId: indexed(uint256)
Це схоже на подію Transfer в ERC-20, за винятком того, що ми повідомляємо tokenId замість суми.
Ніхто не володіє нульовою адресою, тому за домовленістю ми використовуємо її для повідомлення про створення та знищення токенів.
# @dev Це генерується, коли схвалена адреса для NFT змінюється або підтверджується. Нульова
# адреса вказує на те, що немає схваленої адреси. Коли генерується подія Transfer, це також
# вказує на те, що схвалена адреса для цього NFT (якщо є) скидається.
# @param _owner Власник NFT.
# @param _approved Адреса, яку ми схвалюємо.
# @param _tokenId NFT, який ми схвалюємо.
event Approval:
owner: indexed(address)
approved: indexed(address)
tokenId: indexed(uint256)
Схвалення (approval) в ERC-721 схоже на дозвіл (allowance) в ERC-20. Певній адресі дозволяється переказувати певний токен. Це надає механізм для контрактів реагувати, коли вони приймають токен. Контракти не можуть прослуховувати події, тому якщо ви просто перекажете їм токен, вони про це не «дізнаються». Таким чином, власник спочатку подає схвалення, а потім надсилає запит до контракту: «Я схвалив для вас переказ токена X, будь ласка, зробіть...».
Це архітектурне рішення, щоб зробити стандарт ERC-721 схожим на стандарт ERC-20. Оскільки токени ERC-721 не є взаємозамінними, контракт також може визначити, що він отримав певний токен, подивившись на право власності на токен.
# @dev Це генерується, коли оператор вмикається або вимикається для власника. Оператор може керувати
# всіма NFT власника.
# @param _owner Власник NFT.
# @param _operator Адреса, якій ми встановлюємо права оператора.
# @param _approved Статус прав оператора (true, якщо права оператора надані, і false, якщо
# відкликані).
event ApprovalForAll:
owner: indexed(address)
operator: indexed(address)
approved: bool
Іноді корисно мати оператора, який може керувати всіма токенами акаунта певного типу (тими, якими керує певний контракт), подібно до довіреності. Наприклад, я можу захотіти надати такі повноваження контракту, який перевіряє, чи не звертався я до нього протягом шести місяців, і якщо так, розподіляє мої активи між моїми спадкоємцями (якщо хтось із них попросить про це, контракти не можуть нічого зробити без виклику транзакцією). В ERC-20 ми можемо просто надати великий дозвіл контракту на спадкування, але це не працює для ERC-721, оскільки токени не є взаємозамінними. Це є еквівалентом.
Значення approved вказує нам, чи стосується подія схвалення, чи його відкликання.
Змінні стану
Ці змінні містять поточний стан токенів: які з них доступні та кому вони належать. Більшість із них
є об'єктами HashMap, односпрямованими відображеннями, що існують між двома типами (opens in a new tab).
# @dev Відображення ID NFT на адресу, яка ним володіє.
idToOwner: HashMap[uint256, address]
# @dev Відображення ID NFT на схвалену адресу.
idToApprovals: HashMap[uint256, address]
Ідентифікатори користувачів і контрактів в Етеріумі представлені 160-бітними адресами. Ці дві змінні відображають ідентифікатори токенів на їхніх власників і тих, кому дозволено їх переказувати (максимум по одному для кожного). В Етеріумі неініціалізовані дані завжди дорівнюють нулю, тому якщо немає власника або затвердженого відправника, значення для цього токена дорівнює нулю.
# @dev Відображення адреси власника на кількість його токенів.
ownerToNFTokenCount: HashMap[address, uint256]
Ця змінна містить кількість токенів для кожного власника. Немає відображення від власників до токенів, тому
єдиний спосіб ідентифікувати токени, якими володіє певний власник, — це зазирнути в історію подій блокчейну
та знайти відповідні події Transfer. Ми можемо використовувати цю змінну, щоб знати, коли ми знайшли всі NFT і нам не
потрібно шукати далі в часі.
Зверніть увагу, що цей алгоритм працює лише для інтерфейсів користувача та зовнішніх серверів. Код, що виконується в самому блокчейні, не може читати минулі події.
# @dev Відображення адреси власника на відображення адрес операторів.
ownerToOperators: HashMap[address, HashMap[address, bool]]
Акаунт може мати більше одного оператора. Простого HashMap недостатньо для
їх відстеження, оскільки кожен ключ веде до одного значення. Натомість ви можете використовувати
HashMap[address, bool] як значення. За замовчуванням значення для кожної адреси — False, що означає, що вона
не є оператором. Ви можете встановлювати значення True за потреби.
# @dev Адреса карбувальника, який може карбувати токен
minter: address
Нові токени мають якось створюватися. У цьому контракті є єдина сутність, якій дозволено це робити —
minter. Цього, ймовірно, буде достатньо, наприклад, для гри. Для інших цілей може знадобитися
створити складнішу бізнес-логіку.
# @dev Відображення ID інтерфейсу на логічне значення щодо того, чи підтримується він
supportedInterfaces: HashMap[bytes32, bool]
# @dev ID інтерфейсу ERC-165 для ERC-165
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
# @dev ID інтерфейсу ERC-165 для ERC-721
ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
ERC-165 (opens in a new tab) визначає механізм, за допомогою якого контракт може розкривати, як застосунки можуть взаємодіяти з ним, і яким стандартам ERC він відповідає. У цьому випадку контракт відповідає ERC-165 та ERC-721.
Функції
Це функції, які фактично реалізують ERC-721.
Конструктор
@external
def __init__():
У Vyper, як і в Python, функція-конструктор називається __init__.
"""
@dev Конструктор контракту.
"""
У Python і у Vyper ви також можете створити коментар, вказавши багаторядковий рядок (який починається і закінчується
на """), і ніяк його не використовуючи. Ці коментарі також можуть містити
NatSpec (opens in a new tab).
self.supportedInterfaces[ERC165_INTERFACE_ID] = True
self.supportedInterfaces[ERC721_INTERFACE_ID] = True
self.minter = msg.sender
Для доступу до змінних стану ви використовуєте self.<variable name> (знову ж таки, як і в Python).
Функції перегляду (View)
Це функції, які не змінюють стан блокчейну, і тому можуть виконуватися безкоштовно, якщо вони викликаються ззовні. Якщо функції перегляду викликаються контрактом, вони все одно мають виконуватися на кожному вузлі, і тому потребують газу.
@view
@external
Ці ключові слова перед визначенням функції, які починаються зі знака «at» (@), називаються декораторами. Вони
визначають обставини, за яких можна викликати функцію.
@viewвказує, що ця функція є функцією перегляду.@externalвказує, що ця конкретна функція може бути викликана транзакціями та іншими контрактами.
def supportsInterface(_interfaceID: bytes32) -> bool:
На відміну від Python, Vyper є мовою зі статичною типізацією (opens in a new tab).
Ви не можете оголосити змінну або параметр функції без визначення типу даних (opens in a new tab). У цьому випадку вхідним параметром є bytes32, 256-бітне значення
(256 біт — це власний розмір слова Віртуальної машини Етеріуму). На виході отримуємо логічне
значення. За домовленістю імена параметрів функції починаються з підкреслення (_).
"""
@dev Ідентифікація інтерфейсу вказана в ERC-165.
@param _interfaceID ID інтерфейсу
"""
return self.supportedInterfaces[_interfaceID]
Повертає значення з HashMap self.supportedInterfaces, яке встановлюється в конструкторі (__init__).
### ФУНКЦІЇ ПЕРЕГЛЯДУ ###
Це функції перегляду, які роблять інформацію про токени доступною для користувачів та інших контрактів.
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev Повертає кількість NFT, якими володіє `_owner`.
Скасовує, якщо `_owner` — нульова адреса. NFT, призначені на нульову адресу, вважаються недійсними.
@param _owner Адреса, для якої запитується баланс.
"""
assert _owner != ZERO_ADDRESS
Цей рядок стверджує (opens in a new tab), що _owner не
дорівнює нулю. Якщо це так, виникає помилка, і операція скасовується.
return self.ownerToNFTokenCount[_owner]
@view
@external
def ownerOf(_tokenId: uint256) -> address:
"""
@dev Повертає адресу власника NFT.
Скасовує, якщо `_tokenId` не є дійсним NFT.
@param _tokenId Ідентифікатор NFT.
"""
owner: address = self.idToOwner[_tokenId]
# Скасовує, якщо `_tokenId` не є дійсним NFT
assert owner != ZERO_ADDRESS
return owner
У Віртуальній машині Етеріуму (EVM) будь-яке сховище, в якому не збережено значення, дорівнює нулю.
Якщо за адресою _tokenId немає токена, то значення self.idToOwner[_tokenId] дорівнює нулю. У такому
разі функція скасовується.
@view
@external
def getApproved(_tokenId: uint256) -> address:
"""
@dev Отримати схвалену адресу для одного NFT.
Скасовує, якщо `_tokenId` не є дійсним NFT.
@param _tokenId ID NFT для запиту схвалення.
"""
# Скасовує, якщо `_tokenId` не є дійсним NFT
assert self.idToOwner[_tokenId] != ZERO_ADDRESS
return self.idToApprovals[_tokenId]
Зверніть увагу, що getApproved може повертати нуль. Якщо токен дійсний, він повертає self.idToApprovals[_tokenId].
Якщо немає того, хто схвалює, це значення дорівнює нулю.
@view
@external
def isApprovedForAll(_owner: address, _operator: address) -> bool:
"""
@dev Перевіряє, чи є `_operator` схваленим оператором для `_owner`.
@param _owner Адреса, яка володіє NFT.
@param _operator Адреса, яка діє від імені власника.
"""
return (self.ownerToOperators[_owner])[_operator]
Ця функція перевіряє, чи дозволено _operator керувати всіма токенами _owner у цьому контракті.
Оскільки операторів може бути кілька, це дворівневий HashMap.
Допоміжні функції переказу
Ці функції реалізують операції, які є частиною переказу або керування токенами.
### ДОПОМІЖНІ ФУНКЦІЇ ПЕРЕКАЗУ ###
@view
@internal
Цей декоратор, @internal, означає, що функція доступна лише з інших функцій у межах
того самого контракту. За домовленістю імена цих функцій також починаються з підкреслення (_).
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
"""
@dev Повертає, чи може вказаний витрачальник переказати вказаний ID токена
@param spender адреса витрачальника для запиту
@param tokenId uint256 ID токена, який буде переказано
@return bool чи схвалений msg.sender для вказаного ID токена,
чи є він оператором власника, або чи є він власником токена
"""
owner: address = self.idToOwner[_tokenId]
spenderIsOwner: bool = owner == _spender
spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Існує три способи, за допомогою яких адресі може бути дозволено переказати токен:
- Адреса є власником токена
- Адресі дозволено витрачати цей токен
- Адреса є оператором для власника токена
Наведена вище функція може бути функцією перегляду, оскільки вона не змінює стан. Щоб зменшити операційні витрати, будь-яка функція, яка може бути функцією перегляду, повинна нею бути.
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
"""
@dev Додати NFT до вказаної адреси
Скасовує, якщо `_tokenId` належить комусь.
"""
# Скасовує, якщо `_tokenId` належить комусь
assert self.idToOwner[_tokenId] == ZERO_ADDRESS
# Змінити власника
self.idToOwner[_tokenId] = _to
# Змінити відстеження кількості
self.ownerToNFTokenCount[_to] += 1
@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
"""
@dev Видалити NFT з вказаної адреси
Скасовує, якщо `_from` не є поточним власником.
"""
# Скасовує, якщо `_from` не є поточним власником
assert self.idToOwner[_tokenId] == _from
# Змінити власника
self.idToOwner[_tokenId] = ZERO_ADDRESS
# Змінити відстеження кількості
self.ownerToNFTokenCount[_from] -= 1
Коли виникає проблема з переказом, ми скасовуємо виклик.
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev Очистити схвалення для вказаної адреси
Скасовує, якщо `_owner` не є поточним власником.
"""
# Скасовує, якщо `_owner` не є поточним власником
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
# Скинути схвалення
self.idToApprovals[_tokenId] = ZERO_ADDRESS
Змінюйте значення лише за необхідності. Змінні стану живуть у сховищі. Запис у сховище є однією з найдорожчих операцій, які виконує EVM (Віртуальна машина Етеріуму) (з точки зору газу). Тому варто мінімізувати це, адже навіть запис існуючого значення має високу вартість.
@internal
def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
"""
@dev Виконати переказ NFT.
Скасовує, якщо `msg.sender` не є поточним власником, авторизованим оператором або схваленою
адресою для цього NFT. (ПРИМІТКА: `msg.sender` не дозволено у приватній функції, тому передайте `_sender`.)
Скасовує, якщо `_to` — нульова адреса.
Скасовує, якщо `_from` не є поточним власником.
Скасовує, якщо `_tokenId` не є дійсним NFT.
"""
Ми маємо цю внутрішню функцію, оскільки існує два способи переказу токенів (звичайний і безпечний), але ми хочемо, щоб у коді було лише одне місце, де ми це робимо, щоб полегшити аудит.
# Перевірити вимоги
assert self._isApprovedOrOwner(_sender, _tokenId)
# Скасовує, якщо `_to` — нульова адреса
assert _to != ZERO_ADDRESS
# Очистити схвалення. Скасовує, якщо `_from` не є поточним власником
self._clearApproval(_from, _tokenId)
# Видалити NFT. Скасовує, якщо `_tokenId` не є дійсним NFT
self._removeTokenFrom(_from, _tokenId)
# Додати NFT
self._addTokenTo(_to, _tokenId)
# Залогувати переказ
log Transfer(_from, _to, _tokenId)
Щоб згенерувати подію у Vyper, ви використовуєте оператор log (докладніше дивіться тут (opens in a new tab)).
Функції переказу
### ФУНКЦІЇ ПЕРЕКАЗУ ###
@external
def transferFrom(_from: address, _to: address, _tokenId: uint256):
"""
@dev Скасовує, якщо `msg.sender` не є поточним власником, авторизованим оператором або схваленою
адресою для цього NFT.
Скасовує, якщо `_from` не є поточним власником.
Скасовує, якщо `_to` — нульова адреса.
Скасовує, якщо `_tokenId` не є дійсним NFT.
@notice Абонент несе відповідальність за підтвердження того, що `_to` здатний отримувати NFT, інакше
вони можуть бути назавжди втрачені.
@param _from Поточний власник NFT.
@param _to Новий власник.
@param _tokenId NFT для переказу.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
Ця функція дозволяє здійснювати переказ на довільну адресу. Якщо адреса не є користувачем або контрактом, який знає, як переказувати токени, будь-який переказаний вами токен застрягне на цій адресі та стане марним.
@external
def safeTransferFrom(
_from: address,
_to: address,
_tokenId: uint256,
_data: Bytes[1024]=b""
):
"""
@dev Переказує право власності на NFT з однієї адреси на іншу адресу.
Скасовує, якщо `msg.sender` не є поточним власником, авторизованим оператором або
схваленою адресою для цього NFT.
Скасовує, якщо `_from` не є поточним власником.
Скасовує, якщо `_to` — нульова адреса.
Скасовує, якщо `_tokenId` не є дійсним NFT.
Якщо `_to` є смарт-контрактом, він викликає `onERC721Received` на `_to` і скасовує, якщо
значення, що повертається, не є `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
ПРИМІТКА: bytes4 представлено як bytes32 з доповненням
@param _from Поточний власник NFT.
@param _to Новий власник.
@param _tokenId NFT для переказу.
@param _data Додаткові дані без визначеного формату, надіслані у виклику до `_to`.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
Цілком нормально спочатку виконати переказ, оскільки якщо виникне проблема, ми все одно скасуємо операцію, тому все, що було зроблено під час виклику, буде анульовано.
if _to.is_contract: # перевірити, чи є `_to` адресою контракту
Спочатку перевірте, чи є адреса контрактом (чи має вона код). Якщо ні, припустіть, що це адреса користувача,
і користувач зможе використовувати токен або переказати його. Але не дозволяйте цьому приспати вашу
пильність. Ви можете втратити токени, навіть з safeTransferFrom, якщо перекажете
їх на адресу, для якої ніхто не знає приватний ключ.
returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
Викличте цільовий контракт, щоб перевірити, чи може він отримувати токени ERC-721.
# Скасовує, якщо пункт призначення переказу є контрактом, який не реалізує 'onERC721Received'
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
Якщо адреса призначення є контрактом, але таким, що не приймає токени ERC-721 (або який вирішив не приймати цей конкретний переказ), скасуйте операцію.
@external
def approve(_approved: address, _tokenId: uint256):
"""
@dev Встановити або підтвердити схвалену адресу для NFT. Нульова адреса вказує на те, що немає схваленої адреси.
Скасовує, якщо `msg.sender` не є поточним власником NFT або авторизованим оператором поточного власника.
Скасовує, якщо `_tokenId` не є дійсним NFT. (ПРИМІТКА: Це не написано в EIP)
Скасовує, якщо `_approved` є поточним власником. (ПРИМІТКА: Це не написано в EIP)
@param _approved Адреса, яка буде схвалена для вказаного ID NFT.
@param _tokenId ID токена, який буде схвалено.
"""
owner: address = self.idToOwner[_tokenId]
# Скасовує, якщо `_tokenId` не є дійсним NFT
assert owner != ZERO_ADDRESS
# Скасовує, якщо `_approved` є поточним власником
assert _approved != owner
За домовленістю, якщо ви не хочете мати того, хто схвалює, ви призначаєте нульову адресу, а не себе.
# Перевірити вимоги
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
Щоб встановити схвалення, ви можете бути або власником, або оператором, уповноваженим власником.
# Встановити схвалення
self.idToApprovals[_tokenId] = _approved
log Approval(owner, _approved, _tokenId)
@external
def setApprovalForAll(_operator: address, _approved: bool):
"""
@dev Вмикає або вимикає схвалення для третьої сторони ("оператора") керувати всіма
активами `msg.sender`. Це також генерує подію ApprovalForAll.
Скасовує, якщо `_operator` є `msg.sender`. (ПРИМІТКА: Це не написано в EIP)
@notice Це працює, навіть якщо відправник на той момент не володіє жодними токенами.
@param _operator Адреса для додавання до набору авторизованих операторів.
@param _approved True, якщо оператор схвалений, false для відкликання схвалення.
"""
# Скасовує, якщо `_operator` є `msg.sender`
assert _operator != msg.sender
self.ownerToOperators[msg.sender][_operator] = _approved
log ApprovalForAll(msg.sender, _operator, _approved)
Карбування нових токенів і знищення існуючих
Акаунт, який створив контракт, є minter, суперкористувачем, який уповноважений карбувати
нові NFT. Однак навіть йому не дозволено спалювати існуючі токени. Це може зробити лише власник або сутність,
уповноважена власником.
### ФУНКЦІЇ КАРБУВАННЯ ТА СПАЛЮВАННЯ ###
@external
def mint(_to: address, _tokenId: uint256) -> bool:
Ця функція завжди повертає True, оскільки якщо операція не вдається, вона скасовується.
"""
@dev Функція для карбування токенів
Скасовує, якщо `msg.sender` не є карбувальником.
Скасовує, якщо `_to` — нульова адреса.
Скасовує, якщо `_tokenId` належить комусь.
@param _to Адреса, яка отримає викарбувані токени.
@param _tokenId ID токена для карбування.
@return Логічне значення, яке вказує, чи була операція успішною.
"""
# Скасовує, якщо `msg.sender` не є карбувальником
assert msg.sender == self.minter
Лише той, хто карбує (акаунт, який створив контракт ERC-721), може карбувати нові токени. Це може стати проблемою в майбутньому, якщо ми захочемо змінити особу того, хто карбує. У робочому контракті ви, ймовірно, захочете мати функцію, яка дозволяє тому, хто карбує, передати привілеї карбування комусь іншому.
# Скасовує, якщо `_to` — нульова адреса
assert _to != ZERO_ADDRESS
# Додати NFT. Скасовує, якщо `_tokenId` належить комусь
self._addTokenTo(_to, _tokenId)
log Transfer(ZERO_ADDRESS, _to, _tokenId)
return True
За домовленістю карбування нових токенів вважається переказом з нульової адреси.
@external
def burn(_tokenId: uint256):
"""
@dev Спалює конкретний токен ERC-721.
Скасовує, якщо `msg.sender` не є поточним власником, авторизованим оператором або схваленою
адресою для цього NFT.
Скасовує, якщо `_tokenId` не є дійсним NFT.
@param _tokenId uint256 ID токена ERC-721, який буде спалено.
"""
# Перевірити вимоги
assert self._isApprovedOrOwner(msg.sender, _tokenId)
owner: address = self.idToOwner[_tokenId]
# Скасовує, якщо `_tokenId` не є дійсним NFT
assert owner != ZERO_ADDRESS
self._clearApproval(owner, _tokenId)
self._removeTokenFrom(owner, _tokenId)
log Transfer(owner, ZERO_ADDRESS, _tokenId)
Будь-кому, кому дозволено переказувати токен, дозволено його спалювати. Хоча спалювання здається еквівалентним переказу на нульову адресу, нульова адреса насправді не отримує токен. Це дозволяє нам звільнити все сховище, яке використовувалося для токена, що може зменшити витрати газу на транзакцію.
Використання цього контракту
На відміну від Solidity, Vyper не має успадкування. Це свідоме архітектурне рішення, щоб зробити код зрозумілішим і, отже, легшим для захисту. Тому, щоб створити власний контракт ERC-721 на Vyper, ви берете цей контракт (opens in a new tab) і змінюєте його для реалізації потрібної вам бізнес-логіки.
Висновок
Для повторення, ось деякі з найважливіших ідей у цьому контракті:
- Щоб отримувати токени ERC-721 за допомогою безпечного переказу, контракти повинні реалізувати інтерфейс
ERC721Receiver. - Навіть якщо ви використовуєте безпечний переказ, токени все одно можуть застрягти, якщо ви надішлете їх на адресу, приватний ключ якої невідомий.
- Коли виникає проблема з операцією, доцільно
revertвиклик, а не просто повертати значення помилки. - Токени ERC-721 існують, коли вони мають власника.
- Існує три способи отримати дозвіл на переказ NFT. Ви можете бути власником, отримати схвалення для певного токена, або бути оператором для всіх токенів власника.
- Минулі події видимі лише за межами блокчейну. Код, що виконується всередині блокчейну, не може їх переглядати.
Тепер ідіть і реалізуйте безпечні контракти на Vyper.