メインコンテンツへスキップ

Vyperで作成したERC-721コントラクトの紹介

VyperERC-721Python
初級
Ori Pomerantz
2021年4月1日
31 分の読書 minute read

はじめに

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)で作成されました。Vyperは、Pythonに似たコントラクト開発言語であり、Solidityと比較するとセキュリティが低いコードを作成しにくくなっています。

コントラクトの内容

1# @dev Implementation of ERC-721 non-fungible token standard.
2# @author Ryuya Nakamura (@nrryuya)
3# Modified from: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
コピー

Pythonの場合と同様に、Vyperのコメントはハッシュ(@/code>)で開始し、行末まで続けます。 <code>@<keyword>を含むコメントは、 NatSpec(opens in a new tab) を通じて人間が読みやすいドキュメントに変換されます。

1from vyper.interfaces import ERC721
2
3implements: ERC721
コピー

ERC-721インターフェイスは、Vyper言語に組み込まれています。 コードの定義はこちら(opens in a new tab)をご覧ください。 インターフェイスの定義は、VyperではなくPythonで書かれています。これは、インターフェイスがブロックチェーン内だけでなく、Pythonで書かれた外部クライアントからブロックチェーンにトランザクションを送る際にも使われるからです。

第1行はインターフェイスをインポートし、第2行はここで実装することを指示します。

ERC-721のReceiverインターフェイス

1# Interface for the contract called by safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(
コピー

ERC-721は、2つの送信方法をサポートしています。

  • transferFromは、送信者が任意の送信先アドレスを指定するもので、送信の責任は送信者が担います。 このため無効なアドレスにも送信できますが、その場合はNFTは永遠に失われます。
  • safeTransferFromは、送信先アドレスがコントラクトかどうかを確認します。 送信先アドレスがコントラクトであることを確認すると、ERC-721コントラクトが受信側のコントラクトにNFTを受け取りたいかどうかを尋ねます。

safeTransferFromリクエストに応答するには、受信側コントラクトがERC721Receiverを実装していなければなりません。

1 _operator: address,
2 _from: address,
コピー

_fromのアドレスは、トークンの現在の所有者のアドレスです。 _operatorのアドレスは、 送信を要求したアドレスです(アローワンスにより、これらの 2 つが同一でない場合があります)。

1 _tokenId: uint256,
コピー

ERC-721トークンのIDは、256ビットです。 通常トークンIDは、トークンの内容をハッシュ化して生成されます。

1 _data: Bytes[1024]
コピー

このリクエストには、最大1024バイトのユーザーデータを含めることができます。

1 ) -> bytes32: view
コピー

コントラクトが誤送信を受け入れるケースを防止するため、戻り値はブール値ではなく、256ビットの特定の値になっています。

この関数はview関数であるため、ブロックチェーンの状態を読み取るだけで、変更はできません。

イベント

イベント(opens in a new tab)は、ブロックチェーン外のユーザーおよびサーバーにイベントを通知するために発行されます。 イベントの内容は、ブロックチェーン上のコントラクトでは利用できないことに注意してください。

1# @dev Emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are
2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any
3# number of NFTs may be created and assigned without emitting Transfer. At the time of any
4# transfer, the approved address for that NFT (if any) is reset to none.
5# @param _from Sender of NFT (if address is zero address it indicates token creation).
6# @param _to Receiver of NFT (if address is zero address it indicates token destruction).
7# @param _tokenId The NFT that got transferred.
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
すべて表示
コピー

これは、金額の代わりにtokenIdを伝える点を除くとERC-20のTransferイベントと類似しています。 アドレスを所有しないユーザーはいないので、慣習的に、このイベントを使ってトークンの発行と破壊を報告しています。

1# @dev This emits when the approved address for an NFT is changed or reaffirmed. The zero
2# address indicates there is no approved address. When a Transfer event emits, this also
3# indicates that the approved address for that NFT (if any) is reset to none.
4# @param _owner Owner of NFT.
5# @param _approved Address that we are approving.
6# @param _tokenId NFT which we are approving.
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 This emits when an operator is enabled or disabled for an owner. The operator can manage
2# all NFTs of the owner.
3# @param _owner Owner of NFT.
4# @param _operator Address to which we are setting operator rights.
5# @param _approved Status of operator rights(true if operator rights are given and false if
6# revoked).
7event ApprovalForAll:
8 owner: indexed(address)
9 operator: indexed(address)
10 approved: bool
すべて表示
コピー

場合によっては、委任状の機能のように、アカウントが所有する特定タイプのトークン(特定のコントラクトが管理するトークン)をすべて管理できるオペレーター を持つことが便利な場合もあります。 例えば、私がコントラクトに対して、6カ月にわたりそのコントラクトとのやりとりを行っていないかどうかを確認する権限を与え、もしやりとりがない場合には、私のアセットを相続人に分配するように設定したいとします(相続人の一人が遺産分配を求めても、トランザクションによって呼び出されない限りコントラクトは何も実行できません)。 ERC-20では、継承コントラクトに対して大きなアローワンスを設定できますが、ERC-721の場合、非代替性を持つために不可能です。 そこで、オペレーターが同等の機能を提供します。

approvedの値を通じて、当該イベントが承認を求めるものか、承認を取り消したものかを確認することができます。

状態変数

状態変数には、トークンの現在の状態、つまりどのトークンが存在し、誰が所有するかの情報が含まれます。 大部分の状態変数はHashMapオブジェクトであり、2つのタイプ間に存在する一方向のマッピング(opens in a new tab)です。

1# @dev Mapping from NFT ID to the address that owns it.
2idToOwner: HashMap[uint256, address]
3
4# @dev Mapping from NFT ID to approved address.
5idToApprovals: HashMap[uint256, address]
コピー

イーサリアムにおいて、ユーザーおよびコントラクトのIDは160ビットのアドレスで表示されます。 これら2つの変数は、トークンIDから、所有者および送信することを承認された者(それぞれにつき最大1つの変数)がマッピングされます。 イーサリアムでは、未初期化データは常にゼロであるため、所有者または承認済み送信者が存在しなければ、トークンの値はゼロになります。

1# @dev Mapping from owner address to count of his tokens.
2ownerToNFTokenCount: HashMap[address, uint256]
コピー

この変数は、各所有者ごとのトークン数を保持します。 所有者とトークンはマッピングされないため、特定の所有者が持つトークンを特定するには、ブロックチェーンのイベント履歴を過去に遡って確認し、当該のTransferイベントを確認するしかありません。 この変数を使用して、すべてのNFTでいつ取得したか知ることができ、それ以上調べる必要がありません。

ただし、このアルゴリズムはユーザーインターフェイスおよび外部サーバーのみを対象とする点に注意してください。 ブロックチェーン上で実行されるコード自体は、過去イベントを読み込むことができません。

1# @dev Mapping from owner address to mapping of operator addresses.
2ownerToOperators: HashMap[address, HashMap[address, bool]]
コピー

1つのアカウントには、複数のオペレーターを含めることができます。 しかし、単純なHashMapでは、各キーごとに単一の値を持つため、複数のオペレーターを追跡するのに十分ではありません。 その代わりに、HashMap[address, bool]を値として使用します。 デフォルトでは、各アドレスの値はFalseになっていますが、これはオペレーターでないことを示します。 オペレーターに設定するには、値をTrueに変更してください。

1# @dev Address of minter, who can mint a token
2minter: address
コピー

何らかの方法で、新規トークンを作成する必要があります。 このコントラクトでは、新規トークンを作成する権限を持つ唯一のエンティティはminterです。 使用事例がゲームなどの場合は、これで十分でしょう。 その他の目的の場合、より複雑なビジネスロジックを作成する必要があるでしょう。

1# @dev Mapping of interface id to bool about whether or not it's supported
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ERC165 interface ID of ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ERC165 interface ID of ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
コピー

ERC-165(opens in a new tab)では、コントラクトにおいてアプリケーションがどのようなやりとりを行うか、およびどのERC規格に準拠するのかを開示するメカニズムの仕様が規定されています。 このコントラクトの場合、ERC-165とERC-721に準拠しています。

関数

以下は、実際にERC-721で実装できる関数です。

コンストラクタ関数

1@external
2def __init__():
コピー

Pythonの場合と同様に、Vyperでもコンストラクタ関数は__init__で呼び出します。

1 """
2 @dev Contract constructor.
3 """
コピー

PythonおよびVyperでは、複数行の文字列(文頭および文末を"""に付ける)を指定し、この文字列を利用しないことでもコメントを作成できます。 これらのコメントには、 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
コピー

状態変数にアクセスするには、self.<variable name>を使用します(これも、Pythonと同様です)。

ビュー関数

ビュー関数は、ブロックチェーンの状態を変更しない関数であり、外部から呼び出されると無料で実行されます。 一方、コントラクトがビュー関数を呼び出す場合は全ノードで実行する必要があるため、ガス代が発生します。

1@view
2@external
コピー

アットマーク (@)で始まる関数定義の前に置かれたこれらのキーワードを、デコレータと呼びます。 デコレータは、関数を呼び出せる状況を特定します。

  • @viewは、関数がビューであることを規定します。
  • @externalは、関数がトランザクションおよび他のコントラクトで呼び出し可能であることを規定します。
1def supportsInterface(_interfaceID: bytes32) -> bool:
コピー

Pythonとは異なり、Vyperは静的型付け言語(opens in a new tab)です。 つまり、データ型(opens in a new tab)を指定しないと変数、つまり関数のパラメータを宣言できません。 この場合では、入力パラメータは、256ビット値のbytes32です (256ビットは、イーサリアム仮想マシンのネイティブワードサイズです) 。 出力は、ブール値です。 慣習により、関数におけるパラメータの名称はアンダースコア (_) で開始します。

1 """
2 @dev Interface identification is specified in ERC-165.
3 @param _interfaceID Id of the interface
4 """
5 return self.supportedInterfaces[_interfaceID]
コピー

コンストラクタ(__init__)で設定したself.supportedInterfacesハッシュマップが値を返します。

1### VIEW FUNCTIONS ###
コピー

これらは、ユーザーや他のコントラクトが利用できるトークンについての情報を提供するビュー関数です。

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev Returns the number of NFTs owned by `_owner`.
6 Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid.
7 @param _owner Address for whom to query the balance.
8 """
9 assert _owner != ZERO_ADDRESS
すべて表示
コピー

この行は、_owner値がゼロでないことを宣言(opens in a new tab)します。 値がゼロの場合、エラーが発生し操作が元に戻されます。

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 """
7 @dev Returns the address of the owner of the NFT.
8 Throws if `_tokenId` is not a valid NFT.
9 @param _tokenId The identifier for an NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Throws if `_tokenId` is not a valid NFT
13 assert owner != ZERO_ADDRESS
14 return owner
すべて表示
コピー

イーサリアム仮想マシン(EVM)では、値が格納されていないストレージはゼロになります。 _tokenIdにトークンが含まれない場合、self.idToOwner[_tokenId]の値はゼロになります。 その場合、関数は元に戻されます。

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev Get the approved address for a single NFT.
6 Throws if `_tokenId` is not a valid NFT.
7 @param _tokenId ID of the NFT to query the approval of.
8 """
9 # Throws if `_tokenId` is not a valid NFT
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
すべて表示
コピー

getApprovedは、ゼロの値を返す可能性がある点に注意してください。 有効なトークンが存在する場合、self.idToApprovals[_tokenId]の値が返されます。 承認者が存在しない場合、値はゼロになります。

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev Checks if `_operator` is an approved operator for `_owner`.
6 @param _owner The address that owns the NFTs.
7 @param _operator The address that acts on behalf of the owner.
8 """
9 return (self.ownerToOperators[_owner])[_operator]
すべて表示
コピー

この関数は、「_operator」がこのコントラクト内の「_owner」のトークンをすべて管理できるかどうかをチェックします。 複数のオペレーターが存在する可能性があるため、ハッシュマップも2つのレベルが存在します。

送信ヘルパー関数

これらの関数は、トークンの送信またはトークン管理の一部のオペレーションを実装しています。

1
2### TRANSFER FUNCTION HELPERS ###
3
4@view
5@internal
コピー

@internalのデコレータは、関数が同一コントラクト内の他の関数からのみアクセス可能であることを意味します。 これらの関数も、慣習によりアンダースコア (_) で開始します。

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev Returns whether the given spender can transfer a given token ID
4 @param spender address of the spender to query
5 @param tokenId uint256 ID of the token to be transferred
6 @return bool whether the msg.sender is approved for the given token ID,
7 is an operator of the owner, or is the owner of the 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
すべて表示
コピー

アドレスからトークンを送信できるようにするには、次の3通りの方法があります:

  1. アドレスが、トークンの所有者であること
  2. アドレスが、トークンの使用を承認されていること
  3. アドレスが、トークンの所有者のオペレーターであること

上記の機能は、状態を変更しないためビュー機能とすることもできます。 オペレーションコストを軽減するには、ビュー機能と指定できる関数はビュー機能にするべきです。

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev Add a NFT to a given address
5 Throws if `_tokenId` is owned by someone.
6 """
7 # Throws if `_tokenId` is owned by someone
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # Change the owner
10 self.idToOwner[_tokenId] = _to
11 # Change count tracking
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev Remove a NFT from a given address
19 Throws if `_from` is not the current owner.
20 """
21 # Throws if `_from` is not the current owner
22 assert self.idToOwner[_tokenId] == _from
23 # Change the owner
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # Change count tracking
26 self.ownerToNFTokenCount[_from] -= 1
すべて表示
コピー

送信に問題が発生した場合、呼び出しは取り消されます。

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev Clear an approval of a given address
5 Throws if `_owner` is not the current owner.
6 """
7 # Throws if `_owner` is not the current owner
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # Reset approvals
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
すべて表示
コピー

必要な場合のみ、値を変更してください。 状態変数は、ストレージ上に保存されます。 ストレージへの書き込みは、EVM(イーサリアム仮想マシン)においてガス代が最も高価なオペレーションの1つです。 既存の値の書き込みもコストが高いため、ストレージへの書き込みは最低限に抑えることをお勧めします。

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev Execute transfer of a NFT.
5 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
6 address for this NFT. (NOTE: `msg.sender` not allowed in private function so pass `_sender`.)
7 Throws if `_to` is the zero address.
8 Throws if `_from` is not the current owner.
9 Throws if `_tokenId` is not a valid NFT.
10 """
すべて表示
コピー

トークンの送信には2つの方法(通常モードと安全モード)が存在するため、この内部関数が提供されていますが、監査を容易にするために、コード内で送信するロケーションは1つだけにする必要があります。

1 # Check requirements
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # Throws if `_to` is the zero address
4 assert _to != ZERO_ADDRESS
5 # Clear approval. Throws if `_from` is not the current owner
6 self._clearApproval(_from, _tokenId)
7 # Remove NFT. Throws if `_tokenId` is not a valid NFT
8 self._removeTokenFrom(_from, _tokenId)
9 # Add NFT
10 self._addTokenTo(_to, _tokenId)
11 # Log the transfer
12 log Transfer(_from, _to, _tokenId)
すべて表示
コピー

Vyper上でイベントを発行するには、logステートメントを使用します(詳細は、こちら(opens in a new tab)をご覧ください)。

送信関数

1
2### TRANSFER FUNCTIONS ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
8 address for this NFT.
9 Throws if `_from` is not the current owner.
10 Throws if `_to` is the zero address.
11 Throws if `_tokenId` is not a valid NFT.
12 @notice The caller is responsible to confirm that `_to` is capable of receiving NFTs or else
13 they maybe be permanently lost.
14 @param _from The current owner of the NFT.
15 @param _to The new owner.
16 @param _tokenId The NFT to transfer.
17 """
18 self._transferFrom(_from, _to, _tokenId, msg.sender)
すべて表示
コピー

この関数は、任意のアドレスに送信する機能を提供します。 送信先のアドレスがユーザーであるか、トークンの送信方法が分かっているコントラクトでない場合、送信するトークンはそのアドレスに置かれたまま利用できない状態になります。

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev Transfers the ownership of an NFT from one address to another address.
10 Throws unless `msg.sender` is the current owner, an authorized operator, or the
11 approved address for this NFT.
12 Throws if `_from` is not the current owner.
13 Throws if `_to` is the zero address.
14 Throws if `_tokenId` is not a valid NFT.
15 If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if
16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
17 NOTE: bytes4 is represented by bytes32 with padding
18 @param _from The current owner of the NFT.
19 @param _to The new owner.
20 @param _tokenId The NFT to transfer.
21 @param _data Additional data with no specified format, sent in call to `_to`.
22 """
23 self._transferFrom(_from, _to, _tokenId, msg.sender)
すべて表示
コピー

問題が発生した場合にはトランザクションが元に戻され、コールに含まれるすべての事項が取り消されるため、先に送信を行っても構いません。

1 if _to.is_contract: # check if `_to` is a contract address
コピー

まず、アドレスがコントラクトであるか(コードを持つか)を確認してください。 アドレスがコントラクトでない場合、ユーザーのアドレスであれば、そのトークンを使用/送信できるようになります。 しかし、これがセキュリティを強化すると考えるべきではありません。 safeTransferFromを使用した場合でも、秘密鍵が不明なアドレスに送信したトークンは失われる場合があります。

1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
コピー

ERC-721トークンを受信できるかどうかを確認するには、送信先のコントラクトを呼び出します。

1 # Throws if transfer destination is a contract which does not implement 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
コピー

送信先がコントラクトであるが、ERC-721トークンを受け付けない場合(または、この特定の送信を受け付けないと選択した場合)、操作を取り消してください。

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev Set or reaffirm the approved address for an NFT. The zero address indicates there is no approved address.
5 Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner.
6 Throws if `_tokenId` is not a valid NFT. (NOTE: This is not written the EIP)
7 Throws if `_approved` is the current owner. (NOTE: This is not written the EIP)
8 @param _approved Address to be approved for the given NFT ID.
9 @param _tokenId ID of the token to be approved.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # Throws if `_tokenId` is not a valid NFT
13 assert owner != ZERO_ADDRESS
14 # Throws if `_approved` is the current owner
15 assert _approved != owner
すべて表示
コピー

慣習により、承認者を設定したくない場合、自分のアドレスではなく、ゼロのアドレスを指定してください。

1 # Check requirements
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)
コピー

承認を設定するには、所有者であるか、所有者が承認したオペレーターである必要があります。

1 # Set the approval
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev Enables or disables approval for a third party ("operator") to manage all of
10 `msg.sender`'s assets. It also emits the ApprovalForAll event.
11 Throws if `_operator` is the `msg.sender`. (NOTE: This is not written the EIP)
12 @notice This works even if sender doesn't own any tokens at the time.
13 @param _operator Address to add to the set of authorized operators.
14 @param _approved True if the operators is approved, false to revoke approval.
15 """
16 # Throws if `_operator` is the `msg.sender`
17 assert _operator != msg.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
すべて表示
コピー

新しいトークンのミントと既存トークンの破壊

コントラクトを作成したアカウントはminterとなり、新規のNFTをミントする承認を受けたスーパーユーザーとなります。 ただし、ミンターは既存トークンをバーンすることはできません。 バーンできるのは、所有者および所有者の承認を受けたエンティティのみです。

1### MINT & BURN FUNCTIONS ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:
コピー

操作が実行されない場合は常に元に戻されるため、この関数は常にTrueの値が返されます。

1 """
2 @dev Function to mint tokens
3 Throws if `msg.sender` is not the minter.
4 Throws if `_to` is zero address.
5 Throws if `_tokenId` is owned by someone.
6 @param _to The address that will receive the minted tokens.
7 @param _tokenId The token id to mint.
8 @return A boolean that indicates if the operation was successful.
9 """
10 # Throws if `msg.sender` is not the minter
11 assert msg.sender == self.minter
すべて表示
コピー

新たなトークンをミントできるのは、このミンター(この ERC-721コントラクトを作成したアカウント)のみです。 ミンターの身元を変更したい場合、この点は将来的に問題になる可能性があります。 本番環境のコントラクトでは、ミンターがミンター特権を他のユーザーに譲渡できる機能が必要になるでしょう。

1 # Throws if `_to` is zero address
2 assert _to != ZERO_ADDRESS
3 # Add NFT. Throws if `_tokenId` is owned by someone
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True
コピー

慣習により、新規トークンのミントは、ゼロアドレスからの送信としてカウントされます。

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev Burns a specific ERC721 token.
6 Throws unless `msg.sender` is the current owner, an authorized operator, or the approved
7 address for this NFT.
8 Throws if `_tokenId` is not a valid NFT.
9 @param _tokenId uint256 id of the ERC721 token to be burned.
10 """
11 # Check requirements
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # Throws if `_tokenId` is not a valid NFT
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
すべて表示
コピー

トークンの送信が許可されているユーザーは、そのトークンをバーンすることができます。 バーンは、ゼロアドレスへの送信と同じ外見を持ちますが、このゼロアドレスは実際にはこのトークンを受け取りません。 バーンを通じて、このトークンに使用されたすべてのストレージを解放できるため、トランザクションのガス代を軽減することができます。

コントラクトの使用方法

Solidityとは異なり、Viperは相続機能を提供しません。 これは、コードをより明確にし、セキュリティを確保しやすくするための意図的な設計上の選択によるものです。 このため、あなた自身がVyperでERC-721コントラクトを作成する際は、このコントラクト(opens in a new tab)を用いて修正し、必要なビジネスロジックを実装してください。

まとめ

このコントラクトにつき、最も重要なポイントを以下にまとめました:

  • 安全モードの送信を使ってERC-721トークンを受け取るには、コントラクトにERC721Receiverインターフェイスを実装する必要があります。
  • 安全モードの送信を実行した場合でも、秘密鍵が不明なアドレスに送信したトークンは行方不明になる可能性があります。
  • 操作に問題が発生した場合、単に失敗値をリターンするのではなく、コール自体を取り消すことをお勧めします。
  • ERC-721トークンには、必ず所有者が必要です。
  • NFTの送信を承認するには、3通りの方法があります。 承認できるのは、所有者であるか、特定トークンの送信を承認されているか、所有者のトークンすべてに対するオペレーターであるユーザーのみです。
  • 過去のイベントは、ブロックチェーン外でのみ表示されます。 ブロックチェーン内で実行されるコードは、表示できません。

それではさっそく、セキュアなVyperコントラクトを実装してみましょう。

最終編集者: @HiroyukiNaito(opens in a new tab), 2024年4月1日

このチュートリアルは役に立ちましたか?