Hướng dẫn hợp đồng Vyper ERC-721
Giới thiệu
Tiêu chuẩn ERC-721 được dùng để nắm giữ quyền sở hữu các Token không thể thay thế (NFT). Các token ERC-20 hoạt động như một loại hàng hóa vì không có sự khác biệt giữa các token riêng lẻ. Ngược lại, các token ERC-721 được thiết kế cho các tài sản tương tự nhau nhưng không giống hệt nhau, chẳng hạn như các phim hoạt hình mèo khác nhau hoặc các quyền sở hữu đối với các bất động sản khác nhau.
Trong bài viết này, chúng ta sẽ phân tích hợp đồng ERC-721 của Ryuya Nakamura (opens in a new tab). Hợp đồng này được viết bằng Vyper (opens in a new tab), một ngôn ngữ hợp đồng giống như Python được thiết kế để gây khó khăn hơn trong việc viết mã không an toàn so với Solidity.
Hợp đồng
1# @dev Triển khai tiêu chuẩn token không thể thay thế ERC-721.2# @author Ryuya Nakamura (@nrryuya)3# Được sửa đổi từ: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vyCác bình luận trong Vyper, cũng như trong Python, bắt đầu bằng một hàm băm (#) và tiếp tục cho đến cuối dòng. Các bình luận bao gồm
@<từ khóa> được NatSpec (opens in a new tab) sử dụng để tạo ra tài liệu tham khảo
có thể đọc được.
1from vyper.interfaces import ERC72123implements: ERC721Giao diện ERC-721 được tích hợp vào ngôn ngữ Vyper. Bạn có thể xem định nghĩa mã tại đây (opens in a new tab). Định nghĩa giao diện được viết bằng Python chứ không phải Vyper, vì giao diện không chỉ được sử dụng trong chuỗi khối, mà còn khi gửi giao dịch đến chuỗi khối từ một ứng dụng bên ngoài, có thể được viết bằng Python.
Dòng đầu tiên nhập giao diện và dòng thứ hai chỉ định rằng chúng tôi đang triển khai nó ở đây.
Giao diện ERC721Receiver
1# Giao diện cho hợp đồng được gọi bởi safeTransferFrom()2interface ERC721Receiver:3 def onERC721Received(ERC-721 hỗ trợ hai loại chuyển:
transferFrom, cho phép người gửi chỉ định bất kỳ địa chỉ đích nào và đặt trách nhiệm chuyển cho người gửi. Điều này có nghĩa là bạn có thể chuyển đến một địa chỉ không hợp lệ, trong trường hợp đó NFT sẽ bị mất vĩnh viễn.safeTransferFrom, kiểm tra xem địa chỉ đích có phải là hợp đồng hay không. Nếu vậy, hợp đồng ERC-721 sẽ hỏi hợp đồng nhận xem có muốn nhận NFT hay không.
Để trả lời yêu cầu của safeTransferFrom, hợp đồng nhận phải triển khai ERC721Receiver.
1 _operator: địa chỉ,2 _from: địa chỉ,Địa chỉ _from là chủ sở hữu hiện tại của token. Địa chỉ _operator là địa chỉ đã
yêu cầu chuyển (hai địa chỉ này có thể không giống nhau, do các khoản phụ cấp).
1 _tokenId: uint256,ID token ERC-721 là 256 bit. Thông thường chúng được tạo ra bằng cách băm mô tả của bất cứ thứ gì mà token đại diện.
1 _data: Byte[1024]Yêu cầu có thể có tối đa 1024 byte dữ liệu người dùng.
1 ) -> bytes32: viewĐể ngăn chặn các trường hợp hợp đồng vô tình chấp nhận chuyển, giá trị trả về không phải là một giá trị boolean, mà là 256 bit với một giá trị cụ thể.
Hàm này là một chế độ xem, có nghĩa là nó có thể đọc trạng thái của chuỗi khối, nhưng không sửa đổi nó.
Sự kiện
Các sự kiện (opens in a new tab) được phát ra để thông báo cho người dùng và các máy chủ bên ngoài chuỗi khối về các sự kiện. Lưu ý rằng nội dung của các sự kiện không có sẵn cho các hợp đồng trên chuỗi khối.
1# @dev Phát ra khi quyền sở hữu của bất kỳ NFT nào thay đổi theo bất kỳ cơ chế nào. Sự kiện này phát ra khi các NFT được2# tạo (`from` == 0) và bị hủy (`to` == 0). Ngoại lệ: trong quá trình tạo hợp đồng, bất kỳ3# số lượng NFT nào cũng có thể được tạo và gán mà không phát ra Transfer. Tại thời điểm bất kỳ4# lần chuyển nào, địa chỉ được phê duyệt cho NFT đó (nếu có) được đặt lại thành không.5# @param _from Người gửi NFT (nếu địa chỉ là địa chỉ không thì cho biết tạo token).6# @param _to Người nhận NFT (nếu địa chỉ là địa chỉ không thì cho biết hủy token).7# @param _tokenId NFT đã được chuyển.8event Transfer:9 sender: indexed(address)10 receiver: indexed(address)11 tokenId: indexed(uint256)Hiện tất cảĐiều này tương tự như sự kiện Chuyển ERC-20, ngoại trừ việc chúng tôi báo cáo tokenId thay vì số lượng.
Không ai sở hữu địa chỉ không, vì vậy theo quy ước, chúng tôi sử dụng nó để báo cáo việc tạo và hủy token.
1# @dev Điều này phát ra khi địa chỉ được phê duyệt cho một NFT được thay đổi hoặc xác nhận lại. Địa chỉ2# không cho biết không có địa chỉ được phê duyệt nào. Khi một sự kiện Transfer phát ra, điều này cũng3# cho biết rằng địa chỉ được phê duyệt cho NFT đó (nếu có) được đặt lại thành không.4# @param _owner Chủ sở hữu của NFT.5# @param _approved Địa chỉ mà chúng tôi đang phê duyệt.6# @param _tokenId NFT mà chúng tôi đang phê duyệt.7event Approval:8 owner: indexed(address)9 approved: indexed(address)10 tokenId: indexed(uint256)Hiện tất cảSự chấp thuận ERC-721 tương tự như một khoản phụ cấp ERC-20. Một địa chỉ cụ thể được phép chuyển một token cụ thể. Điều này cung cấp một cơ chế để các hợp đồng phản hồi khi chúng chấp nhận một token. Các hợp đồng không thể lắng nghe các sự kiện, vì vậy nếu bạn chỉ chuyển token cho chúng, chúng sẽ không "biết" về nó. Bằng cách này, chủ sở hữu trước tiên gửi một sự chấp thuận và sau đó gửi một yêu cầu đến hợp đồng: "Tôi đã chấp thuận cho bạn chuyển token X, xin hãy làm ...".
Đây là một lựa chọn thiết kế để làm cho tiêu chuẩn ERC-721 tương tự như tiêu chuẩn ERC-20. Vì các token ERC-721 không thể thay thế được, một hợp đồng cũng có thể xác định rằng nó đã nhận được một token cụ thể bằng cách nhìn vào quyền sở hữu của token.
1# @dev Điều này phát ra khi một người vận hành được kích hoạt hoặc vô hiệu hóa cho một chủ sở hữu. Người vận hành có thể quản lý2# tất cả các NFT của chủ sở hữu.3# @param _owner Chủ sở hữu của NFT.4# @param _operator Địa chỉ mà chúng tôi đang đặt quyền của người vận hành.5# @param _approved Trạng thái quyền của người vận hành (true nếu quyền của người vận hành được cấp và false nếu6# bị thu hồi).7event ApprovalForAll:8 owner: indexed(address)9 operator: indexed(address)10 approved: boolHiện tất cảĐôi khi, việc có một người vận hành có thể quản lý tất cả các token của một tài khoản thuộc một loại cụ thể (những token được quản lý bởi một hợp đồng cụ thể) là hữu ích, tương tự như giấy ủy quyền. Ví dụ: tôi có thể muốn trao quyền đó cho một hợp đồng kiểm tra xem tôi đã không liên lạc với nó trong sáu tháng, và nếu vậy sẽ phân phối tài sản của tôi cho những người thừa kế của tôi (nếu một trong số họ yêu cầu, các hợp đồng không thể làm bất cứ điều gì mà không được gọi bởi một giao dịch). Trong ERC-20, chúng tôi chỉ có thể cung cấp một khoản phụ cấp cao cho một hợp đồng thừa kế, nhưng điều đó không hoạt động với ERC-721 vì các token không thể thay thế được. Đây là điều tương đương.
Giá trị approved cho chúng ta biết liệu sự kiện đó là để chấp thuận hay rút lại sự chấp thuận.
Biến trạng thái
Các biến này chứa trạng thái hiện tại của các token: token nào có sẵn và ai sở hữu chúng. Hầu hết trong số này
là các đối tượng HashMap, các ánh xạ một chiều tồn tại giữa hai loại (opens in a new tab).
1# @dev Ánh xạ từ ID NFT đến địa chỉ sở hữu nó.2idToOwner: HashMap[uint256, address]34# @dev Ánh xạ từ ID NFT đến địa chỉ được phê duyệt.5idToApprovals: HashMap[uint256, address]Danh tính người dùng và hợp đồng trong Ethereum được biểu thị bằng các địa chỉ 160-bit. Hai biến này ánh xạ từ ID token đến chủ sở hữu của chúng và những người được chấp thuận chuyển chúng (tối đa một người cho mỗi token). Trong Ethereum, dữ liệu chưa được khởi tạo luôn bằng không, vì vậy nếu không có chủ sở hữu hoặc người chuyển được chấp thuận, giá trị của token đó là không.
1# @dev Ánh xạ từ địa chỉ chủ sở hữu đến số lượng token của anh ấy.2ownerToNFTokenCount: HashMap[address, uint256]Biến này chứa số lượng token cho mỗi chủ sở hữu. Không có ánh xạ từ chủ sở hữu đến token, vì vậy
cách duy nhất để xác định các token mà một chủ sở hữu cụ thể sở hữu là xem lại lịch sử sự kiện của chuỗi khối
và xem các sự kiện Chuyển thích hợp. Chúng ta có thể sử dụng biến này để biết khi nào chúng ta có tất cả các NFT và không
cần phải tìm kiếm xa hơn nữa trong quá khứ.
Lưu ý rằng thuật toán này chỉ hoạt động đối với giao diện người dùng và các máy chủ bên ngoài. Mã chạy trên chính chuỗi khối không thể đọc các sự kiện trong quá khứ.
1# @dev Ánh xạ từ địa chỉ chủ sở hữu đến ánh xạ các địa chỉ của người vận hành.2ownerToOperators: HashMap[address, HashMap[address, bool]]Một tài khoản có thể có nhiều hơn một người vận hành. Một HashMap đơn giản là không đủ để
theo dõi chúng, bởi vì mỗi khóa dẫn đến một giá trị duy nhất. Thay vào đó, bạn có thể sử dụng
HashMap[địa chỉ, bool] làm giá trị. Theo mặc định, giá trị cho mỗi địa chỉ là False, có nghĩa là nó
không phải là một người vận hành. Bạn có thể đặt các giá trị thành True khi cần.
1# @dev Địa chỉ của người đúc, người có thể đúc một token2minter: addressCác token mới phải được tạo ra bằng một cách nào đó. Trong hợp đồng này, có một thực thể duy nhất được phép làm như vậy, đó là
người đúc. Ví dụ, điều này có thể là đủ cho một trò chơi. Đối với các mục đích khác, có thể cần phải
tạo ra một logic kinh doanh phức tạp hơn.
1# @dev Ánh xạ của id giao diện đến bool về việc nó có được hỗ trợ hay không2supportedInterfaces: HashMap[bytes32, bool]34# @dev ID giao diện ERC165 của ERC1655ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a767# @dev ID giao diện ERC165 của ERC7218ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cdERC-165 (opens in a new tab) chỉ định một cơ chế để một hợp đồng tiết lộ cách các ứng dụng có thể giao tiếp với nó, nó tuân thủ những ERC nào. Trong trường hợp này, hợp đồng tuân thủ ERC-165 và ERC-721.
Các hàm
Đây là các hàm thực sự triển khai ERC-721.
Hàm khởi tạo
1@external2def __init__():Trong Vyper, cũng như trong Python, hàm khởi tạo được gọi là __init__.
1 """2 @dev Hàm khởi tạo hợp đồng.3 """Trong Python và trong Vyper, bạn cũng có thể tạo một bình luận bằng cách chỉ định một chuỗi nhiều dòng (bắt đầu và kết thúc
với """), và không sử dụng nó theo bất kỳ cách nào. Những bình luận này cũng có thể bao gồm
NatSpec (opens in a new tab).
1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True3 self.minter = msg.senderĐể truy cập các biến trạng thái, bạn sử dụng self.<tên biến> (một lần nữa, giống như trong Python).
Xem Hàm
Đây là các hàm không sửa đổi trạng thái của chuỗi khối, và do đó có thể được thực thi miễn phí nếu chúng được gọi từ bên ngoài. Nếu các hàm xem được gọi bởi một hợp đồng, chúng vẫn phải được thực thi trên mọi nút và do đó tốn gas.
1@view2@externalNhững từ khóa này trước một định nghĩa hàm bắt đầu bằng dấu a còng (@) được gọi là decorations. Chúng
chỉ định các trường hợp trong đó một hàm có thể được gọi.
@viewchỉ định rằng hàm này là một chế độ xem.@externalchỉ định rằng hàm cụ thể này có thể được gọi bởi các giao dịch và bởi các hợp đồng khác.
1def supportsInterface(_interfaceID: bytes32) -> bool:Trái ngược với Python, Vyper là một ngôn ngữ được gõ tĩnh (opens in a new tab).
Bạn không thể khai báo một biến, hoặc một tham số hàm, mà không xác định kiểu dữ liệu (opens in a new tab). Trong trường hợp này, tham số đầu vào là bytes32, một giá trị 256-bit
(256 bit là kích thước từ gốc của Máy ảo Ethereum). Đầu ra là một giá trị
boolean. Theo quy ước, tên của các tham số hàm bắt đầu bằng dấu gạch dưới (_).
1 """2 @dev Việc xác định giao diện được chỉ định trong ERC-165.3 @param _interfaceID Id của giao diện4 """5 return self.supportedInterfaces[_interfaceID]Trả về giá trị từ HashMap self.supportedInterfaces, được đặt trong hàm khởi tạo (__init__).
1### XEM HÀM ###2Đây là các hàm xem giúp cung cấp thông tin về các token cho người dùng và các hợp đồng khác.
1@view2@external3def balanceOf(_owner: address) -> uint256:4 """5 @dev Trả về số lượng NFT thuộc sở hữu của `_owner`.6 Thông báo lỗi nếu `_owner` là địa chỉ không. Các NFT được gán cho địa chỉ không được coi là không hợp lệ.7 @param _owner Địa chỉ để truy vấn số dư.8 """9 assert _owner != ZERO_ADDRESSHiện tất cảDòng này khẳng định (opens in a new tab) rằng _owner không phải là
không. Nếu đúng như vậy, sẽ có lỗi và hoạt động sẽ bị hoàn nguyên.
1 return self.ownerToNFTokenCount[_owner]23@view4@external5def ownerOf(_tokenId: uint256) -> address:6 """7 @dev Trả về địa chỉ của chủ sở hữu NFT.8 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.9 @param _tokenId Mã định danh cho một NFT.10 """11 owner: address = self.idToOwner[_tokenId]12 # Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ13 assert owner != ZERO_ADDRESS14 return ownerHiện tất cảTrong Máy ảo Ethereum (EVM) bất kỳ bộ nhớ nào không có giá trị được lưu trữ trong đó đều bằng không.
Nếu không có token nào ở _tokenId thì giá trị của self.idToOwner[_tokenId] bằng không. Trong trường hợp đó
hàm sẽ hoàn nguyên.
1@view2@external3def getApproved(_tokenId: uint256) -> address:4 """5 @dev Nhận địa chỉ được phê duyệt cho một NFT duy nhất.6 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.7 @param _tokenId ID của NFT để truy vấn sự phê duyệt.8 """9 # Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS11 return self.idToApprovals[_tokenId]Hiện tất cảLưu ý rằng getApproved có thể trả về không. Nếu token hợp lệ, nó sẽ trả về self.idToApprovals[_tokenId].
Nếu không có người phê duyệt, giá trị đó là không.
1@view2@external3def isApprovedForAll(_owner: address, _operator: address) -> bool:4 """5 @dev Kiểm tra xem `_operator` có phải là người vận hành được phê duyệt cho `_owner` không.6 @param _owner Địa chỉ sở hữu các NFT.7 @param _operator Địa chỉ hoạt động thay mặt cho chủ sở hữu.8 """9 return (self.ownerToOperators[_owner])[_operator]Hiện tất cảHàm này kiểm tra xem _operator có được phép quản lý tất cả các token của _owner trong hợp đồng này hay không.
Bởi vì có thể có nhiều người vận hành, đây là một HashMap hai cấp.
Chuyển các Hàm Trợ giúp
Các hàm này thực hiện các hoạt động là một phần của việc chuyển hoặc quản lý token.
12### TRỢ GIÚP HÀM CHUYỂN ###34@view5@internalTrang trí này, @internal, có nghĩa là hàm chỉ có thể được truy cập từ các hàm khác trong cùng một
hợp đồng. Theo quy ước, tên các hàm này cũng bắt đầu bằng dấu gạch dưới (_).
1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:2 """3 @dev Trả về liệu người chi tiêu đã cho có thể chuyển một id token đã cho hay không4 @param spender địa chỉ của người chi tiêu để truy vấn5 @param tokenId uint256 ID của token sẽ được chuyển6 @return bool liệu msg.sender có được chấp thuận cho id token đã cho hay không,7 là một người vận hành của chủ sở hữu, hoặc là chủ sở hữu của 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 spenderIsApprovedForAllHiện tất cảCó ba cách để một địa chỉ có thể được phép chuyển một token:
- Địa chỉ là chủ sở hữu của token
- Địa chỉ được chấp thuận để chi tiêu token đó
- Địa chỉ là người vận hành cho chủ sở hữu token
Hàm trên có thể là một chế độ xem vì nó không thay đổi trạng thái. Để giảm chi phí vận hành, bất kỳ hàm nào có thể là một chế độ xem nên là một chế độ xem.
1@internal2def _addTokenTo(_to: address, _tokenId: uint256):3 """4 @dev Thêm một NFT vào một địa chỉ đã cho5 Thông báo lỗi nếu `_tokenId` được sở hữu bởi ai đó.6 """7 # Thông báo lỗi nếu `_tokenId` được sở hữu bởi ai đó8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS9 # Thay đổi chủ sở hữu10 self.idToOwner[_tokenId] = _to11 # Thay đổi theo dõi số lượng12 self.ownerToNFTokenCount[_to] += 1131415@internal16def _removeTokenFrom(_from: address, _tokenId: uint256):17 """18 @dev Xóa một NFT khỏi một địa chỉ đã cho19 Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại.20 """21 # Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại22 assert self.idToOwner[_tokenId] == _from23 # Thay đổi chủ sở hữu24 self.idToOwner[_tokenId] = ZERO_ADDRESS25 # Thay đổi theo dõi số lượng26 self.ownerToNFTokenCount[_from] -= 1Hiện tất cảKhi có vấn đề với việc chuyển, chúng tôi sẽ hoàn nguyên cuộc gọi.
1@internal2def _clearApproval(_owner: address, _tokenId: uint256):3 """4 @dev Xóa sự phê duyệt của một địa chỉ đã cho5 Thông báo lỗi nếu `_owner` không phải là chủ sở hữu hiện tại.6 """7 # Thông báo lỗi nếu `_owner` không phải là chủ sở hữu hiện tại8 assert self.idToOwner[_tokenId] == _owner9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:10 # Đặt lại các phê duyệt11 self.idToApprovals[_tokenId] = ZERO_ADDRESSHiện tất cảChỉ thay đổi giá trị nếu cần thiết. Các biến trạng thái nằm trong bộ nhớ. Việc ghi vào bộ nhớ là một trong những hoạt động tốn kém nhất mà EVM (Máy ảo Ethereum) thực hiện (về mặt gas). Do đó, đó là một ý tưởng tốt để giảm thiểu nó, ngay cả việc viết giá trị hiện có cũng có chi phí cao.
1@internal2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):3 """4 @dev Thực hiện chuyển một NFT.5 Thông báo lỗi trừ khi `msg.sender` là chủ sở hữu hiện tại, một người vận hành được ủy quyền, hoặc địa chỉ được phê duyệt6 cho NFT này. (LƯU Ý: `msg.sender` không được phép trong hàm riêng tư nên truyền `_sender`.)7 Thông báo lỗi nếu `_to` là địa chỉ không.8 Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại.9 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.10 """Hiện tất cảChúng tôi có hàm nội bộ này vì có hai cách để chuyển token (thông thường và an toàn), nhưng chúng tôi chỉ muốn có một vị trí duy nhất trong mã nơi chúng tôi thực hiện nó để việc kiểm tra dễ dàng hơn.
1 # Kiểm tra các yêu cầu2 assert self._isApprovedOrOwner(_sender, _tokenId)3 # Thông báo lỗi nếu `_to` là địa chỉ không4 assert _to != ZERO_ADDRESS5 # Xóa phê duyệt. Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại6 self._clearApproval(_from, _tokenId)7 # Xóa NFT. Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ8 self._removeTokenFrom(_from, _tokenId)9 # Thêm NFT10 self._addTokenTo(_to, _tokenId)11 # Ghi lại việc chuyển12 log Transfer(_from, _to, _tokenId)Hiện tất cảĐể phát ra một sự kiện trong Vyper, bạn sử dụng một câu lệnh log (xem ở đây để biết thêm chi tiết (opens in a new tab)).
Hàm Chuyển
12### HÀM CHUYỂN ###34@external5def transferFrom(_from: address, _to: address, _tokenId: uint256):6 """7 @dev Thông báo lỗi trừ khi `msg.sender` là chủ sở hữu hiện tại, một người vận hành được ủy quyền, hoặc địa chỉ được phê duyệt8 cho NFT này.9 Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại.10 Thông báo lỗi nếu `_to` là địa chỉ không.11 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.12 @notice Người gọi chịu trách nhiệm xác nhận rằng `_to` có khả năng nhận NFT hoặc nếu không13 chúng có thể bị mất vĩnh viễn.14 @param _from Chủ sở hữu hiện tại của NFT.15 @param _to Chủ sở hữu mới.16 @param _tokenId NFT sẽ chuyển.17 """18 self._transferFrom(_from, _to, _tokenId, msg.sender)Hiện tất cảHàm này cho phép bạn chuyển đến một địa chỉ tùy ý. Trừ khi địa chỉ là người dùng, hoặc một hợp đồng biết cách chuyển token, bất kỳ token nào bạn chuyển sẽ bị kẹt trong địa chỉ đó và vô dụng.
1@external2def safeTransferFrom(3 _from: address,4 _to: address,5 _tokenId: uint256,6 _data: Bytes[1024]=b""7 ):8 """9 @dev Chuyển quyền sở hữu của một NFT từ một địa chỉ sang địa chỉ khác.10 Thông báo lỗi trừ khi `msg.sender` là chủ sở hữu hiện tại, một người vận hành được ủy quyền, hoặc11 địa chỉ được phê duyệt cho NFT này.12 Thông báo lỗi nếu `_from` không phải là chủ sở hữu hiện tại.13 Thông báo lỗi nếu `_to` là địa chỉ không.14 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.15 Nếu `_to` là một hợp đồng thông minh, nó sẽ gọi `onERC721Received` trên `_to` và thông báo lỗi nếu16 giá trị trả về không phải là `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.17 LƯU Ý: bytes4 được biểu thị bằng bytes32 với phần đệm18 @param _from Chủ sở hữu hiện tại của NFT.19 @param _to Chủ sở hữu mới.20 @param _tokenId NFT sẽ chuyển.21 @param _data Dữ liệu bổ sung không có định dạng được chỉ định, được gửi trong cuộc gọi đến `_to`.22 """23 self._transferFrom(_from, _to, _tokenId, msg.sender)Hiện tất cảViệc chuyển trước là ổn vì nếu có vấn đề, chúng tôi sẽ hoàn nguyên, do đó mọi thứ được thực hiện trong cuộc gọi sẽ bị hủy.
1 if _to.is_contract: # kiểm tra xem `_to` có phải là địa chỉ hợp đồng khôngĐầu tiên, hãy kiểm tra xem địa chỉ có phải là hợp đồng không (nếu nó có mã). Nếu không, giả sử đó là một địa chỉ người dùng
và người dùng sẽ có thể sử dụng token hoặc chuyển nó. Nhưng đừng để nó ru bạn vào
một cảm giác an toàn giả tạo. Bạn có thể mất token, ngay cả với safeTransferFrom, nếu bạn chuyển
chúng đến một địa chỉ mà không ai biết khóa riêng tư.
1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)Gọi hợp đồng đích để xem liệu nó có thể nhận các token ERC-721 hay không.
1 # Thông báo lỗi nếu đích chuyển là một hợp đồng không triển khai 'onERC721Received'2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)Nếu đích là một hợp đồng, nhưng là hợp đồng không chấp nhận các token ERC-721 (hoặc đã quyết định không chấp nhận việc chuyển cụ thể này), hãy hoàn nguyên.
1@external2def approve(_approved: address, _tokenId: uint256):3 """4 @dev Đặt hoặc xác nhận lại địa chỉ được phê duyệt cho một NFT. Địa chỉ không cho biết không có địa chỉ được phê duyệt nào.5 Thông báo lỗi trừ khi `msg.sender` là chủ sở hữu NFT hiện tại, hoặc một người vận hành được ủy quyền của chủ sở hữu hiện tại.6 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ. (LƯU Ý: Điều này không được viết trong EIP)7 Thông báo lỗi nếu `_approved` là chủ sở hữu hiện tại. (LƯU Ý: Điều này không được viết trong EIP)8 @param _approved Địa chỉ sẽ được phê duyệt cho ID NFT đã cho.9 @param _tokenId ID của token sẽ được phê duyệt.10 """11 owner: address = self.idToOwner[_tokenId]12 # Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ13 assert owner != ZERO_ADDRESS14 # Thông báo lỗi nếu `_approved` là chủ sở hữu hiện tại15 assert _approved != ownerHiện tất cảTheo quy ước, nếu bạn không muốn có người phê duyệt, bạn hãy chỉ định địa chỉ không, không phải chính bạn.
1 # Kiểm tra các yêu cầu2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]4 assert (senderIsOwner or senderIsApprovedForAll)Để đặt một sự chấp thuận, bạn có thể là chủ sở hữu, hoặc một người vận hành được chủ sở hữu ủy quyền.
1 # Đặt sự phê duyệt2 self.idToApprovals[_tokenId] = _approved3 log Approval(owner, _approved, _tokenId)456@external7def setApprovalForAll(_operator: address, _approved: bool):8 """9 @dev Kích hoạt hoặc vô hiệu hóa sự phê duyệt cho một bên thứ ba ("người vận hành") để quản lý tất cả10 tài sản của `msg.sender`. Nó cũng phát ra sự kiện ApprovalForAll.11 Thông báo lỗi nếu `_operator` là `msg.sender`. (LƯU Ý: Điều này không được viết trong EIP)12 @notice Điều này hoạt động ngay cả khi người gửi không sở hữu bất kỳ token nào vào thời điểm đó.13 @param _operator Địa chỉ để thêm vào tập hợp các người vận hành được ủy quyền.14 @param _approved True nếu người vận hành được phê duyệt, false để thu hồi phê duyệt.15 """16 # Thông báo lỗi nếu `_operator` là `msg.sender`17 assert _operator != msg.sender18 self.ownerToOperators[msg.sender][_operator] = _approved19 log ApprovalForAll(msg.sender, _operator, _approved)Hiện tất cảĐúc Token mới và Hủy Token hiện có
Tài khoản tạo hợp đồng là người đúc, người dùng cấp cao được ủy quyền để đúc
các NFT mới. Tuy nhiên, ngay cả nó cũng không được phép đốt các token hiện có. Chỉ chủ sở hữu, hoặc một thực thể
được chủ sở hữu ủy quyền, mới có thể làm điều đó.
1### CÁC HÀM ĐÚC & ĐỐT ###23@external4def mint(_to: address, _tokenId: uint256) -> bool:Hàm này luôn trả về True, vì nếu hoạt động thất bại, nó sẽ được hoàn nguyên.
1 """2 @dev Hàm để đúc token3 Thông báo lỗi nếu `msg.sender` không phải là người đúc.4 Thông báo lỗi nếu `_to` là địa chỉ không.5 Thông báo lỗi nếu `_tokenId` được sở hữu bởi ai đó.6 @param _to Địa chỉ sẽ nhận được các token được đúc.7 @param _tokenId ID token để đúc.8 @return Một giá trị boolean cho biết hoạt động có thành công hay không.9 """10 # Thông báo lỗi nếu `msg.sender` không phải là người đúc11 assert msg.sender == self.minterHiện tất cảChỉ người đúc (tài khoản đã tạo hợp đồng ERC-721) mới có thể đúc các token mới. Điều này có thể là một vấn đề trong tương lai nếu chúng ta muốn thay đổi danh tính của người đúc. Trong một hợp đồng sản xuất, bạn có thể muốn có một hàm cho phép người đúc chuyển đặc quyền đúc cho người khác.
1 # Thông báo lỗi nếu `_to` là địa chỉ không2 assert _to != ZERO_ADDRESS3 # Thêm NFT. Thông báo lỗi nếu `_tokenId` được sở hữu bởi ai đó4 self._addTokenTo(_to, _tokenId)5 log Transfer(ZERO_ADDRESS, _to, _tokenId)6 return TrueTheo quy ước, việc đúc các token mới được tính là một lần chuyển từ địa chỉ không.
12@external3def burn(_tokenId: uint256):4 """5 @dev Đốt một token ERC721 cụ thể.6 Thông báo lỗi trừ khi `msg.sender` là chủ sở hữu hiện tại, một người vận hành được ủy quyền, hoặc địa chỉ được phê duyệt7 cho NFT này.8 Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ.9 @param _tokenId uint256 id của token ERC721 sẽ được đốt.10 """11 # Kiểm tra các yêu cầu12 assert self._isApprovedOrOwner(msg.sender, _tokenId)13 owner: address = self.idToOwner[_tokenId]14 # Thông báo lỗi nếu `_tokenId` không phải là một NFT hợp lệ15 assert owner != ZERO_ADDRESS16 self._clearApproval(owner, _tokenId)17 self._removeTokenFrom(owner, _tokenId)18 log Transfer(owner, ZERO_ADDRESS, _tokenId)Hiện tất cảBất kỳ ai được phép chuyển một token đều được phép đốt nó. Mặc dù việc đốt có vẻ tương đương với việc chuyển đến địa chỉ không, địa chỉ không thực sự nhận được token. Điều này cho phép chúng ta giải phóng tất cả bộ nhớ đã được sử dụng cho token, điều này có thể làm giảm chi phí gas của giao dịch.
Sử dụng Hợp đồng này
Trái ngược với Solidity, Vyper không có tính kế thừa. Đây là một lựa chọn thiết kế có chủ ý để làm cho mã rõ ràng hơn và do đó dễ bảo mật hơn. Vì vậy, để tạo hợp đồng Vyper ERC-721 của riêng bạn, bạn hãy lấy hợp đồng này và sửa đổi nó để triển khai logic kinh doanh mà bạn muốn.
Kết luận
Để xem lại, đây là một số ý tưởng quan trọng nhất trong hợp đồng này:
- Để nhận các token ERC-721 bằng một lần chuyển an toàn, các hợp đồng phải triển khai giao diện
ERC721Receiver. - Ngay cả khi bạn sử dụng chuyển an toàn, các token vẫn có thể bị kẹt nếu bạn gửi chúng đến một địa chỉ có khóa riêng tư không xác định.
- Khi có vấn đề với một hoạt động, tốt hơn hết là
hoàn nguyêncuộc gọi, thay vì chỉ trả về một giá trị thất bại. - Các token ERC-721 tồn tại khi chúng có chủ sở hữu.
- Có ba cách để được ủy quyền chuyển một NFT. Bạn có thể là chủ sở hữu, được chấp thuận cho một token cụ thể, hoặc là người vận hành cho tất cả các token của chủ sở hữu.
- Các sự kiện trong quá khứ chỉ hiển thị bên ngoài chuỗi khối. Mã chạy bên trong chuỗi khối không thể xem chúng.
Bây giờ hãy đi và triển khai các hợp đồng Vyper an toàn.
Xem thêm công việc của tôi tại đây (opens in a new tab).
Lần cập nhật trang lần cuối: 22 tháng 8, 2025