Pular para o conteúdo principal

Passo a passo do Contrato Vyper ERC-721

Vyper
erc-721
Python
Iniciante
Ori Pomerantz
1 de abril de 2021
21 minutos de leitura

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.vy

Comentá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 ERC721
2
3implements: ERC721

A 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: view

Para 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ão
2# criados (`from` == 0) e destruídos (`to` == 0). Exceção: durante a criação do contrato, qualquer
3# número de NFTs pode ser criado e atribuído sem emitir Transfer. No momento de qualquer
4# 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 tudo

Isso é 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ço
2# zero indica que não há endereço aprovado. Quando um evento Transfer emite, isso também
3# 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 tudo

Uma 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 gerenciar
2# 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 se
6# revogado).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
Exibir 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]
3
4# @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 token
2minter: address

Novos 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 suportado
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ID da interface ERC165 do ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ID da interface ERC165 do ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

O 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@external
2def __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] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender

Para 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@view
2@external

Essas 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.

  • @view especifica que esta função é uma visualização.
  • @external especifica 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 interface
4 """
5 return self.supportedInterfaces[_interfaceID]

Retorne o valor do HashMap self.supportedInterfaces, que é definido no construtor (__init__).

1### FUNÇÕES DE VISUALIZAÇÃO ###
2

Estas são as funções de visualização que disponibilizam informações sobre os tokens para usuários e outros contratos.

1@view
2@external
3def 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_ADDRESS
Exibir tudo

Esta 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]
2
3@view
4@external
5def 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álido
13 assert owner != ZERO_ADDRESS
14 return owner
Exibir tudo

Na 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@view
2@external
3def 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álido
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
Exibir tudo

Observe 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@view
2@external
3def 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 tudo

Esta 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.

1
2### FUNÇÕES AUXILIARES DE TRANSFERÊNCIA ###
3
4@view
5@internal

Esta 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 token
4 @param spender endereço do gastador para consultar
5 @param tokenId uint256 ID do token a ser transferido
6 @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 token
8 """
9 owner: address = self.idToOwner[_tokenId]
10 spenderIsOwner: bool = owner == _spender
11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
Exibir tudo

Há três maneiras pelas quais um endereço pode ter permissão para transferir um token:

  1. O endereço é o proprietário do token
  2. O endereço é aprovado para gastar esse token
  3. 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@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Adiciona um NFT a um determinado endereço
5 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ém
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Altera o proprietário
10 self.idToOwner[_tokenId] = _to
11 # Altera o rastreamento da contagem
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Remove um NFT de um determinado endereço
19 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 atual
22 assert self.idToOwner[_tokenId] == _from
23 # Altera o proprietário
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Altera o rastreamento da contagem
26 self.ownerToNFTokenCount[_from] -= 1
Exibir tudo

Quando há um problema com uma transferência, revertemos a chamada.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Limpa a aprovação de um determinado endereço
5 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 atual
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Redefinir aprovações
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
Exibir tudo

Altere 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@internal
2def _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ço
6 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 tudo

Temos 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 requisitos
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Lança uma exceção se `_to` for o endereço zero
4 assert _to != ZERO_ADDRESS
5 # Limpar aprovação. Lança uma exceção se `_from` não for o proprietário atual
6 self._clearApproval(_from, _tokenId)
7 # Remover NFT. Lança uma exceção se `_tokenId` não for um NFT válido
8 self._removeTokenFrom(_from, _tokenId)
9 # Adicionar NFT
10 self._addTokenTo(_to, _tokenId)
11 # Registrar a transferência
12 log Transfer(_from, _to, _tokenId)
Exibir tudo

Para 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

1
2### FUNÇÕES DE TRANSFERÊNCIA ###
3
4@external
5def 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 o
8 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ário
13 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 tudo

Esta 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@external
2def 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 o
11 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 se
16 o valor de retorno não for `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 NOTA: bytes4 é representado por bytes32 com preenchimento
18 @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 tudo

Nã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 contrato

Primeiro, 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@external
2def 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álido
13 assert owner != ZERO_ADDRESS
14 # Lança uma exceção se `_approved` for o proprietário atual
15 assert _approved != owner
Exibir tudo

Por convenção, se você não quiser ter um aprovador, você nomeia o endereço zero, não a si mesmo.

1 # Verificar requisitos
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 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ção
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Habilita ou desabilita a aprovação para um terceiro ("operador") gerenciar todos os
10 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.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
Exibir tudo

Cunhar 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 ###
2
3@external
4def 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 tokens
3 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 minter
11 assert msg.sender == self.minter
Exibir tudo

Apenas 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 zero
2 assert _to != ZERO_ADDRESS
3 # Adicionar NFT. Lança uma exceção se `_tokenId` for de propriedade de alguém
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

Por convenção, a cunhagem de novos tokens conta como uma transferência do endereço zero.

1
2@external
3def 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ço
7 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 requisitos
12 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álido
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
Exibir tudo

Qualquer 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 reverter a 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

Este tutorial foi útil?