Passo a passo do Contrato Vyper ERC-721
Introdução
O padrão ERC-721 é usado para manter a propriedade de Tokens Não Fungíveis (NFT). Os tokens ERC-20 se comportam como uma commodity, porque não há diferença entre tokens individuais. Em contraste com isso, os tokens ERC-721 são projetados para ativos que são semelhantes, mas não idênticos, como diferentes desenhos de gatos (opens in a new tab) ou títulos para diferentes peças de imóveis.
Neste artigo, analisaremos o contrato ERC-721 de Ryuya Nakamura (opens in a new tab). Este contrato é escrito em Vyper (opens in a new tab), uma linguagem de contrato semelhante ao Python, projetada para tornar mais difícil escrever código inseguro do que em Solidity.
O Contrato
1# @dev Implementação do padrão de token não fungível ERC-721.2# @author Ryuya Nakamura (@nrryuya)3# Modificado de: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vyComentários em Vyper, como em Python, começam com um hash (#) e continuam até o final da linha. Comentários que incluem
@<palavra-chave> são usados pelo NatSpec (opens in a new tab) para produzir documentação legível
por humanos.
1from vyper.interfaces import ERC72123implements: ERC721A interface ERC-721 é integrada à linguagem Vyper. Você pode ver a definição do código aqui (opens in a new tab). A definição da interface é escrita em Python, em vez de Vyper, porque as interfaces são usadas não apenas dentro da blockchain, mas também ao enviar uma transação para a blockchain a partir de um cliente externo, que pode ser escrito em Python.
A primeira linha importa a interface, e a segunda especifica que estamos implementando-a aqui.
A interface ERC721Receiver
1# Interface para o contrato chamado por safeTransferFrom()2interface ERC721Receiver:3 def onERC721Received(O ERC-721 suporta dois tipos de transferência:
transferFrom, que permite ao remetente especificar qualquer endereço de destino e coloca a responsabilidade da transferência no remetente. Isso significa que você pode transferir para um endereço inválido, caso em que o NFT é perdido para sempre.safeTransferFrom, que verifica se o endereço de destino é um contrato. Se for, o contrato ERC-721 pergunta ao contrato receptor se ele quer receber o NFT.
Para responder às solicitações de safeTransferFrom, um contrato de recebimento precisa implementar o ERC721Receiver.
1 _operator: address,2 _from: address,O endereço _from é o proprietário atual do token. O endereço _operator é aquele que
solicitou a transferência (esses dois podem não ser os mesmos, por causa das permissões).
1 _tokenId: uint256,Os IDs de token ERC-721 são de 256 bits. Normalmente, eles são criados por hashing de uma descrição do que o token representa.
1 _data: Bytes[1024]A solicitação pode ter até 1024 bytes de dados do usuário.
1 ) -> bytes32: viewPara evitar casos em que um contrato aceite acidentalmente uma transferência, o valor de retorno não é um booleano, mas 256 bits com um valor específico.
Essa função é uma view, o que significa que pode ler o estado da blockchain, mas não modificá-lo.
Eventos
Eventos (opens in a new tab) são emitidos para informar usuários e servidores fora da blockchain sobre os eventos. Observe que o conteúdo dos eventos não está disponível para contratos na blockchain.
1# @dev Emite quando a propriedade de qualquer NFT muda por qualquer mecanismo. Este evento é emitido quando os NFTs são2# criados (`from` == 0) e destruídos (`to` == 0). Exceção: durante a criação do contrato, qualquer3# número de NFTs pode ser criado e atribuído sem emitir Transfer. No momento de qualquer4# transferência, o endereço aprovado para esse NFT (se houver) é redefinido para nenhum.5# @param _from Remetente do NFT (se o endereço for o endereço zero, isso indica a criação do token).6# @param _to Receptor do NFT (se o endereço for o endereço zero, indica a destruição do token).7# @param _tokenId O NFT que foi transferido.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Exibir tudoIsso é semelhante ao evento Transfer do ERC-20, exceto que relatamos um tokenId em vez de um valor.
Ninguém é dono do endereço zero, então, por convenção, nós o usamos para relatar a criação e a destruição de tokens.
1# @dev Isto emite quando o endereço aprovado para um NFT é alterado ou reafirmado. O endereço2# zero indica que não há endereço aprovado. Quando um evento Transfer emite, isso também3# indica que o endereço aprovado para esse NFT (se houver) é redefinido para nenhum.4# @param _owner Proprietário do NFT.5# @param _approved Endereço que estamos aprovando.6# @param _tokenId NFT que estamos aprovando.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Exibir tudoUma aprovação ERC-721 é semelhante a uma permissão ERC-20. Um endereço específico tem permissão para transferir um token específico. Isso dá um mecanismo para os contratos responderem quando aceitam um token. Contratos não podem escutar eventos, então se você apenas transferir o token para eles, eles não "sabem" sobre isso. Dessa forma, o proprietário primeiro envia uma aprovação e, em seguida, envia uma solicitação ao contrato: "Eu aprovei que você transfira o token X, por favor, faça ...".
Esta é uma escolha de design para tornar o padrão ERC-721 semelhante ao padrão ERC-20. Como os tokens ERC-721 não são fungíveis, um contrato também pode identificar que obteve um token específico observando a propriedade do token.
1# @dev Isto emite quando um operador é habilitado ou desabilitado para um proprietário. O operador pode gerenciar2# todos os NFTs do proprietário.3# @param _owner Proprietário do NFT.4# @param _operator Endereço para o qual estamos definindo os direitos do operador.5# @param _approved Status dos direitos do operador (verdadeiro se os direitos do operador forem concedidos e falso se6# revogado).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolExibir tudoÀs vezes é útil ter um operador que pode gerenciar todos os tokens de um tipo específico de uma conta (aqueles que são gerenciados por um contrato específico), semelhante a uma procuração. Por exemplo, eu talvez queira dar esse poder a um contrato que verifica se eu não o contatei por seis meses e, em caso afirmativo, distribui meus ativos para meus herdeiros (se um deles pedir, os contratos não podem fazer nada sem serem chamados por uma transação). No ERC-20, podemos simplesmente dar uma alta permissão a um contrato de herança, mas isso não funciona para o ERC-721 porque os tokens não são fungíveis. Este é o equivalente.
O valor aprovado nos diz se o evento é para uma aprovação ou a retirada de uma aprovação.
Variáveis de Estado
Essas variáveis contêm o estado atual dos tokens: quais estão disponíveis e quem os possui. A maioria deles
são objetos HashMap, mapeamentos unidirecionais que existem entre dois tipos (opens in a new tab).
1# @dev Mapeamento do ID do NFT para o endereço que o possui.2idToOwner: HashMap[uint256, address]34# @dev Mapeamento do ID do NFT para o endereço aprovado.5idToApprovals: HashMap[uint256, address]As identidades de usuários e contratos no Ethereum são representadas por endereços de 160 bits. Essas duas variáveis mapeiam de IDs de token para seus proprietários e aqueles aprovados para transferi-los (no máximo um para cada). No Ethereum, os dados não inicializados são sempre zero, então se não houver proprietário ou transferidor aprovado, o valor para aquele token será zero.
1# @dev Mapeamento do endereço do proprietário para a contagem de seus tokens.2ownerToNFTokenCount: HashMap[address, uint256]Esta variável contém a contagem de tokens para cada proprietário. Não há mapeamento de proprietários para tokens, então
a única maneira de identificar os tokens que um proprietário específico possui é olhar para trás no histórico de eventos da blockchain
e ver os eventos Transfer apropriados. Podemos usar essa variável para saber quando temos todos os NFTs e não
precisamos procurar ainda mais no tempo.
Note que este algoritmo funciona apenas para interfaces de usuário e servidores externos. Código rodando na blockchain em si não pode ler eventos passados.
1# @dev Mapeamento de endereço de proprietário para mapeamento de endereços de operador.2ownerToOperators: HashMap[address, HashMap[address, bool]]Uma conta pode ter mais de um único operador. Um simples HashMap é insuficiente para
rastreá-los, porque cada chave leva a um único valor. Em vez disso, você pode usar
HashMap[address, bool] como o valor. Por padrão, o valor para cada endereço é False, o que significa que ele
não é um operador. Você pode definir os valores como True conforme necessário.
1# @dev Endereço do minter, que pode cunhar um token2minter: addressNovos tokens precisam ser criados de alguma forma. Neste contrato, há uma única entidade que tem permissão para fazer isso, o
minter. Isso provavelmente é suficiente para um jogo, por exemplo. Para outros fins, pode ser necessário
criar uma lógica de negócios mais complicada.
1# @dev Mapeamento do ID da interface para bool sobre se é ou não suportado2supportedInterfaces: HashMap[bytes32, bool]34# @dev ID da interface ERC165 do ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ID da interface ERC165 do ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdO ERC-165 (opens in a new tab) especifica um mecanismo para um contrato divulgar como os aplicativos podem se comunicar com ele, e a quais ERCs ele se conforma. Nesse caso, o contrato está em conformidade com o ERC-165 e o ERC-721.
Funções
Estas são as funções que realmente implementam o ERC-721.
Construtor
1@external2def __init__():No Vyper, assim como no Python, a função construtora é chamada __init__.
1 """2 @dev Construtor do contrato.3 """Em Python, e em Vyper, você também pode criar um comentário especificando uma string de várias linhas (que começa e termina
com """), e não usá-la de forma alguma. Esses comentários também podem incluir
NatSpec (opens in a new tab).
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderPara acessar as variáveis de estado, você usa self.<variable name> (novamente, como em Python).
Funções de Visualização
Estas são funções que não modificam o estado da blockchain e, portanto, podem ser executadas gratuitamente se forem chamadas externamente. Se as funções de visualização forem chamadas por um contrato, elas ainda precisarão ser executadas em cada nó e, portanto, custarão gás.
1@view2@externalEssas palavras-chave antes de uma definição de função que começam com um sinal de arroba (@) são chamadas de decorações. Elas
especificam as circunstâncias em que uma função pode ser chamada.
@viewespecifica que esta função é uma visualização.@externalespecifica que esta função específica pode ser chamada por transações e por outros contratos.
1def supportsInterface(_interfaceID: bytes32) -> bool:Ao contrário do Python, o Vyper é uma linguagem de tipagem estática (opens in a new tab).
Você não pode declarar uma variável, ou um parâmetro de função, sem identificar o tipo de dados (opens in a new tab). Nesse caso, o parâmetro de entrada é bytes32, um valor de 256 bits
(256 bits é o tamanho de palavra nativo da Máquina Virtual Ethereum). A saída é um valor
booleano. Por convenção, os nomes dos parâmetros da função começam com um sublinhado (_).
1 """2 @dev A identificação da interface é especificada no ERC-165.3 @param _interfaceID Id da interface4 """5 return self.supportedInterfaces[_interfaceID]Retorne o valor do HashMap self.supportedInterfaces, que é definido no construtor (__init__).
1### FUNÇÕES DE VISUALIZAÇÃO ###2Estas são as funções de visualização que disponibilizam informações sobre os tokens para usuários e outros contratos.
1@view2@external3def balanceOf(_owner: address) -> uint256:4 """5 @dev Retorna o número de NFTs de propriedade de `_owner`.6 Lança uma exceção se `_owner` for o endereço zero. NFTs atribuídos ao endereço zero são considerados inválidos.7 @param _owner Endereço para o qual consultar o saldo.8 """9 assert _owner != ZERO_ADDRESSExibir tudoEsta linha afirma (opens in a new tab) que _owner não é
zero. Se for, há um erro e a operação é revertida.
1 return self.ownerToNFTokenCount[_owner]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Retorna o endereço do proprietário do NFT.8 Lança uma exceção se `_tokenId` não for um NFT válido.9 @param _tokenId O identificador para um NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Lança uma exceção se `_tokenId` não for um NFT válido13 assert owner != ZERO_ADDRESS14 return ownerExibir tudoNa Máquina Virtual Ethereum (EVM), qualquer armazenamento que não tenha um valor armazenado nele é zero.
Se não houver token em _tokenId, o valor de self.idToOwner[_tokenId] será zero. Nesse
caso, a função é revertida.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Obtenha o endereço aprovado para um único NFT.6 Lança uma exceção se `_tokenId` não for um NFT válido.7 @param _tokenId ID do NFT para consultar a aprovação.8 """9 # Lança uma exceção se `_tokenId` não for um NFT válido10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Exibir tudoObserve que getApproved pode retornar zero. Se o token for válido, ele retorna self.idToApprovals[_tokenId].
Se não houver aprovador, esse valor é zero.
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev Verifica se `_operator` é um operador aprovado para `_owner`.6 @param _owner O endereço que possui os NFTs.7 @param _operator O endereço que atua em nome do proprietário.8 """9 return (self.ownerToOperators[_owner])[_operator]Exibir tudoEsta função verifica se o _operator tem permissão para gerenciar todos os tokens do _owner neste contrato.
Como pode haver vários operadores, este é um HashMap de dois níveis.
Funções Auxiliares de Transferência
Essas funções implementam operações que fazem parte da transferência ou gerenciamento de tokens.
12### FUNÇÕES AUXILIARES DE TRANSFERÊNCIA ###34@view5@internalEsta decoração, @internal, significa que a função só é acessível a partir de outras funções dentro do
mesmo contrato. Por convenção, esses nomes de função também começam com um sublinhado (_).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Retorna se o gastador informado pode transferir um determinado ID de token4 @param spender endereço do gastador para consultar5 @param tokenId uint256 ID do token a ser transferido6 @return bool se o msg.sender está aprovado para o ID de token informado,7 é um operador do proprietário ou é o proprietário do token8 """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 spenderIsApprovedForAllExibir tudoHá três maneiras pelas quais um endereço pode ter permissão para transferir um token:
- O endereço é o proprietário do token
- O endereço é aprovado para gastar esse token
- O endereço é um operador para o proprietário do token
A função acima pode ser uma visualização porque não altera o estado. Para reduzir os custos operacionais, qualquer função que possa ser uma visualização deve ser uma visualização.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Adiciona um NFT a um determinado endereço5 Lança uma exceção se `_tokenId` for de propriedade de alguém.6 """7 # Lança uma exceção se `_tokenId` for de propriedade de alguém8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Altera o proprietário10 self.idToOwner[_tokenId] = _to11 # Altera o rastreamento da contagem12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Remove um NFT de um determinado endereço19 Lança uma exceção se `_from` não for o proprietário atual.20 """21 # Lança uma exceção se `_from` não for o proprietário atual22 assert self.idToOwner[_tokenId] == _from23 # Altera o proprietário24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Altera o rastreamento da contagem26 self.ownerToNFTokenCount[_from] -= 1Exibir tudoQuando há um problema com uma transferência, revertemos a chamada.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Limpa a aprovação de um determinado endereço5 Lança uma exceção se `_owner` não for o proprietário atual.6 """7 # Lança uma exceção se `_owner` não for o proprietário atual8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Redefinir aprovações11 self.idToApprovals[_tokenId] = ZERO_ADDRESSExibir tudoAltere o valor apenas se necessário. As variáveis de estado ficam no armazenamento. Gravar no armazenamento é uma das operações mais caras que a EVM (Máquina Virtual Ethereum) faz (em termos de gás). Portanto, é uma boa ideia minimizá-la, mesmo que a escrita do valor existente tenha um custo alto.
1@internal2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):3 """4 @dev Executa a transferência de um NFT.5 Lança uma exceção, a menos que `msg.sender` seja o proprietário atual, um operador autorizado ou o endereço6 aprovado para este NFT. (NOTA: `msg.sender` não é permitido em função privada, então passe `_sender`.)7 Lança uma exceção se `_to` for o endereço zero.8 Lança uma exceção se `_from` não for o proprietário atual.9 Lança uma exceção se `_tokenId` não for um NFT válido.10 """Exibir tudoTemos essa função interna porque há duas maneiras de transferir tokens (regular e segura), mas queremos apenas um único local no código onde fazemos isso para facilitar a auditoria.
1 # Verificar requisitos2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Lança uma exceção se `_to` for o endereço zero4 assert _to != ZERO_ADDRESS5 # Limpar aprovação. Lança uma exceção se `_from` não for o proprietário atual6 self._clearApproval(_from, _tokenId)7 # Remover NFT. Lança uma exceção se `_tokenId` não for um NFT válido8 self._removeTokenFrom(_from, _tokenId)9 # Adicionar NFT10 self._addTokenTo(_to, _tokenId)11 # Registrar a transferência12 log Transfer(_from, _to, _tokenId)Exibir tudoPara emitir um evento em Vyper, você usa uma instrução log (veja aqui para mais detalhes (opens in a new tab)).
Funções de Transferência
12### FUNÇÕES DE TRANSFERÊNCIA ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Lança uma exceção, a menos que `msg.sender` seja o proprietário atual, um operador autorizado ou o8 endereço aprovado para este NFT.9 Lança uma exceção se `_from` não for o proprietário atual.10 Lança uma exceção se `_to` for o endereço zero.11 Lança uma exceção se `_tokenId` não for um NFT válido.12 @notice O chamador é responsável por confirmar que `_to` é capaz de receber NFTs, caso contrário13 eles podem ser permanentemente perdidos.14 @param _from O proprietário atual do NFT.15 @param _to O novo proprietário.16 @param _tokenId O NFT a ser transferido.17 """18 self._transferFrom(_from, _to, _tokenId, msg.sender)Exibir tudoEsta função permite que você transfira para um endereço arbitrário. A menos que o endereço seja de um usuário ou de um contrato que saiba como transferir tokens, qualquer token que você transferir ficará preso nesse endereço e será inútil.
1@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 """9 @dev Transfere a propriedade de um NFT de um endereço para outro.10 Lança uma exceção, a menos que `msg.sender` seja o proprietário atual, um operador autorizado ou o11 endereço aprovado para este NFT.12 Lança uma exceção se `_from` não for o proprietário atual.13 Lança uma exceção se `_to` for o endereço zero.14 Lança uma exceção se `_tokenId` não for um NFT válido.15 Se `_to` for um contrato inteligente, ele chama `onERC721Received` em `_to` e lança uma exceção se16 o valor de retorno não for `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 NOTA: bytes4 é representado por bytes32 com preenchimento18 @param _from O proprietário atual do NFT.19 @param _to O novo proprietário.20 @param _tokenId O NFT a ser transferido.21 @param _data Dados adicionais sem formato especificado, enviados na chamada para `_to`.22 """23 self._transferFrom(_from, _to, _tokenId, msg.sender)Exibir tudoNão há problema em fazer a transferência primeiro porque, se houver um problema, vamos reverter de qualquer maneira, então tudo o que for feito na chamada será cancelado.
1 if _to.is_contract: # verifica se `_to` é um endereço de contratoPrimeiro, verifique se o endereço é um contrato (se ele tem código). Caso contrário, presuma que é um endereço de
usuário e que o usuário poderá usar o token ou transferi-lo. Mas não deixe que isso o iluda
com uma falsa sensação de segurança. Você pode perder tokens, mesmo com safeTransferFrom, se os transferir
para um endereço cuja chave privada ninguém conhece.
1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)Chame o contrato de destino para ver se ele pode receber tokens ERC-721.
1 # Lança uma exceção se o destino da transferência for um contrato que não implementa 'onERC721Received'2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)Se o destino for um contrato, mas um que não aceita tokens ERC-721 (ou que decidiu não aceitar esta transferência em particular), reverta.
1@external2def approve(_approved: address, _tokenId: uint256):3 """4 @dev Define ou reafirma o endereço aprovado para um NFT. O endereço zero indica que não há endereço aprovado.5 Lança uma exceção, a menos que `msg.sender` seja o proprietário atual do NFT ou um operador autorizado do proprietário atual.6 Lança uma exceção se `_tokenId` não for um NFT válido. (NOTA: Isso não está escrito no EIP)7 Lança uma exceção se `_approved` for o proprietário atual. (NOTA: Isso não está escrito no EIP)8 @param _approved Endereço a ser aprovado para o ID de NFT fornecido.9 @param _tokenId ID do token a ser aprovado.10 """11 owner: address = self.idToOwner[_tokenId]12 # Lança uma exceção se `_tokenId` não for um NFT válido13 assert owner != ZERO_ADDRESS14 # Lança uma exceção se `_approved` for o proprietário atual15 assert _approved != ownerExibir tudoPor convenção, se você não quiser ter um aprovador, você nomeia o endereço zero, não a si mesmo.
1 # Verificar requisitos2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Para definir uma aprovação, você pode ser o proprietário ou um operador autorizado pelo proprietário.
1 # Definir a aprovação2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Habilita ou desabilita a aprovação para um terceiro ("operador") gerenciar todos os10 ativos de `msg.sender`. Também emite o evento ApprovalForAll.11 Lança uma exceção se `_operator` for o `msg.sender`. (NOTA: Isso não está escrito no EIP)12 @notice Isso funciona mesmo que o remetente não possua nenhum token no momento.13 @param _operator Endereço a ser adicionado ao conjunto de operadores autorizados.14 @param _approved Verdadeiro se os operadores forem aprovados, falso para revogar a aprovação.15 """16 # Lança uma exceção se `_operator` for o `msg.sender`17 assert _operator != msg.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Exibir tudoCunhar Novos Tokens e Destruir os Existentes
A conta que criou o contrato é o minter, o superusuário que está autorizado a cunhar
novos NFTs. No entanto, mesmo ele não tem permissão para queimar tokens existentes. Apenas o proprietário, ou uma entidade
autorizada pelo proprietário, pode fazer isso.
1### FUNÇÕES DE CUNHAGEM E QUEIMA ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:Esta função sempre retorna True, porque se a operação falhar, ela é revertida.
1 """2 @dev Função para cunhar tokens3 Lança uma exceção se `msg.sender` não for o minter.4 Lança uma exceção se `_to` for o endereço zero.5 Lança uma exceção se `_tokenId` for de propriedade de alguém.6 @param _to O endereço que receberá os tokens cunhados.7 @param _tokenId O id do token a ser cunhado.8 @return Um booleano que indica se a operação foi bem-sucedida.9 """10 # Lança uma exceção se `msg.sender` não for o minter11 assert msg.sender == self.minterExibir tudoApenas o minter (a conta que criou o contrato ERC-721) pode cunhar novos tokens. Isso pode ser um problema no futuro se quisermos mudar a identidade do minter. Em um contrato de produção, você provavelmente desejaria uma função que permitisse ao minter transferir os privilégios de minter para outra pessoa.
1 # Lança uma exceção se `_to` for o endereço zero2 assert _to != ZERO_ADDRESS3 # Adicionar NFT. Lança uma exceção se `_tokenId` for de propriedade de alguém4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TruePor convenção, a cunhagem de novos tokens conta como uma transferência do endereço zero.
12@external3def burn(_tokenId: uint256):4 """5 @dev Queima um token ERC721 específico.6 Lança uma exceção, a menos que `msg.sender` seja o proprietário atual, um operador autorizado ou o endereço7 aprovado para este NFT.8 Lança uma exceção se `_tokenId` não for um NFT válido.9 @param _tokenId id uint256 do token ERC721 a ser queimado.10 """11 # Verificar requisitos12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Lança uma exceção se `_tokenId` não for um NFT válido15 assert owner != ZERO_ADDRESS16 self._clearApproval(owner, _tokenId)17 self._removeTokenFrom(owner, _tokenId)18 log Transfer(owner, ZERO_ADDRESS, _tokenId)Exibir tudoQualquer pessoa que tenha permissão para transferir um token tem permissão para queimá-lo. Embora uma queima pareça equivalente a uma transferência para o endereço zero, o endereço zero na verdade não recebe o token. Isso nos permite liberar todo o armazenamento que foi usado para o token, o que pode reduzir o custo de gás da transação.
Usando este Contrato
Em contraste com o Solidity, o Vyper não tem herança. Essa é uma escolha de design deliberada para tornar o código mais claro e, portanto, mais fácil de proteger. Então, para criar seu próprio contrato Vyper ERC-721, você pega este contrato e o modifica para implementar a lógica de negócios que você deseja.
Conclusão
Para revisão, aqui estão algumas das ideias mais importantes deste contrato:
- Para receber tokens ERC-721 com uma transferência segura, os contratos precisam implementar a interface
ERC721Receiver. - Mesmo se você usar a transferência segura, os tokens ainda podem ficar presos se você os enviar para um endereço cuja chave privada é desconhecida.
- Quando há um problema com uma operação, é uma boa ideia
revertera chamada, em vez de apenas retornar um valor de falha. - Os tokens ERC-721 existem quando têm um proprietário.
- Existem três maneiras de ser autorizado a transferir um NFT. Você pode ser o proprietário, ser aprovado para um token específico ou ser um operador para todos os tokens do proprietário.
- Eventos passados são visíveis apenas fora da blockchain. O código em execução dentro da blockchain não pode visualizá-los.
Agora, vá e implemente contratos Vyper seguros.
Veja aqui mais do meu trabalho (opens in a new tab).
Última atualização da página: 22 de agosto de 2025