Разбор контракта 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 (если есть) сбрасывается на none.
# @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 (если есть) сбрасывается на none.
# @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 интерфейса на bool о том, поддерживается ли он
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
Эти ключевые слова перед определением функции, которые начинаются со знака «собачка» (@), называются декораторами. Они
определяют обстоятельства, при которых может быть вызвана функция.
@viewуказывает, что эта функция является функцией просмотра (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
Эта строка утверждает (asserts) (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 Возвращает, может ли данный spender выполнить перевод данного ID токена
@param spender адрес 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
Существует три способа, с помощью которых адресу может быть разрешено перевести токен:
- Адрес является владельцем токена
- Адресу одобрено расходование этого токена
- Адрес является оператором для владельца токена
Приведенная выше функция может быть функцией просмотра (view), потому что она не изменяет состояние. Чтобы снизить эксплуатационные расходы, любая функция, которая может быть функцией просмотра, должна ею быть.
@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.