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

Vyper ERC-721コントラクトウォークスルー

Vyper
ERC-721
Python
初級
Ori Pomerantz
2021年4月1日
35 分の読書

はじめに

ERC-721標準は、非代替性トークン(NFT)の所有権を保持するために使用されます。 ERC-20トークンは、個々のトークンに違いがないため、コモディティのように振る舞います。 対照的に、ERC-721トークンは、さまざまな猫の 漫画や、異なる不動産の所有権など、似ているが同一ではない資産のために設計されています。

この記事では、中村龍矢氏のERC-721コントラクト (opens in a new tab)を分析します。 このコントラクトは、Pythonに似たコントラクト言語であるVyper (opens in a new tab)で書かれています。Vyperは、Solidityよりも安全でないコードを書くのが難しくなるように設計されています。

コントラクト

1# @dev ERC-721非代替性トークン標準の実装。
2# @author Ryuya Nakamura (@nrryuya)
3# 変更元: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Vyperのコメントは、Pythonと同様に、ハッシュ記号()で始まり、行末まで続きます。 @<キーワード>を含むコメントは、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で書かれている可能性のある外部クライアントからブロックチェーンにトランザクションを送信する際にも使用されるからです。

最初の行でインターフェイスをインポートし、2行目でここで実装することを指定しています。

ERC721Receiverインターフェイス

1# 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 何らかのメカニズムによってNFTの所有権が変更されたときに発行されます。このイベントは、NFTが作成
2# (`from` == 0)および破棄(`to` == 0)されたときに発行されます。例外:コントラクト作成中、任意の
3# 数のNFTがTransferを発行せずに作成および割り当てられる場合があります。任意の
4# 送金時、そのNFTの承認済みアドレス(もしあれば)はnoneにリセットされます。
5# @param _from NFTの送信者(アドレスがゼロアドレスの場合、トークンの作成を示します)。
6# @param _to NFTの受信者(アドレスがゼロアドレスの場合、トークンの破棄を示します)。
7# @param _tokenId 送金されたNFT。
8event Transfer:
9 sender: indexed(address)
10 receiver: indexed(address)
11 tokenId: indexed(uint256)
すべて表示

これは、金額の代わりにtokenIdを報告する点を除けば、ERC-20のTransferイベントと似ています。 ゼロアドレスを所有する者はいないため、慣例的にトークンの作成と破棄を報告するために使用します。

1# @dev NFTの承認済みアドレスが変更または再確認されたときに発行されます。ゼロ
2# アドレスは、承認済みアドレスがないことを示します。Transferイベントが発行されると、これも
3# そのNFTの承認済みアドレス(もしあれば)がnoneにリセットされることを示します。
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、
6# 取り消された場合はfalse)。
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 NFT IDからそれを所有するアドレスへのマッピング。
2idToOwner: HashMap[uint256, address]
3
4# @dev NFT IDから承認済みアドレスへのマッピング。
5idToApprovals: HashMap[uint256, address]

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

1# @dev 所有者アドレスからそのトークン数へのマッピング。
2ownerToNFTokenCount: HashMap[address, uint256]

この変数は、各所有者のトークン数を保持します。 所有者からトークンへのマッピングはないため、 特定の所有者が所有するトークンを識別する唯一の方法は、ブロックチェーンのイベント履歴を遡り、 適切なTransferイベントを見ることです。 この変数を使用して、すべてのNFTをいつ取得したかを知ることができ、 それ以上調べる必要がありません。

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

1# @dev 所有者アドレスからオペレーターアドレスのマッピングへのマッピング。
2ownerToOperators: HashMap[address, HashMap[address, bool]]

1つのアカウントに複数のオペレーターがいる場合があります。 単純なHashMapでは、各キーが単一の値につながるため、 それらを追跡するには不十分です。 代わりに、値として HashMap[address, bool]を使用できます。 デフォルトでは、各アドレスの値はFalseであり、 オペレーターではないことを意味します。 必要に応じて値をTrueに設定できます。

1# @dev トークンをミントできるミンターのアドレス
2minter: address

新しいトークンは何らかの方法で作成されなければなりません。 このコントラクトには、それを許可された単一のエンティティ、 minterが存在します。 例えば、ゲームにとってはこれで十分でしょう。 他の目的のためには、 より複雑なビジネスロジックを作成する必要があるかもしれません。

1# @dev インターフェイスIDから、それがサポートされているかどうかを示すbool値へのマッピング
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev ERC165のERC165インターフェイスID
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev ERC721のERC165インターフェイスID
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

ERC-165 (opens in a new tab)は、コントラクトがアプリケーションと 通信する方法、つまりどのERCに準拠しているかを公開するためのメカニズムを指定します。 この場合、コントラクトはERC-165とERC-721に準拠しています。

関数

これらは、実際にERC-721を実装する関数です。

コンストラクタ

1@external
2def __init__():

Vyperでは、Pythonと同様に、コンストラクタ関数は__init__と呼ばれます。

1 """
2 @dev コントラクトコンストラクタ。
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.<変数名>を使用します(これもPythonと同じです)。

ビュー関数

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

1@view
2@external

関数定義の前にある、アットマーク(@)で始まるこれらのキーワードは、_デコレータ_と呼ばれます。 これらは 関数を呼び出すことができる状況を指定します。

  • @viewは、この関数がビューであることを指定します。
  • @externalは、この特定の関数がトランザクションや他のコントラクトによって呼び出されることを指定します。
1def supportsInterface(_interfaceID: bytes32) -> bool:

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

1 """
2 @dev インターフェイスの識別はERC-165で指定されています。
3 @param _interfaceID インターフェイスのID
4 """
5 return self.supportedInterfaces[_interfaceID]

コンストラクタ(__init__)で設定されたself.supportedInterfaces HashMapから値を返します。

1### ビュー関数 ###
2

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

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev `_owner`が所有するNFTの数を返します。
6 `_owner`がゼロアドレスの場合はスローします。ゼロアドレスに割り当てられたNFTは無効と見なされます。
7 @param _owner 残高を照会するアドレス。
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 NFTの所有者のアドレスを返します。
8 `_tokenId`が有効なNFTでない場合はスローします。
9 @param _tokenId NFTの識別子。
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # `_tokenId`が有効な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 単一のNFTの承認済みアドレスを取得します。
6 `_tokenId`が有効なNFTでない場合はスローします。
7 @param _tokenId 承認を照会するNFTのID。
8 """
9 # `_tokenId`が有効な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 `_operator`が`_owner`の承認済みオペレーターであるかどうかをチェックします。
6 @param _owner NFTを所有するアドレス。
7 @param _operator 所有者の代わりに機能するアドレス。
8 """
9 return (self.ownerToOperators[_owner])[_operator]
すべて表示

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

送金ヘルパー関数

これらの関数は、トークンの送金または管理の一部である操作を実装します。

1
2### 送金関数ヘルパー ###
3
4@view
5@internal

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

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev 指定されたスペンダーが指定されたトークンIDを送金できるかどうかを返します
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 == _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 指定されたアドレスにNFTを追加します
5 `_tokenId`が誰かによって所有されている場合はスローします。
6 """
7 # `_tokenId`が誰かによって所有されている場合はスローします
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # 所有者を変更
10 self.idToOwner[_tokenId] = _to
11 # カウント追跡を変更
12 self.ownerToNFTokenCount[_to] += 1
13
14
15@internal
16def _removeTokenFrom(_from: address, _tokenId: uint256):
17 """
18 @dev 指定されたアドレスからNFTを削除します
19 `_from`が現在の所有者でない場合はスローします。
20 """
21 # `_from`が現在の所有者でない場合はスローします
22 assert self.idToOwner[_tokenId] == _from
23 # 所有者を変更
24 self.idToOwner[_tokenId] = ZERO_ADDRESS
25 # カウント追跡を変更
26 self.ownerToNFTokenCount[_from] -= 1
すべて表示

送金に問題がある場合、呼び出しを元に戻します。

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev 指定されたアドレスの承認をクリアします
5 `_owner`が現在の所有者でない場合はスローします。
6 """
7 # `_owner`が現在の所有者でない場合はスローします
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # 承認をリセット
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
すべて表示

必要な場合のみ、値を変更してください。 状態変数はストレージに存在します。 ストレージへの書き込みは、 EVM (イーサリアム仮想マシン) が行う最も高価な操作の1つです(ガスの観点から)。 したがって、既存の値を書き込むだけでも コストが高いため、これを最小限に抑えることをお勧めします。

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev NFTの送金を実行します。
5 `msg.sender`が現在の所有者、承認されたオペレーター、またはこのNFTの承認
6 済みアドレスでない限り、スローします。(注:`msg.sender`はプライベート関数では許可されていないため、`_sender`を渡します。)
7 `_to`がゼロアドレスの場合はスローします。
8 `_from`が現在の所有者でない場合はスローします。
9 `_tokenId`が有効なNFTでない場合はスローします。
10 """
すべて表示

トークンを送金するには2つの方法(通常と安全)があるため、この内部関数がありますが、 監査を容易にするために、コード内の1つの場所でのみ実行するようにしています。

1 # 要件をチェック
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # `_to`がゼロアドレスの場合はスローします
4 assert _to != ZERO_ADDRESS
5 # 承認をクリアします。`_from`が現在の所有者でない場合はスローします
6 self._clearApproval(_from, _tokenId)
7 # NFTを削除します。`_tokenId`が有効なNFTでない場合はスローします
8 self._removeTokenFrom(_from, _tokenId)
9 # NFTを追加
10 self._addTokenTo(_to, _tokenId)
11 # 送金をログに記録
12 log Transfer(_from, _to, _tokenId)
すべて表示

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

送金関数

1
2### 送金関数 ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev `msg.sender`が現在の所有者、承認されたオペレーター、またはこのNFTの承認
8 済みアドレスでない限り、スローします。
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@external
2def 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`がスマートコントラクトの場合、`_to`で`onERC721Received`を呼び出し、
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@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev NFTの承認済みアドレスを設定または再確認します。ゼロアドレスは、承認済みアドレスがないことを示します。
5 `msg.sender`が現在のNFT所有者または現在の所有者の承認済みオペレーターでない限り、スローします。
6 `_tokenId`が有効なNFTでない場合はスローします。(注:これはEIPには書かれていません)
7 `_approved`が現在の所有者である場合はスローします。(注:これはEIPには書かれていません)
8 @param _approved 指定されたNFT IDに対して承認されるアドレス。
9 @param _tokenId 承認されるトークンのID。
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # `_tokenId`が有効なNFTでない場合はスローします
13 assert owner != ZERO_ADDRESS
14 # `_approved`が現在の所有者である場合はスローします
15 assert _approved != owner
すべて表示

慣例により、承認者を設定したくない場合は、自分自身ではなくゼロアドレスを指定します。

1 # 要件をチェック
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)

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

1 # 承認を設定
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5
6@external
7def setApprovalForAll(_operator: address, _approved: bool):
8 """
9 @dev サードパーティ(「オペレーター」)が`msg.sender`の
10 すべての資産を管理するための承認を有効または無効にします。また、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.sender
18 self.ownerToOperators[msg.sender][_operator] = _approved
19 log ApprovalForAll(msg.sender, _operator, _approved)
すべて表示

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

コントラクトを作成したアカウントはminterであり、新しいNFTをミントする権限を持つ スーパーユーザーです。 ただし、既存のトークンをバーンすることは許可されていません。 所有者、または所有者によって承認された エンティティのみがそれを行うことができます。

1### ミント&バーン関数 ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:

操作が失敗した場合は元に戻されるため、この関数は常にTrueを返します。

1 """
2 @dev トークンをミントする関数
3 `msg.sender`がミンターでない場合はスローします。
4 `_to`がゼロアドレスの場合はスローします。
5 `_tokenId`が誰かによって所有されている場合はスローします。
6 @param _to ミントされたトークンを受け取るアドレス。
7 @param _tokenId ミントするトークンID。
8 @return 操作が成功したかどうかを示すブール値。
9 """
10 # `msg.sender`がミンターでない場合はスローします
11 assert msg.sender == self.minter
すべて表示

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

1 # `_to`がゼロアドレスの場合はスローします
2 assert _to != ZERO_ADDRESS
3 # NFTを追加します。`_tokenId`が誰かによって所有されている場合はスローします
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 特定のERC721トークンをバーンします。
6 `msg.sender`が現在の所有者、承認されたオペレーター、またはこのNFTの承認
7 済みアドレスでない限り、スローします。
8 `_tokenId`が有効なNFTでない場合はスローします。
9 @param _tokenId バーンされるERC721トークンのuint256 ID。
10 """
11 # 要件をチェック
12 assert self._isApprovedOrOwner(msg.sender, _tokenId)
13 owner: address = self.idToOwner[_tokenId]
14 # `_tokenId`が有効なNFTでない場合はスローします
15 assert owner != ZERO_ADDRESS
16 self._clearApproval(owner, _tokenId)
17 self._removeTokenFrom(owner, _tokenId)
18 log Transfer(owner, ZERO_ADDRESS, _tokenId)
すべて表示

トークンの送金を許可されている人は誰でも、それをバーンすることが許可されています。 バーンはゼロアドレスへの送金と 同等に見えますが、ゼロアドレスは実際にはトークンを受け取りません。 これにより、トークンに使用されていたすべてのストレージを 解放でき、トランザクションのガス代を削減できます。

このコントラクトの使用

Solidityとは対照的に、Vyperには継承がありません。 これは、コードをより明確にし、 したがってセキュリティを確保しやすくするための意図的な設計上の選択です。 したがって、独自のVyper ERC-721コントラクトを作成するには、この コントラクトを取得し、 必要なビジネスロジックを実装するように変更します。

結論

復習のために、このコントラクトの最も重要なアイデアをいくつか紹介します。

  • 安全な送金でERC-721トークンを受信するには、コントラクトはERC721Receiverインターフェイスを実装する必要があります。
  • 安全な送金を使用しても、秘密鍵が不明なアドレスに送信すると、 トークンがスタックする可能性があります。
  • 操作に問題がある場合は、単に失敗値を返すのではなく、 呼び出しをrevertすることをお勧めします。
  • ERC-721トークンは、所有者がいる場合に存在します。
  • NFTの送金を承認するには、3つの方法があります。 所有者であるか、特定のトークンに対して承認されているか、 または所有者のすべてのトークンのオペレーターであることができます。
  • 過去のイベントはブロックチェーンの外部からのみ表示されます。 ブロックチェーン内で実行されるコードは、それらを表示できません。

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

私の他の作品はこちらでご覧いただけます (opens in a new tab).

最終更新: 2025年8月22日

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