Пошаговый разбор контракта Vyper ERC-721
Введение
Стандарт ERC-721 используется для владения невзаимозаменяемыми токенами (NFT). Токены ERC-20 ведут себя как товар, так как между отдельными токенами нет разницы. В отличие от них, токены ERC-721 предназначены для активов, которые похожи, но не идентичны, например, различные мультяшные коты или права собственности на различные объекты недвижимости.
В этой статье мы проанализируем контракт ERC-721 от Ryuya Nakamura (opens in a new tab). Этот контракт написан на Vyper (opens in a new tab), языке контрактов, похожем на Python, который разработан таким образом, чтобы затруднить написание небезопасного кода по сравнению с Solidity.
Контракт
1# @dev Реализация стандарта невзаимозаменяемых токенов ERC-721.2# @author Ryuya Nakamura (@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 ERC72123implements: ERC721Интерфейс ERC-721 встроен в язык Vyper. Определение кода можно посмотреть здесь (opens in a new tab). Определение интерфейса написано на Python, а не на Vyper, потому что интерфейсы используются не только в блокчейне, но и при отправке транзакции в блокчейн с внешнего клиента, который может быть написан на Python.
Первая строка импортирует интерфейс, а вторая указывает, что мы реализуем его здесь.
Интерфейс ERC721Receiver
1# Интерфейс для контракта, вызываемого safeTransferFrom()2interface ERC721Receiver:3 def onERC721Received(ERC-721 поддерживает два типа перевода:
transferFrom, который позволяет отправителю указать любой адрес назначения и возлагает ответственность за перевод на отправителя. Это означает, что вы можете перевести средства на недействительный адрес, и в этом случае NFT будет безвозвратно утерян.safeTransferFrom, который проверяет, является ли адрес назначения контрактом. Если это так, контракт ERC-721 спрашивает у принимающего контракта, хочет ли он получить NFT.
Чтобы отвечать на запросы safeTransferFrom, принимающий контракт должен реализовывать ERC721Receiver.
1 _operator: address,2 _from: address,Адрес _from — это текущий владелец токена. Адрес _operator — это адрес, который
запросил перевод (эти два адреса могут не совпадать из-за разрешений).
1 _tokenId: uint256,Идентификаторы токенов ERC-721 имеют размер 256 бит. Обычно они создаются путем хэширования описания того, что представляет собой токен.
1 _data: Bytes[1024]Запрос может содержать до 1024 байт пользовательских данных.
1 ) -> bytes32: viewЧтобы предотвратить случаи, когда контракт случайно принимает перевод, возвращаемое значение является не логическим, а 256-битным значением с определенным содержанием.
Эта функция является view, что означает, что она может читать состояние блокчейна, но не изменять его.
События
События (opens in a new tab) создаются для информирования пользователей и серверов за пределами блокчейна о событиях. Обратите внимание, что содержимое событий недоступно контрактам в блокчейне.
1# @dev Создается, когда право собственности на любой NFT изменяется любым механизмом. Это событие создается при создании NFT (`from` == 0) и их уничтожении (`to` == 0). Исключение: во время создания контракта любое2# количество NFT может быть создано и назначено без создания события Transfer. Во время любого3# перевода одобренный адрес для этого NFT (если он есть) сбрасывается.4# @param _from Отправитель NFT (если адрес нулевой, это указывает на создание токена).5# @param _to Получатель NFT (если адрес нулевой, это указывает на уничтожение токена).6# @param _tokenId Переданный NFT.7event Transfer:8 sender: indexed(address)9 receiver: indexed(address)10 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 Сопоставление идентификатора NFT с адресом его владельца.2idToOwner: HashMap[uint256, address]34# @dev Сопоставление идентификатора NFT с одобренным адресом.5idToApprovals: HashMap[uint256, address]Идентификаторы пользователей и контрактов в Ethereum представлены 160-битными адресами. Эти две переменные сопоставляют идентификаторы токенов с их владельцами и теми, кто одобрен для их перевода (максимум по одному для каждого). В 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Новые токены должны как-то создаваться. В этом контракте есть единственная сущность, которой разрешено это делать, —
минтер. Этого, например, скорее всего, будет достаточно для игры. Для других целей может потребоваться
создать более сложную бизнес-логику.
1# @dev Сопоставление идентификатора интерфейса с логическим значением о том, поддерживается он или нет2supportedInterfaces: HashMap[bytes32, bool]34# @dev Идентификатор интерфейса ERC165 для ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev Идентификатор интерфейса 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).
Нельзя объявить переменную или параметр функции, не указав тип данных (opens in a new tab). В данном случае входной параметр — bytes32, 256-битное значение
(256 бит — это нативный размер слова виртуальной машины Ethereum). Выходные данные — это логическое
значение. По соглашению имена параметров функции начинаются с символа подчеркивания (_).
1 """2 @dev Идентификация интерфейса указана в ERC-165.3 @param _interfaceID Идентификатор интерфейса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]23@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 Идентификатор 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.
Вспомогательные функции перевода
Эти функции реализуют операции, которые являются частью перевода или управления токенами.
12### ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ПЕРЕВОДА ###34@view5@internalЭтот декоратор @internal означает, что функция доступна только из других функций в том
же контракте. По соглашению, имена этих функций также начинаются с символа подчеркивания (_).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Возвращает, может ли данный тратящий перевести данный идентификатор токена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] += 1131415@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)).
Функции перевода
12### ФУНКЦИИ ПЕРЕВОДА ###34@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 Адрес, который должен быть одобрен для данного идентификатора NFT.9 @param _tokenId Идентификатор токена, который должен быть одобрен.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)456@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)Показать всеСоздание новых токенов и уничтожение существующих
Аккаунт, создавший контракт, является минтером — суперпользователем, уполномоченным создавать
новые NFT. Однако даже ему не разрешается сжигать существующие токены. Это может сделать только владелец или уполномоченное им лицо.
1### ФУНКЦИИ СОЗДАНИЯ И СЖИГАНИЯ ###23@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По соглашению, создание новых токенов считается переводом с нулевого адреса.
12@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).
Последнее обновление страницы: 22 августа 2025 г.