آموزش استاندارد ERC-1155 در سالیدیتی

استاندارد ERC-1155 در سالیدیتی توضیح می دهد که چگونه می توان توکن های قابل تعویض (Fungible) و غیرقابل تعویض (Non-Fungible) را در یک قرارداد هوشمند واحد ایجاد کرد و در کنار هم به کار گرفت. این روش زمانی که چندین نوع توکن وجود دارد، به شکل قابل توجهی در هزینه های استقرار صرفه جویی می کند.

فرض کنید توسعه دهنده یک بازی هستید و قصد دارید NFTها و توکن های مبتنی بر استاندارد ERC-20 را در پلتفرم خود پیاده سازی کنید. این دارایی ها می توانند شامل آیتم هایی مثل کفش، شمشیر، کلاه یا ارز درون بازی باشند.

اگر از استانداردهای ERC-721 و ERC-20 استفاده کنید، باید برای هر مجموعه از NFTها و توکن ها یک قرارداد جداگانه بنویسید. استقرار این تعداد قرارداد هم زمان بر خواهد بود و هم پرهزینه.

اما اگر بتوانید همه این دارایی ها و توکن ها را در یک قرارداد واحد تعریف و مدیریت کنید، توسعه سیستم بسیار ساده تر و بهینه تر خواهد شد. حتی می توانید مکانیزمی طراحی کنید که امکان تایید یا انتقال چندین NFT را به صورت همزمان فراهم کند.

همین نیاز باعث شد سازمان Enjin که در حوزه NFT و بازی فعالیت می کند، اولین پیشنهاد استاندارد چندتوکنی ERC-1155 را در مخزن گیتهاب اتریوم ثبت کند. در تاریخ ۱۷ ژوئن ۲۰۱۸، بنیاد اتریوم این استاندارد را به صورت رسمی پذیرفت.

در واقع ERC-1155 مسیری کارآمد و منعطف برای مدیریت توکن ها در پروژه های برنامه نویسی مبتنی بر بلاکچین فراهم می کند.

ویژگی های کلیدی ERC-1155 در سالیدیتی

مدیریت انواع مختلف توکن: قابل تعویض و غیرقابل تعویض

برای پشتیبانی از چندین نوع توکن (قابل تعویض و غیرقابل تعویض) در یک قرارداد واحد، پیاده سازی استاندارد ERC-1155 باید هر نوع توکن را با استفاده از یک شناسه عددی uint256 منحصربه‌فرد از هم جدا کند. این ساختار به قرارداد اجازه می دهد تا ویژگی های اختصاصی هر توکن مانند میزان عرضه، آدرس URI، نام، نماد و سایر اطلاعات را تعریف کند و اطمینان حاصل شود که تنظیمات هر توکن به صورت مستقل باقی می ماند.

در ادامه مثالی از ساختار شناسه توکن در ERC-1155 آورده شده است:

  • Token ID: 0

  • Token ID: 1

  • Token ID: 2

شناسه های توکن الزامی ندارند که به صورت ترتیبی ایجاد شوند؛ تنها نکته مهم این است که هر شناسه باید منحصربه‌فرد باشد. این استاندارد مشخص نکرده که شناسه ها باید با چه الگویی تولید شوند، به همین دلیل تابع mint به صورت رسمی بخشی از مشخصات ERC-1155 نیست.

تعریف قابلیت تعویض (Fungibility)

در این بخش، تفاوت بین توکن های قابل تعویض و غیرقابل تعویض توضیح داده می شود؛ استاندارد ERC-1155 از هر دو نوع توکن پشتیبانی می کند.

  • توکن های قابل تعویض (Fungible)
    توکن های قابل تعویض، دارایی هایی هستند که با یکدیگر کاملاً یکسان محسوب می شوند، مانند واحدهای یک ارز. در استاندارد ERC-1155، برای تعریف مجموعه ای از این توکن ها کافی است چندین توکن با یک شناسه یکسان تولید (mint) شود.
    زمانی که چند توکن شناسه یکسانی داشته باشند، نام و نماد (symbol) یکسانی نیز خواهند داشت. همین ویژگی باعث می شود که این توکن ها رفتاری مشابه با توکن های استاندارد ERC-20 داشته باشند؛ زیرا دارای واحدهای متعدد، مشابه و قابل جایگزینی هستند. تنها تفاوت این است که برخلاف ERC-20، در ERC-1155 امکان تعریف اعشار برای موجودی توکن ها وجود ندارد و تمام مقادیر به‌صورت عدد صحیح نمایش داده می شوند.
  • توکن های غیرقابل تعویض (Non-Fungible)
    در مقابل، توکن های غیرقابل تعویض یا همان NFTها در ERC-1155، دارایی هایی کاملاً منحصر به فرد هستند که هر کدام با دیگری تفاوت دارند. برای تعریف این نوع توکن، به هر آیتم منحصر به فرد یک شناسه جداگانه (uint256) اختصاص داده می شود.

درک این تفاوت بنیادی، یکی از بخش های مهم در آموزش استاندارد ERC-1155 در سالیدیتی است و کمک می کند توسعه دهندگان بتوانند ساختارهای پیچیده تری برای دارایی های دیجیتال در قراردادهای خود طراحی کنند.

چگونه چند توکن غیرقابل تعویض را در یک قرارداد ERC-1155 قرار دهیم؟

زمانی که چند مجموعه NFT را در یک قرارداد ERC-1155 مدیریت می کنیم، اگر برای هر توکن یک شناسه منحصر به فرد و کاملاً تصادفی اختصاص دهیم، شناسایی اینکه یک شناسه مشخص به کدام مجموعه تعلق دارد دشوار خواهد شد.

برای حل این مشکل، می توان شناسه ها را طوری ساختاربندی کرد که اطلاعات مجموعه و آیتم به صورت ترکیبی در یک شناسه رمزگذاری شود. در این روش، کافی است عدد مربوط به مجموعه و عدد مربوط به آیتم را به هم متصل کنیم تا یک شناسه واحد به دست بیاید.

در این ساختار، شناسه توکن uint256 به دو بخش تقسیم می شود:

  • شناسه مجموعه (collection ID): ۱۲۸ بیت بالایی (بیت های پرارزش تر) از شناسه توکن، نمایانگر یک مجموعه خاص هستند.

  • شناسه آیتم (item ID): ۱۲۸ بیت پایینی (بیت های کم ارزش تر) برای نمایش آیتمی خاص در همان مجموعه استفاده می شوند.

با این روش، می توان به سادگی تشخیص داد که هر شناسه توکن به کدام مجموعه تعلق دارد و آیتم آن شناسه کدام مورد از آن مجموعه است. این سیستم، همه توکن های غیرقابل تعویض را با حفظ یکتایی از یکدیگر متمایز می کند.

در تصویر زیر نیز تقسیم شناسه توکن به دو بخش collection ID (مقادیر X) و item ID (مقادیر Y) نمایش داده شده است:

تصویر، شناسه توکن را نشان می‌دهد که به شناسه مجموعه (مقادیر `X`) و شناسه آیتم (مقادیر `Y`) تقسیم شده است.

برای رمزگذاری اطلاعات مربوط به مجموعه و آیتم در یک شناسه توکن از نوع uint256، می توان از عملیات شیفت بیت و جمع استفاده کرد.

شیفت بیت (Bit-Shifting) در سالیدیتی

شیفت بیت به فرایندی گفته می شود که در آن، بیت های صفر به ابتدا یا انتهای یک دنباله بیت اضافه می شوند. این عملیات باعث می شود بیت های موجود به چپ (<<) یا راست (>>) منتقل شوند.

با استفاده از شیفت بیت، می توان یک عدد ۱۲۸ بیتی را در ۱۲۸ بیت پرارزش تر (بیت های بالایی) از یک عدد ۲۵۶ بیتی قرار داد. به‌طور پیش فرض، اگر یک عدد ۱۲۸ بیتی را به عدد ۲۵۶ بیتی تبدیل کنیم، آن عدد به صورت خودکار در ۱۲۸ بیت کم‌ارزش تر (بخش پایین‌تر عدد) قرار می گیرد.

به عنوان مثال، فرض کنید عدد دهدهی ۲ را به اندازه ۱۲۸ بیت (یا ۱۶ بایت) به چپ شیفت می دهیم. مقدار جدیدی که به دست می آید، یک عدد ۲۵۶ بیتی خواهد بود که عدد ۲ در بیت های پرارزش تر آن قرار گرفته است.

پس از آنکه عدد دهدهی ۲ را به اندازه ۱۲۸ بیت به چپ شیفت کنیم (2 << 128)، مقدار جدید برابر خواهد بود با عدد دهدهی:

680564733841876926926749214863536422912

و معادل هگزادسیمال آن:

0x0000000000000000000000000000000200000000000000000000000000000000

با استفاده از این تکنیک شیفت بیت، می توانیم ۱۲۸ بیت کم‌ارزش تر (بخش پایین تر عدد) را با صفر پر کنیم. از آنجا که شناسه های NFT از نوع uint256 ذخیره می شوند، می توان به سادگی شناسه آیتم را به شناسه مجموعه‌ی شیفت شده اضافه کرد.

فرمول ساده زیر این فرایند را نشان می دهد:

انیمیشن زیر نشان می دهد که عملیات شیفت و جمع چگونه در پشت صحنه انجام می شوند.

فرض کنید یک قرارداد ERC-1155 شامل دو مجموعه متفاوت از توکن های غیرقابل تعویض باشد: مجموعه CoolPhotos با شناسه ۱ و مجموعه RareSkills با شناسه ۲.

اگر باب بخواهد بررسی کند که آیا مالک آیتمی با شناسه itemID = 7 از مجموعه RareSkills هست یا نه، شناسه معتبر توکن برای این بررسی باید ترکیبی از شناسه مجموعه (collectionID = 2) و شناسه آیتم (itemID = 7) باشد.

در این ترکیب، بیت های نارنجی نشان دهنده شناسه مجموعه RareSkills و بیت های سبز بیانگر شناسه آیتم هستند.

نحوه ذخیره و بازیابی موجودی ها در قرارداد ERC-1155

در این مثال، قرارداد ERC-1155 از نگاشت (mapping) تو در تو برای نگهداری موجودی ها استفاده می کند:

باب می تواند تابع balanceOf را با شناسه توکن (2 << 128) + 7 فراخوانی کند تا وضعیت مالکیت خود را بررسی کند:

اگر مقدار bobBalance برابر با ۱ باشد، یعنی باب مالک آیتم با itemID = 7 از مجموعه RareSkills است. نکته بسیار مهم این است که قرارداد باید تضمین کند که عرضه کل (total supply) برای این شناسه توکن از عدد ۱ بیشتر نشود، در غیر این صورت توکن از حالت غیرقابل تعویض خارج شده و به توکن قابل تعویض تبدیل خواهد شد.

بازیابی collectionID و itemID از یک tokenID

در بخش های قبلی، نحوه تولید شناسه توکن با استفاده از عملیات شیفت بیت را بررسی کردیم. اکنون برای استخراج معکوس اطلاعات از شناسه توکن، مراحل زیر انجام می شود:

  • برای به دست آوردن collectionID، شناسه توکن را ۱۲۸ بیت به راست شیفت می دهیم.

  • برای به دست آوردن itemID، شناسه توکن را به ۱۲۸ بیت پایینی تبدیل (cast) می کنیم.

در بخش بعدی، یک مثال کد ارائه می شود که نحوه انجام این عملیات را نشان می دهد:

  • محاسبه شناسه توکن ERC-1155 با داشتن collectionID و itemID

  • استخراج collectionID و itemID از شناسه توکن ERC-1155

تصویر زیر از محیط Remix نحوه تست این توابع را نمایش می دهد:

تصویر کد Remix که هر دو تابع را فراخوانی می‌کند

تکنیک استفاده از شناسه ساختاریافته یکی از روش های رایج برای پیاده سازی چند توکن غیرقابل تعویض در استاندارد ERC-1155 محسوب می شود. دلیل آن هم این است که خود استاندارد ERC-1155 نحوه دقیق ایجاد شناسه ها را مشخص نکرده است و به توسعه دهنده اجازه می دهد این ساختار را با توجه به نیاز پروژه طراحی کند.

اما در کنار این روش، یک پیاده سازی خاص از ERC-1155 با نام ERC-1155D نیز وجود دارد. این نسخه با هدف بهینه سازی مصرف گس در عملیات mint کردن NFTها طراحی شده، به شرطی که قرارداد تنها نیاز به پشتیبانی از یک مجموعه توکن غیرقابل تعویض داشته باشد.

معرفی استاندارد ERC-1155D

ERC-1155D به طور خاص برای توکن های غیرقابل تعویض (مشابه با ERC-721) طراحی شده است، جایی که هر توکن یک شناسه یکتا و یک مالک خاص دارد. این استاندارد کاملاً با ERC-1155 سازگار است و به‌صورت کامل از آن تبعیت می کند.

چه زمانی از ERC-1155D استفاده کنیم؟

اگر قرارداد شما فقط نیاز به یک مجموعه NFT دارد (برخلاف مثال قبلی که در آن مجموعه های CoolPhotos و RareSkills به صورت همزمان وجود داشتند)، و قصد دارید تضمین کنید که هر توکن تنها یک نسخه داشته باشد و فقط یک مالک بتواند آن را در اختیار داشته باشد، استفاده از ERC-1155D انتخاب مناسبی خواهد بود.

در نهایت، تمام توکن ها در قالب یک قرارداد واحد مدیریت می شوند و شناسه ها به صورت uint256 تعریف می شوند. اما این‌که چگونه این شناسه ها بین انواع مختلف توکن ها اختصاص یابند، کاملاً به منطق و هدف آن قرارداد بستگی دارد.

توابع اصلی در استاندارد ERC-1155 در سالیدیتی

توابعی که در این بخش معرفی می شوند، بخشی از رابط رسمی (interface) ERC-1155 هستند و هر قراردادی که قصد داشته باشد از این استاندارد پیروی کند، باید آن ها را پیاده سازی کند. قطعه کدی که از هر تابع نمایش داده می شود، مستقیماً از مستندات استاندارد استخراج شده است.

بازیابی موجودی توکن

  • balanceOf

در استاندارد ERC-721، تابع balanceOf(address _owner) تعداد توکن هایی را که یک آدرس مشخص در کل مجموعه در اختیار دارد، برمی گرداند. مثلاً اگر یک آدرس مالک توکن های ۱، ۵ و ۷ باشد، مقدار برگشتی balanceOf(_owner) عدد ۳ خواهد بود.

اما در ERC-1155، ساختار این تابع متفاوت است. در این استاندارد، تابع balanceOf طوری طراحی شده که فقط موجودی یک شناسه توکن مشخص برای یک آدرس مشخص را بازیابی می کند، نه تعداد کل توکن ها.

در ERC-1155، یک آدرس می تواند از توکن های مختلف، مقادیر متفاوتی در اختیار داشته باشد. برای مثال، ممکن است فقط یک واحد از توکن با شناسه ۱ و بیست واحد از توکن با شناسه ۵ داشته باشد.

اما برخلاف ERC-721، هیچ تابع مستقیمی برای محاسبه مجموع تعداد توکن هایی که یک آدرس در کل قرارداد ERC-1155 دارد وجود ندارد. تابع balanceOf تنها مقدار موجودی یک توکن خاص را بررسی می کند و نه تعداد کل توکن هایی که آن آدرس در اختیار دارد.

  • balanceOfBatch

استاندارد ERC-1155 یک مکانیزم گروهی (batch) برای بررسی موجودی چندین آدرس و چند شناسه توکن به‌صورت همزمان فراهم کرده است. تابع balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) این امکان را فراهم می کند که به‌جای فراخوانی جداگانه تابع balanceOf برای هر آدرس و هر شناسه، همه موارد در یک مرحله بررسی شوند.

در استاندارد ERC-1155، هیچ مکانیزم داخلی برای لیست کردن تمام شناسه های موجود در قرارداد وجود ندارد. به عبارت دیگر، نمی توان از داخل قرارداد پرسید که چه شناسه هایی تاکنون صادر شده اند.

برای به دست آوردن این اطلاعات، باید رویدادهای (logs) ثبت‌شده توسط قرارداد را خارج از زنجیره (off-chain) تجزیه و تحلیل کرد. در بخش های بعدی مقاله، نحوه انجام این کار را به‌صورت عملی توضیح می دهیم.

تایید کلی با استفاده از تابع setApprovalForAll

استاندارد ERC-1155 این امکان را فراهم می کند که یک مالک، آدرس دیگری را به عنوان اپراتور تعیین کند تا بتواند در یک تراکنش، مجوز مدیریت همه توکن های او را—بدون توجه به شناسه آن‌ها—دریافت کند. این کار با استفاده از تابع زیر انجام می شود:

نکته بسیار مهمی که باید به آن توجه داشت این است که این تایید شامل تمام توکن هایی است که کاربر در قرارداد ERC-1155 دارد. عملکرد این تابع را می توان با تعیین حداکثر مجوز در استاندارد ERC-20 یا تابع setApprovalForAll در استاندارد ERC-721 مقایسه کرد.

یعنی پس از تایید اپراتور، او می تواند هر مقدار از هر توکن متعلق به مالک را از طریق قرارداد منتقل کند. بنابراین استفاده از این تابع باید با دقت و درک کامل از پیامدهای آن انجام شود.

انتقال امن (Safe Transfers) در ERC-1155

همانند استاندارد ERC-721، استاندارد ERC-1155 نیز از مکانیزم انتقال امن استفاده می کند تا مطمئن شود گیرنده توکن، صلاحیت دریافت آن را دارد. با این تفاوت که در ERC-1155 فقط انتقال امن پشتیبانی می شود و هیچ نوع دیگری از انتقال مجاز نیست.

  • تابع safeTransferFrom

اگر گیرنده یک حساب معمولی (EOA) باشد، تابع safeTransferFrom بررسی می کند که آدرس مقصد مقدار address(0) نباشد.

اما اگر گیرنده یک قرارداد هوشمند باشد، تابع safeTransferFrom موظف است متد زیر را در قرارداد گیرنده صدا بزند:

این تابع باید مقدار ویژه‌ای به‌نام “مقدار جادویی” برگرداند که برابر است با:
اگر این مقدار به‌درستی برنگردد یا تابع در قرارداد گیرنده وجود نداشته باشد، انتقال لغو خواهد شد و تراکنش revert می شود.

به عبارت دیگر، یک توکن ERC-1155 نمی تواند به قراردادی منتقل شود که یا تابع onERC1155Received را پیاده سازی نکرده باشد، یا این تابع را به‌صورت نادرست پیاده سازی کرده باشد.

  • تابع safeBatchTransferFrom

استاندارد ERC-1155 امکان انتقال چند توکن مختلف را در یک تراکنش فراهم می کند. این قابلیت به مالکان یا اپراتورها اجازه می دهد مجموعه ای از توکن ها را از یک آدرس مبدأ به یک آدرس مقصد به صورت گروهی منتقل کنند.

این انتقال از طریق تابع safeBatchTransferFrom انجام می شود:

انتقال های گروهی را می‌ توان با فراخوانی تابع زیر انجام داد:
این تابع، متد زیر را در قرارداد گیرنده فراخوانی خواهد کرد:
و انتظار دارد که مقدار جادویی زیر بازگردانده شود:

مقایسه SafeTransferFrom و SafeBatchTransferFrom

با استفاده از پیاده‌سازی OpenZeppelin برای استاندارد ERC-1155، تصویر زیر میزان مصرف گس در دو حالت مختلف را مقایسه می‌ کند:

  • حالت اول، فراخوانی تابع safeTransferFrom به‌صورت جداگانه در سه مرتبه

  • حالت دوم، تجمیع هر سه انتقال در یک تراکنش با استفاده از safeBatchTransferFrom

معیار Gas برای SafeTransferFrom و SafeBatchTransferFrom

با استفاده از safeBatchTransferFrom، همان‌طور که در کادر قرمز مشاهده می‌ شود، تنها 132,437 واحد گس مصرف می‌ شود. این مقدار به‌ شکل قابل توجهی کمتر از 189,861 واحد گس است که برای سه فراخوانی جداگانه safeTransferFrom (نمایش داده شده در کادر آبی) مورد نیاز است.

ساختارهای داده اصلی در ERC-1155

پیاده‌سازی‌های ERC-1155 معمولاً از mapping برای نگهداری وضعیت داده‌های اصلی مانند موجودی ها، تاییدیه ها (approvals) و URIها استفاده می‌کنند. برای مثال، یک قرارداد مبتنی بر ERC-1155 ممکن است متغیرهای ذخیره‌سازی زیر را به‌کار گیرد:

در بخش‌های بعدی، هر یک از این ساختارهای داده را بررسی می‌کنیم.

موجودی ها (Balances)

موجودی توکن ها در یک نگاشت تو در تو (nested mapping) ذخیره می‌شوند که دارای دو سطح است:

  • نگاشت بیرونی: کلید آن یک token ID است.

  • نگاشت داخلی: به هر شناسه، یک نگاشت دیگر نسبت داده شده که آدرس مالک را به مقدار موجودی (uint256) مرتبط می‌سازد.

برای برگرداندن موجودی یک حساب خاص برای یک شناسه توکن مشخص، تابع balanceOf در این ساختار به‌صورت زیر عمل می‌کند:

این طراحی اجازه می‌دهد که هر آدرس بتواند مقادیر مختلفی از توکن های مختلف را در اختیار داشته باشد، در حالی که ساختار داده همچنان ساده و بهینه باقی می‌ماند.

تاییدیه ها (Approvals)

مشابه با موجودی ها، تاییدیه ها نیز در یک نگاشت تو در تو ذخیره می شوند، چرا که یک حساب ممکن است به چند اپراتور مختلف اجازه مدیریت توکن های خود را بدهد.

کلید نگاشت بیرونی، آدرس مالک است و به یک نگاشت داخلی اشاره می کند که وضعیت تایید هر اپراتور را نگه می دارد.

در ادامه، یک نمونه پیاده سازی از تابع isApprovedForAll را مشاهده می کنید که وضعیت تایید یک اپراتور را بررسی می کند:

ثبت رویدادها (Logging and Events)

استاندارد ERC-1155 تضمین می کند که می توان سوابق دقیقی از وضعیت فعلی توکن ها را از طریق رویدادهایی که در قرارداد هوشمند ثبت می شوند به دست آورد. چرا که هر عملیات mint، burn یا transfer منجر به ثبت یک رویداد می شود.

در ادامه، لیستی از موقعیت هایی که باید منجر به صدور رویداد شوند آمده است:

  • در صورتی که یک آدرس به اپراتور دیگری مجوز مدیریت تمام توکن های خود را بدهد یا آن را لغو کند، باید رویداد ApprovalForAll صادر شود:
  • هنگامی که توکن ها از یک آدرس به آدرس دیگر منتقل می شوند—شامل mint و burn—باید یکی از دو رویداد زیر صادر شود:
نکته: اگر تابع safeBatchTransferFrom با تنها یک شناسه توکن صدا زده شود، رویداد TransferSingle صادر می شود. در غیر این صورت، رویداد TransferBatch منتشر خواهد شد.

  • اگر URI مرتبط با متادیتای یک توکن خاص تغییر کند، باید رویداد URI منتشر شود:
با ثبت این رویدادها در زمان اجرای توابع مرتبط، می توان در سمت کلاینت یا به‌صورت خارج از زنجیره (off-chain) در زبان هایی مانند JavaScript، اطلاعات مورد نیاز را به‌راحتی استخراج کرد:

  • شناسه‌ های موجود در یک قرارداد ERC-1155

کد زیر با استفاده از کتابخانه ethers.js به یک قرارداد ERC-1155 متصل می‌شود و لیستی از تمام شناسه‌ های توکن (token ID) را که در رویدادهای TransferSingle و TransferBatch منتشر شده‌اند، در یک بازه مشخص از بلاک ها استخراج می‌کند:

  • لیست تمام شناسه‌ های توکن که یک کاربر مالک آن‌هاست

کد زیر تمام شناسه‌ های توکنی را که یک کاربر مالک آن‌هاست لیست می‌ کند. این کار با بررسی رویدادهای TransferSingle و TransferBatch انجام می‌شود که به آدرس مورد نظر ارسال شده یا از آن خارج شده‌اند.

برای دقت بیشتر، مقدار startBlockNumber باید به بلاکی تنظیم شود که قبل از اولین تعامل آن آدرس با قرارداد بوده باشد.

شناسه های منبع یکنواخت (URIs) در ERC-1155

در استاندارد ERC-1155، تنها یک تابع uri تعریف شده است. این استاندارد مشخص نمی‌ کند که آیا تابع uri باید شناسه توکن (token ID) را در نظر بگیرد یا نادیده بگیرد. در نتیجه، نحوه بازگرداندن URI به نحوه پیاده‌سازی قرارداد بستگی دارد.

برای مثال، اگر پیاده‌سازی قرارداد به‌گونه‌ای باشد که از یک URI مشترک برای تمام توکن ها استفاده شود، می‌توان id را نادیده گرفت و فقط مقدار پایه (_uri) را برگرداند. در غیر این صورت، می‌توان شناسه توکن را به همراه URI پایه ترکیب کرد.

مثال پیاده‌سازی URI مشترک برای تمام شناسه‌های توکن

در این پیاده‌سازی، تابع uri همیشه یک مقدار ثابت را بازمی‌گرداند و شناسه توکن را نادیده می‌گیرد.

مثال پیاده‌سازی URI اختصاصی برای هر شناسه توکن

اگر بخواهیم مقدار برگشتی تابع uri را براساس شناسه توکن تغییر دهیم، استفاده از کتابخانه Strings بسیار کاربردی خواهد بود. این کتابخانه به‌صورت داخلی در زبان سالیدیتی وجود ندارد، بلکه بخشی از مجموعه کتابخانه‌های OpenZeppelin است.

در پیاده‌سازی زیر، از این کتابخانه برای تبدیل شناسه توکن (tokenID) از نوع uint256 به یک رشته هگزادسیمال استفاده شده که به‌صورت رشته‌ای در سالیدیتی رمزگذاری شده است.

در کد زیر مثالی از تغییر URI بر اساس شناسه توکن با استفاده از کتابخانه Strings وجود دارد:

تابع uri یک URI منحصر به‌فرد برای هر توکن تولید می‌ کند، به این صورت که شناسه توکن دریافت‌شده را به انتهای URI پایه اضافه می‌ کند.

برای مثال، اگر URI پایه برابر باشد با: https://token-cdn-domain/ و تابع با شناسه توکن 314592 فراخوانی شود (که معادل هگزادسیمال آن 0x4CCE0 است)، خروجی تابع به شکل زیر خواهد بود:

https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json

استاندارد ERC-1155 الزام می‌ کند که در صورت استفاده از الگوی {id} در URI، کلاینت‌ ها موظف‌اند مقدار {id} را با نسخه هگزادسیمال کوچک‌شده، بدون پیشوند 0x و با صفرهای پیشوندی برای رسیدن به طول ۶۴ کاراکتر جایگزین کنند.

فرمت این رشته جایگزین باید فقط شامل حروف و ارقام کوچکتر باشد از مجموعه [0-9a-f] و دقیقاً ۶۴ کاراکتر طول داشته باشد.

این روش جایگزینی شناسه توکن در URI، باعث کاهش قابل توجهی در مصرف حافظه و بهینه‌سازی ذخیره‌سازی در مجموعه‌های بزرگ از توکن ها می‌شود؛ چرا که نیازی نیست برای هر توکن URI جداگانه‌ای ذخیره شود، بلکه فقط با ترکیب شناسه با URI پایه می‌توان به URI نهایی رسید.

ساختار URIها چگونه است

استاندارد مشخص نمی‌ کند که توکن‌ های ERC-1155 حتماً باید دارای متادیتای URI باشند. با این حال، اگر قرارداد پیاده‌سازی ERC-1155 برای یک توکن URI تعریف کرده باشد، آن URI باید به یک فایل JSON اشاره داشته باشد که با ساختار “ERC-1155 Metadata URI JSON Schema” مطابقت دارد.

این URI معمولاً به یک منبع خارج از زنجیره اشاره می‌ کند، مانند یک سرور یا IPFS، جایی که متادیتا در آن ذخیره شده است.

ساختار JSON استاندارد ERC-1155 برای URI متادیتا، همان‌طور که در خود استاندارد آمده، به شکل زیر است:

در ادامه، مثالی از یک فایل JSON برای یک NFT مربوط به خودرو ارائه می‌شود که با ساختار استاندارد بالا کاملاً هم‌راستا است:
فیلد title هدف متادیتا را توصیف می‌ کند، فیلد type فرمت داده‌ ای متادیتا را مشخص می‌ کند، و فیلد properties ویژگی‌ ها یا اطلاعات تکمیلی بیشتری درباره خودرو را تعریف می‌ کند.

فیلد Localization در ساختار JSON مربوط به URI

کلاینت‌هایی که از محلی‌سازی (Localization) پشتیبانی می‌کنند، می‌توانند در صورت وجود فیلد localization در فایل JSON مربوط به ERC-1155، اطلاعات توکن را در چند زبان مختلف نمایش دهند.

ساختار استاندارد متادیتای مربوط به localization به صورت زیر تعریف می‌شود:

در ادامه، یک نمونه فایل JSON برای متادیتای ERC-1155 نمایش داده می‌شود که شامل فیلد localization است و از قابلیت محلی‌سازی پشتیبانی می‌کند:
خصوصیت locales یک آرایه شامل سه عنصر است: en، es و fr که در آن، en به‌عنوان زبان پیش‌فرض تعیین شده است. هر عنصر در این آرایه نمایانگر یک زبان است و دارای فایل متادیتای JSON مخصوص به همان زبان می‌باشد.

es.json:

fr.json:

مشابه با جایگزینی شناسه توکن در URI، اگر آدرس URI شامل رشته {locale} باشد، کلاینت‌ها موظف‌اند این بخش را با یکی از مقادیر موجود در آرایه locales جایگزین کنند. نتیجه این جایگزینی باید به یک فایل JSON حاوی متادیتا به زبان مورد نظر منتهی شود.

مثال برای دریافت متادیتا به زبان فرانسوی

1. تابع uri را با شناسه توکن 314592 فراخوانی کنید تا آدرس URI مربوط به متادیتای آن توکن را به دست آورید.

2. محتوای JSON را از آدرس بالا به‌صورت خارج از زنجیره (off-chain) بخوانید تا به بخش localization در متادیتا دسترسی پیدا کنید.

3. رشته {locale} را در مقدار فیلد uri جایگزین کنید با fr، تا آدرس فایل متادیتای زبان فرانسوی تولید شود.

هنگام کار با متادیتای ناشناخته و دریافت شده از منابع غیرمطمئن، حتماً پیش از پردازش، داده‌ ها را پاک‌سازی (sanitize) کنید. هر فایل JSON که در رابط کاربری نمایش داده می‌ شود، می‌ تواند مسیر حملات XSS (اجرای اسکریپت مخرب در مرورگر) را هموار کند.

نحوه تفسیر متادیتا توسط OpenSea

قراردادهای ERC-1155 توسط پلتفرم OpenSea پشتیبانی می‌ شوند و در این بخش توضیح داده می‌ شود که OpenSea چگونه متادیتای تعریف‌شده در قرارداد ERC-1155 را تفسیر می‌ کند.

یک نمونه واقعی از این فرآیند در بازی بلاکچینی Common Ground World قابل مشاهده است:

اسکرین شات از بازی Common Worlds nft در سایت opensea

در زمان نگارش این مطلب، بازی Common Ground World دارای ۶۸۱ مجموعه (collection) به‌عنوان دارایی‌ های داخل بازی است که OpenSea از آن‌ ها با عنوان “Unique items” (در کادر قرمز تصویر) یاد می‌ کند.

مجموع تمام دارایی‌ های موجود در این مجموعه‌ ها حدود ۹ میلیون آیتم است (نمایش‌داده‌شده در کادر سبز تصویر).

در ادامه، یک نمونه از یکی از مجموعه‌ های این بازی معرفی می‌ شود:

تصویر صفحه یکی از مجموعه‌های جهان‌های مشترک

مجموعه Water Tank در بازی Common Ground World دارای حدود ۴,۸۰۰ آیتم است (نمایش داده شده در کادر سبز) که توسط تقریباً ۲,۹۰۰ آدرس متفاوت نگهداری می‌شوند (در کادر قرمز مشخص شده است).

نکته قابل توجه این است که OpenSea اطلاعات مربوط به تعداد کل توکن‌ها را برای توکن‌های ERC-721 نمایش نمی‌دهد، زیرا در استاندارد ERC-721، هر tokenId فقط یک واحد دارد و تنها یک مالک مشخص برای آن وجود دارد.

برای مقایسه، در ادامه نمونه‌ای تصادفی از یکی از توکن‌های مجموعه Bored Ape Yacht Club نمایش داده شده که توسط آدرس F15C93 مالکیت دارد:

تصویر یک NFT تصادفی از Bored Ape Yacht Club متعلق به F15C93

این نکته زمانی واضح‌تر می‌شود که به بخش Details در صفحه توکن مربوطه در OpenSea توجه کنید؛ همان‌طور که در کادر قرمز تصویر مشخص شده، این توکن به‌وضوح از استاندارد ERC-1155 پیروی می‌کند:

تصویر از متادیتاهای رایج جهان

پلتفرم OpenSea قادر است توضیحات (description) و ویژگی‌ ها (traits) یک توکن را مستقیماً از متادیتای آن بارگذاری و نمایش دهد. این اطلاعات از طریق URI متادیتای مرتبط با توکن دریافت می‌ شوند.

شما می‌ توانید این فرآیند را به‌راحتی مشاهده کنید؛ کافی است در صفحه مجموعه، روی Token ID کلیک کنید.

OpenSea استانداردهایی برای متادیتا تعریف کرده است که URIها باید از آن‌ها پیروی کنند تا OpenSea بتواند متادیتای خارج از زنجیره را برای دارایی‌های ERC721 و ERC1155 دریافت کند.

نمونه‌ای از پیاده‌سازی ERC-1155

در ادامه، یک نمونه قرارداد ERC-1155 مربوط به یک بازی ساده ارائه شده است. این قرارداد با استفاده از نسخه انتزاعی OpenZeppelin از استاندارد ERC-1155 پیاده‌سازی شده و شامل چند تابع کمکی برای مدیریت وضعیت بازی است:

  • initializePlayer: حساب یک بازیکن را با مقدار اولیه‌ای از ارز درون بازی (که توسط ثابت INITIAL_IN_GAME_CURRENCY_BALANCE تعریف شده) مقداردهی اولیه می‌کند.

  • mintInGameCurrency: برای بازیکن مشخص‌شده ارز درون بازی ایجاد (mint) می‌کند.

  • mintCar: به بازیکنان اجازه می‌دهد خودروهایی منحصر به‌فرد بر پایه NFT ایجاد کنند.

🔔 توجه: این قرارداد صرفاً برای مقاصد آموزشی طراحی شده است و بسیاری از موارد امنیتی و بهینه‌سازی‌ های مهم در آن در نظر گرفته نشده‌اند.

این بازی قرار است شامل دو نوع توکن باشد:

  1. ارز درون بازی ($IGC):
    بازیکنان می‌توانند این توکن قابل تعویض را با انجام مأموریت‌ها به دست آورند. این توکن از نوع قابل تعویض (fungible) است.

  2. توکن غیرقابل تعویض (NFT):
    این توکن‌ها نمایانگر مجموعه‌ای از خودروها هستند که بازیکنان می‌توانند آن‌ها را ایجاد (mint) کنند. هر خودرو یک NFT منحصربه‌فرد خواهد بود.

زمانی که قرارداد را مستقر (deploy) می‌کنیم:

  • آدرس قرارداد:
    0xCc3958FE4Beb3bcb894c184362486eBEc2E1fD4D

  • آدرس بازیکن:
    0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

در بخش‌های بعدی، نحوه تعامل با این قرارداد برای مدیریت دارایی‌ های توکن آن را مرحله‌ به‌ مرحله نمایش خواهیم داد.

یک مثال از بازی با استفاده از استاندارد ERC-1155 در سالیدیتی

فلوچارت زیر نشان می‌دهد که بازیکنان چگونه با قرارداد ERC-1155 بازی تعامل دارند. این تعامل شامل مراحل مختلفی مانند ایجاد (mint) ارز درون بازی و ایجاد خودروهای NFT است.

نمودار گردش کار قرارداد بازی

توکن 0 در ERC-1155: ایجاد (Mint) $IGC

فرض کنیم می‌خواهیم بازیکنان در ابتدای بازی با موجودی ۱۰۰۰ عدد از توکن $IGC شروع کنند. برای این کار می‌توانیم با فراخوانی تابع initializePlayer در قرارداد، این مقدار از توکن را برای هر بازیکن ایجاد (mint) کنیم.

این تابع، شناسه توکن مربوط به $IGC یعنی ۰ و مقدار مورد نظر را به تابع زیر از قرارداد پایه OpenZeppelin ارسال می‌کند:

تابع _mint یکی از متدهای تعریف‌شده در کتابخانه OpenZeppelin برای ایجاد توکن است و مطابق استاندارد ERC-1155 رفتار می‌کند. این تابع:

  • بررسی‌ های لازم برای پذیرش (acceptance checks) را انجام می‌دهد،

  • در صورت نیاز، تابع safeTransferFrom را فراخوانی می‌ کند،

  • و در نهایت رویداد TransferSingle را منتشر می‌ کند (که در کادر آبی تصویر زیر مشاهده می‌شود).

پس از فراخوانی تابع initializePlayer، می‌ توانیم لاگ‌ های زیر را مشاهده کنیم:

تصویر صفحه نمایش رویداد انتقال واحد

در کادر قرمز می‌توان مشاهده کرد که رویداد TransferSingle منتشر شده است، و در کادر سبز نشان داده شده که آدرس صفر (که نشان‌دهنده ایجاد توکن است) تعداد 1000 واحد از ارز درون بازی (با شناسه توکن 0) را به آدرس بازیکن ارسال کرده است.

ایجاد (Mint) توکن $IGC بیشتر

هر زمان که بازیکن ما یک مأموریت را با موفقیت به پایان می‌رساند، می‌خواهیم به او توکن $IGC بیشتری به‌عنوان پاداش بدهیم. برای انجام این کار، کافی است تابع mintInGameCurrency را در قرارداد بازی فراخوانی کنیم.

این تابع در نهایت تابع _mint از کتابخانه OpenZeppelin را فراخوانی می‌کند و پارامترهای زیر را ارسال می‌نماید:

  • آدرس بازیکن:
    0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

  • مقدار توکن برای ایجاد:
    500 (به‌عنوان پاداش)

  • داده دلخواه:
    bytes خالی (در این مثال، داده‌ای ارسال نمی‌شود)

با فراخوانی تابع mintInGameCurrency با این مقادیر، 500 توکن $IGC جدید برای بازیکن ایجاد می‌شود.

اکنون اگر موجودی توکن $IGC بازیکن را از طریق تابع balanceOf بررسی کنیم:

تصویر از موجودی حساب IGC بازیکن

می‌ بینیم که بازیکن ما اکنون دارای موجودی 1500 عدد از توکن $IGC است (مقدار اولیه + پاداش).

توکن 1 در ERC-1155: ایجاد دارایی‌ های غیرقابل تعویض (خودروها)

car nft

حالا فرض کنیم می‌خواهیم به بازیکنان اجازه دهیم خودرو mint کنند، مشروط بر اینکه حداقل مقدار مشخصی از توکن $IGC را در اختیار داشته باشند. توجه داشته باشید که مجموعه خودروها از نوع غیرقابل تعویض (non-fungible) هستند.

ابتدا برای هر NFT خودرو، یک فایل JSON مستقل و اختصاصی وجود خواهد داشت که ویژگی‌های خودرو (مانند مدل، رنگ، قدرت، و غیره) را در خود دارد.

برای مثال، URI مربوط به اولین خودرو در مجموعه ما به شکل زیر خواهد بود:

جایی که آیدی برابر است با:

340282366920938463463374607431768211456 در دسیمال

یا

0x\textcolor{orange}{00000000000000000000000000000001}\textcolor{lightgreen}{00000000000000000000000000000000} در هگز

بخش‌های نارنجی نشان‌ دهنده شناسه مجموعه خودروها (collection ID = 1) هستند و بخش‌های سبز نشان‌ دهنده شناسه اولین خودرو در مجموعه (item ID = 0). این دو با هم یک شناسه یکتا تشکیل می‌ دهند که به یک متادیتا اشاره دارد، مثلاً:

اکنون، تابع mintCar را در قرارداد خود فراخوانی می‌ کنیم تا NFT خودرو برای بازیکن ایجاد شود:

متغیر carId جایی است که «جادوی غیرقابل تعویض بودن» اتفاق می‌ افتد. این متغیر یک شناسه یکتای توکن برای هر NFT خودرو محاسبه می‌ کند، با ترکیب شناسه مجموعه خودروها و اندیس بعدی قابل استفاده برای توکن (که از صفر شروع می‌ شود).

پس از فراخوانی تابع mintCar:

تصویر گزارش پس از فراخوانی تابع mintCar

همان‌طور که انتظار می‌رفت، یک توکن NFT خودرو (کادر زرد) از آدرس صفر به آدرس بازیکن ارسال (mint) شد.

🔴 نکته:
شناسه NFT (کادر قرمز) برابر است با:
340282366920938463463374607431768211456
که نتیجه محاسبه (1 << 128) + 0 می‌باشد؛
در اینجا عدد ۱ نشان‌ دهنده شناسه پایه توکن برای مجموعه خودرو است و عدد ۰ شناسه آیتم (itemID) مربوط به NFT درون آن مجموعه است.

فراتر از مدیریت همزمان توکن‌ های قابل تعویض و غیرقابل تعویض در یک قرارداد، توجه به آسیب‌پذیری‌ های امنیتی در قراردادهای ERC-1155 نیز بسیار حیاتی است. یکی از آسیب‌پذیری‌ های رایج، حملات reentrancy (بازگشتی) است که ممکن است فرآیند ایجاد یا انتقال توکن را مورد سوءاستفاده قرار دهد.

حملات Reentrancy در عملیات Mint و Transfer در ERC-1155

به‌دلیل وجود توابع callback در عملیات safeTransferFrom و safeBatchTransferFrom، قراردادهایی که از استاندارد ERC-1155 استفاده می‌کنند، در صورت پیاده‌سازی نادرست ممکن است در برابر حملات بازگشتی (reentrancy attacks) آسیب‌پذیر باشند.
خود استاندارد ERC-1155 ذاتاً ایمن است، اما اضافه کردن کدهای ناامن مانند mint بدون محافظت می‌تواند این آسیب‌پذیری را ایجاد کند.

نمونه زیر از مجموعه Solidity Riddles ارائه‌شده توسط RareSkills CTF، یک پیاده‌سازی ERC-1155 است که در برابر حمله بازگشتی قابل بهره‌برداری است:

توجه داشته باشید که تابع mint سعی دارد مانع از این شود که msg.sender بیش از ۳ توکن NFT ایجاد کند. اما این تابع نه تنها قفل مقابله با reentrancy ندارد، بلکه ترتیب عملیات آن نیز از الگوی checks-effects-interactions پیروی نمی‌ کند.

توجه داشته باشید که تابع mint تلاش می‌ کند از ایجاد بیش از ۳ NFT توسط msg.sender جلوگیری کند. با این حال، این تابع نه قفل بازگشتی (reentrancy lock) دارد و نه ترتیب عملیات آن از الگوی بررسی–تأثیر–تعامل (checks-effects-interactions) پیروی می‌ کند، چرا که مقدار توکن‌ های ایجادشده برای msg.sender را بعد از انجام mint و اجرای callback بررسی می‌ کند. بنابراین، مهاجم می‌ تواند از این قرارداد سوءاستفاده کند و از درون تابع onERC1155Received قرارداد مخرب خود، تابع mint را دوباره فراخوانی کند، همان‌ طور که قرارداد مخرب زیر نشان می‌ دهد:

مهاجم ابتدا تابعی را در قرارداد مخرب خود فراخوانی می‌کند تا فرآیند mint آغاز شود. این کار باعث می‌شود مقدار msg.sender برابر با آدرس قرارداد مهاجم باشد. زمانی که NFT ایجاد (mint) می‌شود، تابع onERC1155Received در قرارداد مهاجم فراخوانی خواهد شد. این تابع بررسی می‌کند که آیا مقدار مورد نظر قبلاً mint شده یا خیر، و اگر هنوز به آن مقدار نرسیده باشد، مجدداً وارد تابع mint می‌شود (reenter).

برای پیاده‌سازی امن استاندارد ERC-1155، ضروری است که توسعه‌دهندگان از این آسیب‌پذیری جلوگیری کنند؛ یا با پیروی دقیق از الگوی checks-effects-interactions و/یا با پیاده‌سازی قفل‌های reentrancy در توابع حساس مانند mint و transfer.

نتیجه‌گیری

استاندارد ERC-1155 یک رابط استاندارد شده برای پیاده‌سازی چند نوع توکن در قالب یک قرارداد واحد ارائه می‌دهد. این ویژگی امکان استفاده از مکانیزم‌ هایی مانند عملیات دسته‌ای (batch operations)، تأییدیه‌های کلی برای چند توکن به‌صورت هم‌زمان و همچنین صرفه‌جویی در هزینه گس هنگام استقرار قرارداد را فراهم می‌ کند.

این استاندارد نیاز به تعامل با چندین قرارداد جداگانه برای مدیریت مجموعه‌ های مختلف توکن را از بین می‌برد و با این کار، بهره‌وری گس و تجربه کاربری (UX) را در بازی‌ های بلاکچینی و پروژه‌ هایی که از توکن‌ های متنوع استفاده می‌ کنند، به‌ شکل چشمگیری بهبود می‌ بخشد.

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

پکیج آموزش صفر تا صد فتوشاپ به زبان فارسی – حرفه ای شوید
  • انتشار: ۲۸ اردیبهشت ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید