Разбор контракта ERC-721 на Vyper
Введение
Стандарт ERC-721 используется для хранения прав собственности на невзаимозаменяемые токены (NFT). Токены ERC-20 ведут себя как товар, поскольку между отдельными токенами нет разницы. В отличие от них, токены ERC-721 предназначены для активов, которые похожи, но не идентичны, таких как различные мультяшные коты (открывается в новой вкладке) или права собственности на различные объекты недвижимости.
В этой статье мы проанализируем контракт ERC-721 Рюи Накамуры (открывается в новой вкладке). Этот контракт написан на Vyper (открывается в новой вкладке), Python-подобном языке контрактов, разработанном таким образом, чтобы писать небезопасный код было сложнее, чем на Solidity.
Контракт
# @dev Реализация стандарта невзаимозаменяемого токена ERC-721.
# @author Ryuya Nakamura (@nrryuya)
# Изменено из: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Комментарии в Vyper, как и в Python, начинаются с символа хеша (ethereum.ercs) и продолжаются до конца строки. Комментарии, включающие
@<keyword>, используются NatSpec (открывается в новой вкладке) для создания удобочитаемой документации.
from vyper.interfaces import ERC721
implements: ERC721
Интерфейс ERC-721 встроен в язык Vyper. Вы можете посмотреть определение кода здесь (открывается в новой вкладке). Определение интерфейса написано на Python, а не на Vyper, поскольку интерфейсы используются не только внутри блокчейна, но и при отправке транзакции в блокчейн от внешнего клиента, который может быть написан на Python.
Первая строка импортирует интерфейс, а вторая указывает, что мы реализуем его здесь.
#pragma version >0.3.10
#pragma version >0.3.10
Интерфейс 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 байт пользовательских данных.
) -> bytes4: nonpayable
Чтобы предотвратить случаи, когда контракт случайно принимает перевод, возвращаемое значение является не логическим, а конкретным четырехбайтовым значением — селектором функции onERC721Received. Функция является nonpayable, поскольку принимающий контракт может изменить свое собственное состояние при принятии токена.
События
События генерируются для информирования пользователей и серверов за пределами блокчейна о происходящем. Обратите внимание, что содержимое событий недоступно контрактам в блокчейне. Три события ERC-721 определены в импортированном нами интерфейсе IERC721, поэтому данный контракт не объявляет их сам; он генерирует их с помощью log IERC721.<Event>(...), как мы увидим в функциях перевода ниже.
Событие Transfer (sender, receiver, token_id) сообщает об изменении права собственности на NFT. Это похоже на событие Transfer в ERC-20, за исключением того, что мы сообщаем token_id вместо суммы. Никто не владеет нулевым адресом, поэтому по соглашению мы используем его для сообщения о создании и уничтожении токенов. Единственным исключением является создание контракта, во время которого может быть создано и назначено любое количество NFT без генерации события Transfer.
Одобрение в ERC-721 похоже на разрешение в ERC-20: определенному адресу разрешается переводить определенный токен, и событие Approval (owner, approved, token_id) генерируется всякий раз, когда этот одобренный адрес устанавливается или подтверждается. Это дает контрактам механизм реагирования при принятии токена. Контракты не могут прослушивать события, поэтому, если вы просто переведете им токен, они об этом не «узнают». Таким образом, владелец сначала отправляет одобрение, а затем отправляет запрос контракту: «Я одобрил для вас перевод токена X, пожалуйста, сделайте...». Это архитектурное решение, призванное сделать стандарт ERC-721 похожим на стандарт ERC-20. Поскольку токены ERC-721 не являются взаимозаменяемыми, контракт также может определить, что он получил определенный токен, посмотрев на право собственности на токен.
Наконец, событие ApprovalForAll (owner, operator, approved) генерируется, когда оператор включается или отключается для владельца. Иногда полезно иметь оператора, который может управлять всеми токенами аккаунта определенного типа (теми, которые управляются определенным контрактом), что похоже на доверенность. Например, я могу захотеть дать такие полномочия контракту, который проверяет, не связывался ли я с ним в течение шести месяцев, и если да, распределяет мои активы между моими наследниками (если один из них попросит об этом, контракты ничего не могут сделать без вызова транзакцией). В ERC-20 мы можем просто дать большое разрешение контракту наследования, но это не работает для ERC-721, потому что токены не являются взаимозаменяемыми. Это эквивалент. Значение approved говорит нам, предназначено ли событие для одобрения или для отзыва одобрения.
Переменные состояния
Эти переменные содержат текущее состояние токенов: какие из них доступны и кому они принадлежат. Большинство из них — это объекты HashMap, однонаправленные сопоставления, существующие между двумя типами (открывается в новой вкладке).
# @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 Статический список поддерживаемых идентификаторов интерфейсов ERC165
SUPPORTED_INTERFACES: constant(bytes4[2]) = [
# Идентификатор интерфейса ERC165 для ERC165
0x01ffc9a7,
# Идентификатор интерфейса ERC165 для ERC721
0x80ac58cd,
]
ERC-165 (открывается в новой вкладке) определяет механизм, с помощью которого контракт может раскрывать, как приложения могут взаимодействовать с ним и каким стандартам ERC он соответствует. SUPPORTED_INTERFACES — это константный список из двух четырехбайтовых идентификаторов интерфейсов, которым соответствует этот контракт: самого ERC-165 и ERC-721.
Функции
Это функции, которые фактически реализуют ERC-721.
Конструктор
@deploy
def __init__():
В Vyper, как и в Python, функция-конструктор называется __init__. Она помечена декоратором @deploy, что означает, что она запускается один раз при развертывании контракта.
"""
@dev Конструктор контракта.
"""
В Python и в Vyper вы также можете создать комментарий, указав многострочную строку (которая начинается и заканчивается на """), и никак ее не использовать. Эти комментарии также могут включать NatSpec (открывается в новой вкладке).
self.minter = msg.sender
Для доступа к переменным состояния вы используете self.<имя переменной> (опять же, как в Python). Конструктор записывает аккаунт, развернувший контракт, как minter (чеканщика).
Функции просмотра (View)
Это функции, которые не изменяют состояние блокчейна, и поэтому могут выполняться бесплатно, если они вызываются извне. Если функции просмотра вызываются контрактом, они все равно должны выполняться на каждом узле и поэтому требуют затрат газа.
@view
@external
Эти ключевые слова перед определением функции, которые начинаются со знака «at» (@), называются декораторами. Они определяют обстоятельства, при которых может быть вызвана функция.
@viewуказывает, что эта функция является функцией просмотра.@externalуказывает, что эта конкретная функция может быть вызвана транзакциями и другими контрактами.
def supportsInterface(interface_id: bytes4) -> bool:
В отличие от Python, Vyper является языком со статической типизацией (открывается в новой вкладке). Вы не можете объявить переменную или параметр функции без указания типа данных (открывается в новой вкладке). В данном случае входным параметром является bytes4, четырехбайтовое значение, а выходным — логическое значение.
"""
@dev Идентификация интерфейса указана в ERC-165.
@param interface_id Идентификатор интерфейса
"""
return interface_id in SUPPORTED_INTERFACES
Возвращает True, если interface_id является одним из идентификаторов интерфейсов в списке SUPPORTED_INTERFACES.
### ФУНКЦИИ ПРОСМОТРА ###
Это функции просмотра, которые делают информацию о токенах доступной для пользователей и других контрактов.
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev Возвращает количество NFT, принадлежащих `_owner`.
Вызывает откат, если `_owner` — нулевой адрес. NFT, назначенные нулевому адресу, считаются недействительными.
@param _owner Адрес, для которого запрашивается баланс.
"""
assert _owner != empty(address)
Эта строка утверждает (открывается в новой вкладке), что _owner не является нулевым адресом, записанным как empty(address). Если это так, возникает ошибка, и операция откатывается.
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 != empty(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] != empty(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] == empty(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] = empty(address)
# Изменить отслеживание количества
self.ownerToNFTokenCount[_from] -= 1
Когда возникает проблема с переводом, мы откатываем вызов.
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev Очистить одобрение заданного адреса
Вызывает откат, если `_owner` не является текущим владельцем.
"""
# Вызывает откат, если `_owner` не является текущим владельцем
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != empty(address):
# Сбросить одобрения
self.idToApprovals[_tokenId] = empty(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 != empty(address)
# Очистить одобрение. Вызывает откат, если `_from` не является текущим владельцем
self._clearApproval(_from, _tokenId)
# Удалить NFT. Вызывает откат, если `_tokenId` не является действительным NFT
self._removeTokenFrom(_from, _tokenId)
# Добавить NFT
self._addTokenTo(_to, _tokenId)
# Записать перевод в журнал
log IERC721.Transfer(sender=_from, receiver=_to, token_id=_tokenId)
Для генерации события в Vyper вы используете оператор log (подробнее см. здесь (открывается в новой вкладке)). Поскольку события принадлежат импортированному интерфейсу, мы ссылаемся на них как на IERC721.Transfer и передаем их поля по ключевому слову.
Функции перевода
### ФУНКЦИИ ПЕРЕВОДА ###
@external
@payable
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)
Эта функция позволяет вам выполнить перевод на произвольный адрес. Если адрес не является пользователем или контрактом, который знает, как переводить токены, любой переведенный вами токен застрянет на этом адресе и станет бесполезным.
Декоратор @payable находится здесь, потому что интерфейс IERC721 объявляет transferFrom, safeTransferFrom и approve как payable, поэтому контракт, реализующий интерфейс, должен соответствовать этим сигнатурам.
@external
@payable
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)"))`.
@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: bytes4 = extcall ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
Вызовите целевой контракт, чтобы узнать, может ли он получать токены ERC-721. Vyper 0.4 требует, чтобы вызовы других контрактов были помечены, поэтому вызову предшествует extcall.
# Вызывает откат, если место назначения перевода — контракт, который не реализует 'onERC721Received'
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)
Если местом назначения является контракт, но он не принимает токены ERC-721 (или решил не принимать этот конкретный перевод), выполните откат.
@external
@payable
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 != empty(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 IERC721.Approval(owner=owner, approved=_approved, token_id=_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 IERC721.ApprovalForAll(owner=msg.sender, operator=_operator, approved=_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, вы берете этот контракт (открывается в новой вкладке) и модифицируете его для реализации нужной вам бизнес-логики.
Заключение
Для повторения, вот некоторые из наиболее важных идей в этом контракте:
- Чтобы получать токены ERC-721 с помощью безопасного перевода, контракты должны реализовывать интерфейс
ERC721Receiver. - Даже если вы используете безопасный перевод, токены все равно могут застрять, если вы отправите их на адрес, приватный ключ которого неизвестен.
- Когда возникает проблема с операцией, хорошей идеей будет
revertвызов, а не просто возвращать значение ошибки. - Токены ERC-721 существуют, когда у них есть владелец.
- Существует три способа получить разрешение на перевод NFT. Вы можете быть владельцем, получить одобрение для определенного токена, или быть оператором для всех токенов владельца.
- Прошлые события видны только за пределами блокчейна. Код, работающий внутри блокчейна, не может их просматривать.
Теперь идите и реализуйте безопасные контракты на Vyper.
Смотрите здесь больше моих работ (открывается в новой вкладке).