Vyperで作成したERC-721コントラクトの紹介
はじめに
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 ERC72123implements: 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 are2# created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any3# number of NFTs may be created and assigned without emitting Transfer. At the time of any4# 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 zero2# address indicates there is no approved address. When a Transfer event emits, this also3# 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 manage2# 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 if6# 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]34# @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 token2minter: addressコピー
何らかの方法で、新規トークンを作成する必要があります。 このコントラクトでは、新規トークンを作成する権限を持つ唯一のエンティティはminter
です。 使用事例がゲームなどの場合は、これで十分でしょう。 その他の目的の場合、より複雑なビジネスロジックを作成する必要があるでしょう。
1# @dev Mapping of interface id to bool about whether or not it's supported2supportedInterfaces: HashMap[bytes32, bool]34# @dev ERC165 interface ID of ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ERC165 interface ID of ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdコピー
ERC-165(opens in a new tab)では、コントラクトにおいてアプリケーションがどのようなやりとりを行うか、およびどのERC規格に準拠するのかを開示するメカニズムの仕様が規定されています。 このコントラクトの場合、ERC-165とERC-721に準拠しています。
関数
以下は、実際にERC-721で実装できる関数です。
コンストラクタ関数
1@external2def __init__():コピー
Pythonの場合と同様に、Vyperでもコンストラクタ関数は__init__
で呼び出します。
1 """2 @dev Contract constructor.3 """コピー
PythonおよびVyperでは、複数行の文字列(文頭および文末を"""
に付ける)を指定し、この文字列を利用しないことでもコメントを作成できます。 これらのコメントには、 NatSpec(opens in a new tab)を含めることも可能です。
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderコピー
状態変数にアクセスするには、self.<variable name>
を使用します(これも、Pythonと同様です)。
ビュー関数
ビュー関数は、ブロックチェーンの状態を変更しない関数であり、外部から呼び出されると無料で実行されます。 一方、コントラクトがビュー関数を呼び出す場合は全ノードで実行する必要があるため、ガス代が発生します。
1@view2@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 interface4 """5 return self.supportedInterfaces[_interfaceID]コピー
コンストラクタ(__init__
)で設定したself.supportedInterfaces
ハッシュマップが値を返します。
1### VIEW FUNCTIONS ###コピー
これらは、ユーザーや他のコントラクトが利用できるトークンについての情報を提供するビュー関数です。
1@view2@external3def 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]23@view4@external5def 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 NFT13 assert owner != ZERO_ADDRESS14 return ownerすべて表示コピー
イーサリアム仮想マシン(EVM)では、値が格納されていないストレージはゼロになります。 _tokenId
にトークンが含まれない場合、self.idToOwner[_tokenId]
の値はゼロになります。 その場合、関数は元に戻されます。
1@view2@external3def 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 NFT10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]すべて表示コピー
getApproved
は、ゼロの値を返す可能性がある点に注意してください。 有効なトークンが存在する場合、self.idToApprovals[_tokenId]
の値が返されます。 承認者が存在しない場合、値はゼロになります。
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev 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つのレベルが存在します。
送信ヘルパー関数
これらの関数は、トークンの送信またはトークン管理の一部のオペレーションを実装しています。
12### TRANSFER FUNCTION HELPERS ###34@view5@internalコピー
@internal
のデコレータは、関数が同一コントラクト内の他の関数からのみアクセス可能であることを意味します。 これらの関数も、慣習によりアンダースコア (_
) で開始します。
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Returns whether the given spender can transfer a given token ID4 @param spender address of the spender to query5 @param tokenId uint256 ID of the token to be transferred6 @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 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 spenderIsApprovedForAllすべて表示コピー
アドレスからトークンを送信できるようにするには、次の3通りの方法があります:
- アドレスが、トークンの所有者であること
- アドレスが、トークンの使用を承認されていること
- アドレスが、トークンの所有者のオペレーターであること
上記の機能は、状態を変更しないためビュー機能とすることもできます。 オペレーションコストを軽減するには、ビュー機能と指定できる関数はビュー機能にするべきです。
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Add a NFT to a given address5 Throws if `_tokenId` is owned by someone.6 """7 # Throws if `_tokenId` is owned by someone8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Change the owner10 self.idToOwner[_tokenId] = _to11 # Change count tracking12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Remove a NFT from a given address19 Throws if `_from` is not the current owner.20 """21 # Throws if `_from` is not the current owner22 assert self.idToOwner[_tokenId] == _from23 # Change the owner24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Change count tracking26 self.ownerToNFTokenCount[_from] -= 1すべて表示コピー
送信に問題が発生した場合、呼び出しは取り消されます。
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Clear an approval of a given address5 Throws if `_owner` is not the current owner.6 """7 # Throws if `_owner` is not the current owner8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Reset approvals11 self.idToApprovals[_tokenId] = ZERO_ADDRESSすべて表示コピー
必要な場合のみ、値を変更してください。 状態変数は、ストレージ上に保存されます。 ストレージへの書き込みは、EVM(イーサリアム仮想マシン)においてガス代が最も高価なオペレーションの1つです。 既存の値の書き込みもコストが高いため、ストレージへの書き込みは最低限に抑えることをお勧めします。
1@internal2def _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 approved6 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 requirements2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Throws if `_to` is the zero address4 assert _to != ZERO_ADDRESS5 # Clear approval. Throws if `_from` is not the current owner6 self._clearApproval(_from, _tokenId)7 # Remove NFT. Throws if `_tokenId` is not a valid NFT8 self._removeTokenFrom(_from, _tokenId)9 # Add NFT10 self._addTokenTo(_to, _tokenId)11 # Log the transfer12 log Transfer(_from, _to, _tokenId)すべて表示コピー
Vyper上でイベントを発行するには、log
ステートメントを使用します(詳細は、こちら(opens in a new tab)をご覧ください)。
送信関数
12### TRANSFER FUNCTIONS ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Throws unless `msg.sender` is the current owner, an authorized operator, or the approved8 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 else13 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@external2def 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 the11 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 if16 the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 NOTE: bytes4 is represented by bytes32 with padding18 @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@external2def 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 NFT13 assert owner != ZERO_ADDRESS14 # Throws if `_approved` is the current owner15 assert _approved != ownerすべて表示コピー
慣習により、承認者を設定したくない場合、自分のアドレスではなく、ゼロのアドレスを指定してください。
1 # Check requirements2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)コピー
承認を設定するには、所有者であるか、所有者が承認したオペレーターである必要があります。
1 # Set the approval2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Enables or disables approval for a third party ("operator") to manage all of10 `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.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)すべて表示コピー
新しいトークンのミントと既存トークンの破壊
コントラクトを作成したアカウントはminter
となり、新規のNFTをミントする承認を受けたスーパーユーザーとなります。 ただし、ミンターは既存トークンをバーンすることはできません。 バーンできるのは、所有者および所有者の承認を受けたエンティティのみです。
1### MINT & BURN FUNCTIONS ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:コピー
操作が実行されない場合は常に元に戻されるため、この関数は常にTrue
の値が返されます。
1 """2 @dev Function to mint tokens3 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 minter11 assert msg.sender == self.minterすべて表示コピー
新たなトークンをミントできるのは、このミンター(この ERC-721コントラクトを作成したアカウント)のみです。 ミンターの身元を変更したい場合、この点は将来的に問題になる可能性があります。 本番環境のコントラクトでは、ミンターがミンター特権を他のユーザーに譲渡できる機能が必要になるでしょう。
1 # Throws if `_to` is zero address2 assert _to != ZERO_ADDRESS3 # Add NFT. Throws if `_tokenId` is owned by someone4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return Trueコピー
慣習により、新規トークンのミントは、ゼロアドレスからの送信としてカウントされます。
12@external3def 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 approved7 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 requirements12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Throws if `_tokenId` is not a valid NFT15 assert owner != ZERO_ADDRESS16 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コントラクトを実装してみましょう。
最終編集者: @nhsz(opens in a new tab), 2023年8月15日