جولة تفصيلية في عقد ERC-721 بلغة Vyper
مقدمة
يُستخدم معيار 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)، وهي لغة عقود تشبه لغة Python مصممة لجعل كتابة التعليمات البرمجية غير الآمنة أكثر صعوبة مما هي عليه في لغة Solidity.
العقد
# @dev تنفيذ معيار الرمز المميز غير القابل للاستبدال ERC-721.
# @author Ryuya Nakamura (@nrryuya)
# معدل من: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
تبدأ التعليقات في لغة Vyper، كما هو الحال في لغة Python، بعلامة التجزئة (#) وتستمر حتى نهاية السطر. تُستخدم التعليقات التي تتضمن
@<keyword> بواسطة NatSpec (opens in a new tab) لإنتاج وثائق قابلة للقراءة من قبل البشر.
from vyper.interfaces import ERC721
implements: ERC721
واجهة ERC-721 مدمجة في لغة Vyper. يمكنك رؤية تعريف التعليمات البرمجية هنا (opens in a new tab). تعريف الواجهة مكتوب بلغة Python، بدلاً من Vyper، لأن الواجهات لا تُستخدم فقط داخل سلسلة الكتل، ولكن أيضًا عند إرسال معاملة إلى سلسلة الكتل من عميل خارجي، والذي قد يكون مكتوبًا بلغة Python.
يستورد السطر الأول الواجهة، ويحدد السطر الثاني أننا نقوم بتنفيذها هنا.
واجهة ERC721Receiver
# واجهة العقد التي يتم استدعاؤها بواسطة safeTransferFrom()
interface ERC721Receiver:
def onERC721Received(
يدعم ERC-721 نوعين من التحويل:
transferFrom، والذي يتيح للمرسل تحديد أي عنوان وجهة ويضع مسؤولية التحويل على المرسل. هذا يعني أنه يمكنك التحويل إلى عنوان غير صالح، وفي هذه الحالة يضيع الرمز المميز غير القابل للاستبدال (NFT) إلى الأبد.safeTransferFrom، والذي يتحقق مما إذا كان عنوان الوجهة عبارة عن عقد. إذا كان الأمر كذلك، يسأل عقد ERC-721 العقد المستلم عما إذا كان يريد استلام الرمز المميز غير القابل للاستبدال (NFT).
للرد على طلبات safeTransferFrom، يجب أن ينفذ العقد المستلم ERC721Receiver.
_operator: address,
_from: address,
العنوان _from هو المالك الحالي للرمز المميز. العنوان _operator هو العنوان الذي
طلب التحويل (قد لا يكون هذان العنوانان متطابقين، بسبب السماحيات).
_tokenId: uint256,
معرفات الرموز المميزة ERC-721 هي 256 bits. عادةً ما يتم إنشاؤها عن طريق عملية التجزئة لوصف ما يمثله الرمز المميز.
_data: Bytes[1024]
يمكن أن يحتوي الطلب على ما يصل إلى 1024 bytes من بيانات المستخدم.
) -> bytes32: view
لمنع الحالات التي يقبل فيها العقد تحويلاً عن طريق الخطأ، فإن القيمة المرجعة ليست قيمة منطقية (boolean)، بل 256 bits بقيمة محددة.
هذه الدالة هي view، مما يعني أنه يمكنها قراءة حالة سلسلة الكتل، ولكن لا يمكنها تعديلها.
أحداث
يتم إصدار الأحداث لإعلام المستخدمين والخوادم خارج سلسلة الكتل بالأحداث. لاحظ أن محتوى الأحداث غير متاح للعقود الموجودة على سلسلة الكتل.
# @dev يُصدر عند تغير ملكية أي رمز مميز غير قابل للاستبدال (NFT) بأي آلية. يُصدر هذا الحدث عندما يتم
# إنشاء الرموز المميزة غير القابلة للاستبدال (`from` == 0) وتدميرها (`to` == 0). استثناء: أثناء إنشاء العقد، يمكن
# إنشاء وتعيين أي عدد من الرموز المميزة غير القابلة للاستبدال دون إصدار تحويل (Transfer). في وقت أي
# تحويل، يتم إعادة تعيين العنوان المعتمد لذلك الرمز المميز غير القابل للاستبدال (إن وجد) إلى لا شيء.
# @param _from مرسل الرمز المميز غير القابل للاستبدال (إذا كان العنوان هو العنوان الصفري فهذا يشير إلى إنشاء رمز مميز).
# @param _to مستقبل الرمز المميز غير القابل للاستبدال (إذا كان العنوان هو العنوان الصفري فهذا يشير إلى تدمير الرمز المميز).
# @param _tokenId الرمز المميز غير القابل للاستبدال الذي تم تحويله.
event Transfer:
sender: indexed(address)
receiver: indexed(address)
tokenId: indexed(uint256)
هذا مشابه لحدث التحويل في ERC-20، باستثناء أننا نبلغ عن tokenId بدلاً من مبلغ.
لا أحد يمتلك العنوان الصفري، لذلك حسب العرف نستخدمه للإبلاغ عن إنشاء وتدمير الرموز المميزة.
# @dev يُصدر هذا عندما يتم تغيير العنوان المعتمد لرمز مميز غير قابل للاستبدال أو إعادة تأكيده. يشير العنوان الصفري
# إلى عدم وجود عنوان معتمد. عندما يُصدر حدث تحويل (Transfer)، يشير هذا أيضًا
# إلى أن العنوان المعتمد لذلك الرمز المميز غير القابل للاستبدال (إن وجد) يتم إعادة تعيينه إلى لا شيء.
# @param _owner مالك الرمز المميز غير القابل للاستبدال.
# @param _approved العنوان الذي نقوم باعتماده.
# @param _tokenId الرمز المميز غير القابل للاستبدال الذي نقوم باعتماده.
event Approval:
owner: indexed(address)
approved: indexed(address)
tokenId: indexed(uint256)
الموافقة في ERC-721 مشابهة للسماحية في ERC-20. يُسمح لعنوان محدد بتحويل رمز مميز محدد. يوفر هذا آلية للعقود للاستجابة عندما تقبل رمزًا مميزًا. لا يمكن للعقود الاستماع إلى الأحداث، لذلك إذا قمت فقط بتحويل الرمز المميز إليها، فلن "تعرف" بذلك. بهذه الطريقة، يقدم المالك أولاً موافقة ثم يرسل طلبًا إلى العقد: "لقد وافقت لك على تحويل الرمز المميز X، يرجى القيام بـ ...".
هذا خيار تصميمي لجعل معيار ERC-721 مشابهًا لمعيار ERC-20. نظرًا لأن الرموز المميزة ERC-721 غير قابلة للاستبدال، يمكن للعقد أيضًا تحديد أنه حصل على رمز مميز محدد من خلال النظر في ملكية الرمز المميز.
# @dev يُصدر هذا عندما يتم تمكين أو تعطيل مشغل لمالك. يمكن للمشغل إدارة
# جميع الرموز المميزة غير القابلة للاستبدال الخاصة بالمالك.
# @param _owner مالك الرمز المميز غير القابل للاستبدال.
# @param _operator العنوان الذي نقوم بتعيين حقوق المشغل له.
# @param _approved حالة حقوق المشغل (صحيح إذا تم منح حقوق المشغل وخاطئ إذا
# تم إلغاؤها).
event ApprovalForAll:
owner: indexed(address)
operator: indexed(address)
approved: bool
من المفيد أحيانًا أن يكون لديك مشغل (operator) يمكنه إدارة جميع الرموز المميزة لحساب من نوع معين (تلك التي يديرها عقد محدد)، على غرار التوكيل الرسمي. على سبيل المثال، قد أرغب في إعطاء مثل هذه الصلاحية لعقد يتحقق مما إذا كنت لم أتصل به لمدة ستة أشهر، وإذا كان الأمر كذلك، يوزع أصولي على ورثتي (إذا طلب أحدهم ذلك، لا يمكن للعقود أن تفعل أي شيء دون أن يتم استدعاؤها بواسطة معاملة). في ERC-20 يمكننا فقط إعطاء سماحية عالية لعقد الميراث، ولكن هذا لا ينجح مع ERC-721 لأن الرموز المميزة غير قابلة للاستبدال. هذا هو المعادل لذلك.
تخبرنا القيمة approved ما إذا كان الحدث مخصصًا لموافقة، أو لسحب موافقة.
متغيرات الحالة
تحتوي هذه المتغيرات على الحالة الحالية للرموز المميزة: أي منها متاح ومن يملكها. معظم هذه
المتغيرات عبارة عن كائنات HashMap، وهي تعيينات أحادية الاتجاه موجودة بين نوعين (opens in a new tab).
# @dev تعيين من معرف الرمز المميز غير القابل للاستبدال إلى العنوان الذي يملكه.
idToOwner: HashMap[uint256, address]
# @dev تعيين من معرف الرمز المميز غير القابل للاستبدال إلى العنوان المعتمد.
idToApprovals: HashMap[uint256, address]
يتم تمثيل هويات المستخدمين والعقود في إيثيريوم بواسطة عناوين بحجم 160-bit. يقوم هذان المتغيران بالتعيين من معرفات الرموز المميزة إلى مالكيها والأشخاص المعتمدين لتحويلها (بحد أقصى واحد لكل منهما). في إيثيريوم، تكون البيانات غير المهيأة دائمًا صفرًا، لذلك إذا لم يكن هناك مالك أو محول معتمد، فإن القيمة لذلك الرمز المميز هي صفر.
# @dev تعيين من عنوان المالك إلى عدد رموزه المميزة.
ownerToNFTokenCount: HashMap[address, uint256]
يحتفظ هذا المتغير بعدد الرموز المميزة لكل مالك. لا يوجد تعيين من المالكين إلى الرموز المميزة، لذا
فإن الطريقة الوحيدة لتحديد الرموز المميزة التي يمتلكها مالك معين هي الرجوع إلى سجل أحداث سلسلة الكتل
ورؤية أحداث Transfer المناسبة. يمكننا استخدام هذا المتغير لمعرفة متى يكون لدينا جميع الرموز المميزة غير القابلة للاستبدال (NFTs) ولا
نحتاج إلى البحث أكثر في الماضي.
لاحظ أن هذه الخوارزمية تعمل فقط مع واجهات المستخدم والخوادم الخارجية. لا يمكن للتعليمات البرمجية التي تعمل على سلسلة الكتل نفسها قراءة الأحداث الماضية.
# @dev تعيين من عنوان المالك إلى تعيين عناوين المشغلين.
ownerToOperators: HashMap[address, HashMap[address, bool]]
قد يكون للحساب أكثر من مشغل واحد. لا يكفي استخدام HashMap بسيط
لتتبعهم، لأن كل مفتاح يؤدي إلى قيمة واحدة. بدلاً من ذلك، يمكنك استخدام
HashMap[address, bool] كقيمة. افتراضيًا، تكون القيمة لكل عنوان هي False، مما يعني أنه
ليس مشغلاً. يمكنك تعيين القيم إلى True حسب الحاجة.
# @dev عنوان الساك، الذي يمكنه سك رمز مميز
minter: address
يجب إنشاء رموز مميزة جديدة بطريقة ما. في هذا العقد، هناك كيان واحد فقط يُسمح له بالقيام بذلك، وهو
minter. من المرجح أن يكون هذا كافيًا للعبة، على سبيل المثال. لأغراض أخرى، قد يكون من الضروري
إنشاء منطق أعمال أكثر تعقيدًا.
# @dev تعيين معرف الواجهة إلى قيمة منطقية حول ما إذا كانت مدعومة أم لا
supportedInterfaces: HashMap[bytes32, bool]
# @dev معرف واجهة ERC-165 لـ ERC-165
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
# @dev معرف واجهة ERC-165 لـ ERC-721
ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
يحدد ERC-165 (opens in a new tab) آلية للعقد للكشف عن كيفية تواصل التطبيقات معه، وإلى أي معايير ERC يتوافق. في هذه الحالة، يتوافق العقد مع ERC-165 و ERC-721.
الدوال
هذه هي الدوال التي تنفذ ERC-721 فعليًا.
المُنشئ
@external
def __init__():
في لغة Vyper، كما هو الحال في لغة Python، تُسمى دالة المُنشئ __init__.
"""
@dev مُنشئ العقد.
"""
في لغة Python، وفي لغة Vyper، يمكنك أيضًا إنشاء تعليق عن طريق تحديد سلسلة نصية متعددة الأسطر (والتي تبدأ وتنتهي
بـ """)، وعدم استخدامها بأي شكل من الأشكال. يمكن أن تتضمن هذه التعليقات أيضًا
NatSpec (opens in a new tab).
self.supportedInterfaces[ERC165_INTERFACE_ID] = True
self.supportedInterfaces[ERC721_INTERFACE_ID] = True
self.minter = msg.sender
للوصول إلى متغيرات الحالة، تستخدم self.<variable name> (مرة أخرى، كما هو الحال في لغة Python).
دوال العرض (View Functions)
هذه هي الدوال التي لا تعدل حالة سلسلة الكتل، وبالتالي يمكن تنفيذها مجانًا إذا تم استدعاؤها خارجيًا. إذا تم استدعاء دوال العرض بواسطة عقد، فلا يزال يتعين تنفيذها على كل عقدة وبالتالي تكلف غاز.
@view
@external
تُسمى هذه الكلمات الرئيسية التي تسبق تعريف الدالة والتي تبدأ بعلامة @ (@) المُزخرفات (decorations). وهي
تحدد الظروف التي يمكن فيها استدعاء الدالة.
- يحدد
@viewأن هذه الدالة هي دالة عرض (view). - يحدد
@externalأنه يمكن استدعاء هذه الدالة المعينة بواسطة المعاملات وبواسطة العقود الأخرى.
def supportsInterface(_interfaceID: bytes32) -> bool:
على النقيض من لغة Python، فإن لغة Vyper هي لغة ذات كتابة ثابتة (static typed language) (opens in a new tab).
لا يمكنك الإعلان عن متغير، أو معلمة دالة، دون تحديد نوع البيانات (opens in a new tab). في هذه الحالة، معلمة الإدخال هي bytes32، وهي قيمة بحجم 256-bit
(256 bits هو حجم الكلمة الأصلي لـ آلة إيثيريوم الافتراضية). المخرجات عبارة عن قيمة منطقية (boolean). حسب العرف، تبدأ أسماء معلمات الدالة بشرطة سفلية (_).
"""
@dev تم تحديد تعريف الواجهة في ERC-165.
@param _interfaceID معرف الواجهة
"""
return self.supportedInterfaces[_interfaceID]
إرجاع القيمة من خريطة التجزئة (HashMap) self.supportedInterfaces، والتي تم تعيينها في المُنشئ (__init__).
### دوال العرض ###
هذه هي دوال العرض التي تجعل المعلومات حول الرموز المميزة متاحة للمستخدمين والعقود الأخرى.
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev يُرجع عدد الرموز المميزة غير القابلة للاستبدال المملوكة لـ `_owner`.
يتراجع إذا كان `_owner` هو العنوان الصفري. تعتبر الرموز المميزة غير القابلة للاستبدال المعينة إلى العنوان الصفري غير صالحة.
@param _owner العنوان المراد الاستعلام عن رصيده.
"""
assert _owner != ZERO_ADDRESS
هذا السطر يؤكد (asserts) (opens in a new tab) أن _owner ليس
صفرًا. إذا كان كذلك، فهناك خطأ ويتم التراجع عن العملية.
return self.ownerToNFTokenCount[_owner]
@view
@external
def ownerOf(_tokenId: uint256) -> address:
"""
@dev يُرجع عنوان مالك الرمز المميز غير القابل للاستبدال.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
@param _tokenId معرف الرمز المميز غير القابل للاستبدال.
"""
owner: address = self.idToOwner[_tokenId]
# يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
assert owner != ZERO_ADDRESS
return owner
في آلة إيثيريوم الافتراضية (EVM)، أي مساحة تخزين لا تحتوي على قيمة مخزنة فيها تكون صفرًا.
إذا لم يكن هناك رمز مميز في _tokenId، فإن قيمة self.idToOwner[_tokenId] تكون صفرًا. في هذه
الحالة، تتراجع الدالة.
@view
@external
def getApproved(_tokenId: uint256) -> address:
"""
@dev الحصول على العنوان المعتمد لرمز مميز غير قابل للاستبدال واحد.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
@param _tokenId معرف الرمز المميز غير القابل للاستبدال المراد الاستعلام عن اعتماده.
"""
# يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
assert self.idToOwner[_tokenId] != ZERO_ADDRESS
return self.idToApprovals[_tokenId]
لاحظ أن getApproved يمكن أن تُرجع صفرًا. إذا كان الرمز المميز صالحًا، فإنها تُرجع self.idToApprovals[_tokenId].
إذا لم يكن هناك معتمد، فإن هذه القيمة تكون صفرًا.
@view
@external
def isApprovedForAll(_owner: address, _operator: address) -> bool:
"""
@dev يتحقق مما إذا كان `_operator` مشغلًا معتمدًا لـ `_owner`.
@param _owner العنوان الذي يملك الرموز المميزة غير القابلة للاستبدال.
@param _operator العنوان الذي يتصرف نيابة عن المالك.
"""
return (self.ownerToOperators[_owner])[_operator]
تتحقق هذه الدالة مما إذا كان يُسمح لـ _operator بإدارة جميع الرموز المميزة الخاصة بـ _owner في هذا العقد.
نظرًا لأنه يمكن أن يكون هناك عدة مشغلين، فهذه خريطة تجزئة (HashMap) ذات مستويين.
دوال المساعدة في التحويل
تنفذ هذه الدوال العمليات التي تعد جزءًا من تحويل أو إدارة الرموز المميزة.
### دوال مساعدة للتحويل ###
@view
@internal
هذا المُزخرف، @internal، يعني أن الدالة لا يمكن الوصول إليها إلا من دوال أخرى داخل نفس
العقد. حسب العرف، تبدأ أسماء هذه الدوال أيضًا بشرطة سفلية (_).
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
"""
@dev يُرجع ما إذا كان المنفق المحدد يمكنه تحويل معرف رمز مميز محدد
@param spender عنوان المنفق المراد الاستعلام عنه
@param tokenId uint256 معرف الرمز المميز المراد تحويله
@return bool ما إذا كان msg.sender معتمدًا لمعرف الرمز المميز المحدد،
أو مشغلًا للمالك، أو مالك الرمز المميز
"""
owner: address = self.idToOwner[_tokenId]
spenderIsOwner: bool = owner == _spender
spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
هناك ثلاث طرق يمكن من خلالها السماح لعنوان بتحويل رمز مميز:
- العنوان هو مالك الرمز المميز
- العنوان معتمد لإنفاق ذلك الرمز المميز
- العنوان هو مشغل لمالك الرمز المميز
يمكن أن تكون الدالة أعلاه دالة عرض (view) لأنها لا تغير الحالة. لتقليل تكاليف التشغيل، أي دالة يمكن أن تكون دالة عرض يجب أن تكون دالة عرض.
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
"""
@dev إضافة رمز مميز غير قابل للاستبدال إلى عنوان محدد
يتراجع إذا كان `_tokenId` مملوكًا لشخص ما.
"""
# يتراجع إذا كان `_tokenId` مملوكًا لشخص ما
assert self.idToOwner[_tokenId] == ZERO_ADDRESS
# تغيير المالك
self.idToOwner[_tokenId] = _to
# تغيير تتبع العدد
self.ownerToNFTokenCount[_to] += 1
@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
"""
@dev إزالة رمز مميز غير قابل للاستبدال من عنوان محدد
يتراجع إذا لم يكن `_from` هو المالك الحالي.
"""
# يتراجع إذا لم يكن `_from` هو المالك الحالي
assert self.idToOwner[_tokenId] == _from
# تغيير المالك
self.idToOwner[_tokenId] = ZERO_ADDRESS
# تغيير تتبع العدد
self.ownerToNFTokenCount[_from] -= 1
عندما تكون هناك مشكلة في التحويل، فإننا نتراجع عن الاستدعاء.
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev مسح اعتماد عنوان محدد
يتراجع إذا لم يكن `_owner` هو المالك الحالي.
"""
# يتراجع إذا لم يكن `_owner` هو المالك الحالي
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
# إعادة تعيين الاعتمادات
self.idToApprovals[_tokenId] = ZERO_ADDRESS
قم بتغيير القيمة فقط إذا لزم الأمر. تعيش متغيرات الحالة في مساحة التخزين. تعد الكتابة في مساحة التخزين واحدة من أكثر العمليات تكلفة التي تقوم بها آلة إيثيريوم الافتراضية (EVM) (من حيث الغاز). لذلك، من الجيد تقليلها إلى الحد الأدنى، فحتى كتابة القيمة الحالية لها تكلفة عالية.
@internal
def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
"""
@dev تنفيذ تحويل رمز مميز غير قابل للاستبدال.
يتراجع ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا مصرحًا له، أو العنوان
المعتمد لهذا الرمز المميز غير القابل للاستبدال. (ملاحظة: `msg.sender` غير مسموح به في دالة خاصة لذا قم بتمرير `_sender`.)
يتراجع إذا كان `_to` هو العنوان الصفري.
يتراجع إذا لم يكن `_from` هو المالك الحالي.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
"""
لدينا هذه الدالة الداخلية لأن هناك طريقتين لتحويل الرموز المميزة (عادية وآمنة)، لكننا نريد موقعًا واحدًا فقط في التعليمات البرمجية حيث نقوم بذلك لجعل التدقيق أسهل.
# التحقق من المتطلبات
assert self._isApprovedOrOwner(_sender, _tokenId)
# يتراجع إذا كان `_to` هو العنوان الصفري
assert _to != ZERO_ADDRESS
# مسح الاعتماد. يتراجع إذا لم يكن `_from` هو المالك الحالي
self._clearApproval(_from, _tokenId)
# إزالة الرمز المميز غير القابل للاستبدال. يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
self._removeTokenFrom(_from, _tokenId)
# إضافة رمز مميز غير قابل للاستبدال
self._addTokenTo(_to, _tokenId)
# تسجيل التحويل
log Transfer(_from, _to, _tokenId)
لإصدار حدث في لغة Vyper، تستخدم عبارة log (انظر هنا لمزيد من التفاصيل (opens in a new tab)).
دوال التحويل
### دوال التحويل ###
@external
def transferFrom(_from: address, _to: address, _tokenId: uint256):
"""
@dev يتراجع ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا مصرحًا له، أو العنوان
المعتمد لهذا الرمز المميز غير القابل للاستبدال.
يتراجع إذا لم يكن `_from` هو المالك الحالي.
يتراجع إذا كان `_to` هو العنوان الصفري.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
@notice المتصل مسؤول عن تأكيد أن `_to` قادر على استقبال الرموز المميزة غير القابلة للاستبدال وإلا
فقد تُفقد بشكل دائم.
@param _from المالك الحالي للرمز المميز غير القابل للاستبدال.
@param _to المالك الجديد.
@param _tokenId الرمز المميز غير القابل للاستبدال المراد تحويله.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
تتيح لك هذه الدالة التحويل إلى عنوان عشوائي. ما لم يكن العنوان مستخدمًا، أو عقدًا يعرف كيفية تحويل الرموز المميزة، فإن أي رمز مميز تقوم بتحويله سيعلق في ذلك العنوان ويصبح عديم الفائدة.
@external
def safeTransferFrom(
_from: address,
_to: address,
_tokenId: uint256,
_data: Bytes[1024]=b""
):
"""
@dev يحول ملكية رمز مميز غير قابل للاستبدال من عنوان إلى عنوان آخر.
يتراجع ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا مصرحًا له، أو
العنوان المعتمد لهذا الرمز المميز غير القابل للاستبدال.
يتراجع إذا لم يكن `_from` هو المالك الحالي.
يتراجع إذا كان `_to` هو العنوان الصفري.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
إذا كان `_to` عقدًا ذكيًا، فإنه يستدعي `onERC721Received` على `_to` ويتراجع إذا
لم تكن القيمة المرجعة هي `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
ملاحظة: يتم تمثيل bytes4 بواسطة bytes32 مع حشو
@param _from المالك الحالي للرمز المميز غير القابل للاستبدال.
@param _to المالك الجديد.
@param _tokenId الرمز المميز غير القابل للاستبدال المراد تحويله.
@param _data بيانات إضافية بدون تنسيق محدد، يتم إرسالها في الاستدعاء إلى `_to`.
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
لا بأس من إجراء التحويل أولاً لأنه إذا كانت هناك مشكلة فسنقوم بالتراجع على أي حال، لذلك سيتم إلغاء كل ما تم إنجازه في الاستدعاء.
if _to.is_contract: # التحقق مما إذا كان `_to` عنوان عقد
تحقق أولاً لمعرفة ما إذا كان العنوان عبارة عن عقد (إذا كان يحتوي على تعليمات برمجية). إذا لم يكن كذلك، فافترض أنه عنوان
مستخدم وسيتمكن المستخدم من استخدام الرمز المميز أو تحويله. لكن لا تدع ذلك يمنحك
شعورًا زائفًا بالأمان. يمكنك فقدان الرموز المميزة، حتى مع safeTransferFrom، إذا قمت بتحويلها
إلى عنوان لا يعرف أحد مفتاحه الخاص.
returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
استدعِ العقد المستهدف لمعرفة ما إذا كان يمكنه استلام الرموز المميزة ERC-721.
# يتراجع إذا كانت وجهة التحويل عقدًا لا ينفذ 'onERC721Received'
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
إذا كانت الوجهة عبارة عن عقد، ولكنه لا يقبل الرموز المميزة ERC-721 (أو قرر عدم قبول هذا التحويل المعين)، فتراجع.
@external
def approve(_approved: address, _tokenId: uint256):
"""
@dev تعيين أو إعادة تأكيد العنوان المعتمد لرمز مميز غير قابل للاستبدال. يشير العنوان الصفري إلى عدم وجود عنوان معتمد.
يتراجع ما لم يكن `msg.sender` هو المالك الحالي للرمز المميز غير القابل للاستبدال، أو مشغلًا مصرحًا له للمالك الحالي.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا. (ملاحظة: هذا غير مكتوب في EIP)
يتراجع إذا كان `_approved` هو المالك الحالي. (ملاحظة: هذا غير مكتوب في EIP)
@param _approved العنوان المراد اعتماده لمعرف الرمز المميز غير القابل للاستبدال المحدد.
@param _tokenId معرف الرمز المميز المراد اعتماده.
"""
owner: address = self.idToOwner[_tokenId]
# يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
assert owner != ZERO_ADDRESS
# يتراجع إذا كان `_approved` هو المالك الحالي
assert _approved != owner
حسب العرف، إذا كنت لا ترغب في وجود معتمد، فإنك تعين العنوان الصفري، وليس نفسك.
# التحقق من المتطلبات
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
لتعيين موافقة، يمكنك إما أن تكون المالك، أو مشغلاً مفوضًا من قبل المالك.
# تعيين الاعتماد
self.idToApprovals[_tokenId] = _approved
log Approval(owner, _approved, _tokenId)
@external
def setApprovalForAll(_operator: address, _approved: bool):
"""
@dev يمكّن أو يعطل الاعتماد لطرف ثالث ("مشغل") لإدارة جميع
أصول `msg.sender`. كما أنه يُصدر حدث ApprovalForAll.
يتراجع إذا كان `_operator` هو `msg.sender`. (ملاحظة: هذا غير مكتوب في EIP)
@notice يعمل هذا حتى إذا لم يكن المرسل يملك أي رموز مميزة في ذلك الوقت.
@param _operator العنوان المراد إضافته إلى مجموعة المشغلين المصرح لهم.
@param _approved صحيح إذا تم اعتماد المشغلين، وخاطئ لإلغاء الاعتماد.
"""
# يتراجع إذا كان `_operator` هو `msg.sender`
assert _operator != msg.sender
self.ownerToOperators[msg.sender][_operator] = _approved
log ApprovalForAll(msg.sender, _operator, _approved)
سك رموز مميزة جديدة وتدمير الرموز الحالية
الحساب الذي أنشأ العقد هو minter، وهو المستخدم المتميز المخول لسك
رموز مميزة غير قابلة للاستبدال (NFTs) جديدة. ومع ذلك، حتى هو غير مسموح له بحرق الرموز المميزة الحالية. فقط المالك، أو كيان
مفوض من قبل المالك، يمكنه القيام بذلك.
### دوال السك والحرق ###
@external
def mint(_to: address, _tokenId: uint256) -> bool:
تُرجع هذه الدالة دائمًا True، لأنه إذا فشلت العملية يتم التراجع عنها.
"""
@dev دالة لسك الرموز المميزة
يتراجع إذا لم يكن `msg.sender` هو الساك.
يتراجع إذا كان `_to` هو العنوان الصفري.
يتراجع إذا كان `_tokenId` مملوكًا لشخص ما.
@param _to العنوان الذي سيتلقى الرموز المميزة المسكوكة.
@param _tokenId معرف الرمز المميز المراد سكه.
@return قيمة منطقية تشير إلى ما إذا كانت العملية ناجحة.
"""
# يتراجع إذا لم يكن `msg.sender` هو الساك
assert msg.sender == self.minter
فقط الساك (الحساب الذي أنشأ عقد ERC-721) يمكنه سك رموز مميزة جديدة. قد يمثل هذا مشكلة في المستقبل إذا أردنا تغيير هوية الساك. في عقد الإنتاج، ربما ترغب في دالة تسمح للساك بنقل امتيازات السك إلى شخص آخر.
# يتراجع إذا كان `_to` هو العنوان الصفري
assert _to != ZERO_ADDRESS
# إضافة رمز مميز غير قابل للاستبدال. يتراجع إذا كان `_tokenId` مملوكًا لشخص ما
self._addTokenTo(_to, _tokenId)
log Transfer(ZERO_ADDRESS, _to, _tokenId)
return True
حسب العرف، تُعد عملية سك الرموز المميزة الجديدة بمثابة تحويل من العنوان الصفري.
@external
def burn(_tokenId: uint256):
"""
@dev يحرق رمز ERC-721 مميز محدد.
يتراجع ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا مصرحًا له، أو العنوان
المعتمد لهذا الرمز المميز غير القابل للاستبدال.
يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
@param _tokenId uint256 معرف رمز ERC-721 المميز المراد حرقه.
"""
# التحقق من المتطلبات
assert self._isApprovedOrOwner(msg.sender, _tokenId)
owner: address = self.idToOwner[_tokenId]
# يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
assert owner != ZERO_ADDRESS
self._clearApproval(owner, _tokenId)
self._removeTokenFrom(owner, _tokenId)
log Transfer(owner, ZERO_ADDRESS, _tokenId)
يُسمح لأي شخص يُسمح له بتحويل رمز مميز بحرقه. في حين أن الحرق يبدو مكافئًا للتحويل إلى العنوان الصفري، فإن العنوان الصفري لا يتلقى الرمز المميز فعليًا. يتيح لنا ذلك تحرير جميع مساحات التخزين التي تم استخدامها للرمز المميز، مما قد يقلل من تكلفة الغاز للمعاملة.
استخدام هذا العقد
على النقيض من لغة Solidity، لا تحتوي لغة Vyper على الوراثة (inheritance). هذا خيار تصميمي متعمد لجعل التعليمات البرمجية أكثر وضوحًا وبالتالي أسهل في التأمين. لذلك لإنشاء عقد ERC-721 الخاص بك بلغة Vyper، فإنك تأخذ هذا العقد (opens in a new tab) وتقوم بتعديله لتنفيذ منطق الأعمال الذي تريده.
الخاتمة
للمراجعة، إليك بعض أهم الأفكار في هذا العقد:
- لاستلام الرموز المميزة ERC-721 بتحويل آمن، يجب أن تنفذ العقود واجهة
ERC721Receiver. - حتى إذا كنت تستخدم التحويل الآمن، فلا يزال من الممكن أن تعلق الرموز المميزة إذا أرسلتها إلى عنوان يكون مفتاحه الخاص غير معروف.
- عندما تكون هناك مشكلة في عملية ما، فمن الجيد
revertالاستدعاء (التراجع عنه)، بدلاً من مجرد إرجاع قيمة فشل. - توجد الرموز المميزة ERC-721 عندما يكون لها مالك.
- هناك ثلاث طرق لتكون مخولاً لتحويل رمز مميز غير قابل للاستبدال (NFT). يمكنك أن تكون المالك، أو أن تكون معتمدًا لرمز مميز محدد، أو أن تكون مشغلاً لجميع الرموز المميزة للمالك.
- الأحداث الماضية مرئية فقط خارج سلسلة الكتل. لا يمكن للتعليمات البرمجية التي تعمل داخل سلسلة الكتل عرضها.
اذهب الآن وقم بتنفيذ عقود Vyper آمنة.