آموزش الگوی Beacon Proxy در سالیدیتی

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

در این مقاله، نحوه عملکرد این الگوی پراکسی را به‌صورت کامل بررسی می‌کنیم.

پیش‌نیازها

در این مقاله فرض می‌کنیم که شما با نحوه عملکرد یک پراکسی مینیمال (Minimal Proxy) آشنا هستید. و احتمالاً با الگوهای UUPS یا Transparent نیز آشنایی دارید.

انگیزه استفاده از Beacon Proxy

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

چندین پروکسی که به یک فلوچارت پیاده‌سازی واحد اشاره می‌کنند

برای درک دلیل این کار، فرض کنید یک بازی کاملاً آن‌چین (Fully On-Chain) طراحی کرده‌اید. این بازی قصد دارد هر حساب کاربری را به‌صورت یک قرارداد مستقل ذخیره کند. با این روش، انتقال حساب به کیف پول دیگر بسیار ساده خواهد بود و یک کیف پول می‌تواند چندین حساب مختلف داشته باشد.

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

برای پیاده سازی این ایده، می‌توان از یکی از دو روش زیر استفاده کرد:

  1. استفاده از استاندارد Minimal Proxy (استاندارد EIP-1167) و ساخت هر حساب به‌عنوان یک کلون

  2. استفاده از الگوهای پراکسی UUPS یا Transparent و ساخت یک پراکسی مجزا برای هر حساب

در بیشتر مواقع، هر دو روش گفته شده قابل استفاده هستند. اما اگر بخواهید قابلیت جدیدی به حساب ها اضافه کنید، چه می‌شود؟ در مورد استاندارد Minimal Proxy (EIP-1167)، به دلیل غیرقابل ارتقاء بودن کلون ها، باید کل سیستم را دوباره پیاده سازی کرده و همه کاربران را به نسخه جدید منتقل کنید، که این کار نیاز به هماهنگی اجتماعی دارد و پرهزینه است.

پراکسی های سنتی مانند UUPS یا Transparent قابلیت ارتقاء دارند، اما در این حالت باید تک تک پراکسی ها را به‌صورت جداگانه ارتقاء دهید. اگر تعداد حساب ها زیاد باشد، این کار زمان‌بر و پرهزینه خواهد شد. چه از کلون استفاده کنید و چه از پراکسی، ارتقاء زمانی که تعدادشان زیاد باشد دردسرساز می‌شود.

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

از دید کلی، این استاندارد به شما اجازه می‌دهد تا به تعداد نامحدود پراکسی برای یک قرارداد پیاده سازی ایجاد کنید و همچنان امکان ارتقاء ساده و متمرکز را حفظ کنید.

Beacon چگونه کار می‌کند

همان‌طور که از نام این الگو مشخص است، این استاندارد به یک قرارداد مرکزی به نام “Beacon” نیاز دارد. در کتابخانه OpenZeppelin، این قرارداد با نام UpgradeableBeacon شناخته می‌شود و در فایل UpgradeableBeacon.sol پیاده سازی شده است.

Beacon به عنوان یک قرارداد هوشمند، از طریق یک تابع عمومی، آدرس نسخه فعلی پیاده سازی را به پراکسی ها ارائه می‌دهد. پراکسی ها برای تعیین محل دقیق پیاده سازی، به همین آدرس تکیه می‌کنند. به همین دلیل به آن “Beacon” گفته می‌شود، چون مانند یک راهنما عمل می‌کند.

وقتی تراکنشی به یکی از پراکسی ها ارسال می‌شود، آن پراکسی ابتدا تابع implementation() را در Beacon فراخوانی می‌کند تا آدرس نسخه فعلی پیاده سازی را به دست آورد. سپس با استفاده از دستور delegatecall، اجرای تراکنش را به همان آدرس منتقل می‌کند.

این ساختار باعث می‌شود که Beacon نقش مرجع و راهنمای اصلی را در تعیین آدرس پیاده سازی ایفا کند.

معماری گام به گام فراخوانی delegatecall پروکسی Beacon

هر پراکسی جدیدی که اضافه می‌شود دقیقاً از همین الگو پیروی می‌کند: ابتدا از طریق فراخوانی تابع implementation() در Beacon، آدرس قرارداد پیاده سازی را دریافت می‌کند و سپس با استفاده از delegatecall، کنترل را به آن آدرس منتقل می‌کند.

نکته مهم این است که پراکسی ها می‌دانند باید تابع implementation() را از کجا فراخوانی کنند، چون آدرس Beacon را در یک متغیر تغییرناپذیر (immutable) در خود ذخیره کرده‌اند. در ادامه، مکانیزم این بخش را دقیق‌تر بررسی خواهیم کرد.

این الگو مقیاس‌پذیری بسیار بالایی دارد، چون هر پراکسی جدید به‌سادگی فقط آدرس پیاده‌سازی را از Beacon می‌خواند و بدون نیاز به منطق پیچیده، از طریق delegatecall به آن متصل می‌شود.

تابع getImplementation() پروکسی Beacon به صورت بصری نمایش داده شد.

با وجود اینکه الگوی Beacon Proxy شامل تعداد بیشتری قرارداد می‌شود، اما خود پراکسی‌ در این الگو ساده‌تر از پراکسی های قابل ارتقاء UUPS یا Transparent هستند. در الگوی Beacon، هر پراکسی همیشه فقط یک وظیفه دارد: فراخوانی آدرس مشخص Beacon برای دریافت آدرس پیاده سازی فعلی. به همین دلیل، نیازی ندارد با جزئیاتی مثل تعیین مدیر (admin) یا نحوه تغییر آدرس پیاده سازی درگیر شود. این ساختار باعث می‌شود پراکسی ها ساده و سبک باقی بمانند، در حالی که منطق ارتقاء در لایه Beacon متمرکز می‌شود.

ارتقاء همزمان چند پراکسی

از آنجا که تمام پراکسی ها آدرس قرارداد پیاده سازی را از متغیر ذخیره‌شده در Beacon دریافت می‌کنند، تنها با تغییر این آدرس در حافظه Beacon، می‌توان تمام پراکسی ها را به‌صورت آنی به نسخه جدید هدایت کرد. این فرآیند عملاً باعث می‌شود همه آن‌ها بلافاصله به آدرس جدید delegatecall کنند.

برای ارتقاء همزمان تمام پراکسی ها، کافی است مراحل زیر را انجام دهید:

  1. یک قرارداد پیاده سازی جدید منتشر کنید

  2. آدرس این قرارداد جدید را در حافظه Beacon ثبت کنید

برای انجام مرحله دوم، باید تابع upgradeTo(address newImplementation) را در قرارداد Beacon صدا بزنید و آدرس نسخه جدید را به‌عنوان آرگومان وارد کنید. تابع upgradeTo() یکی از دو تابع عمومی قرارداد UpgradeableBeacon.sol است. تابع دیگر، implementation() است که پیش‌تر درباره آن صحبت کردیم. نکته مهم این است که تابع upgradeTo() دارای محدودیت دسترسی onlyOwner است. این محدودیت هنگام ساخت (constructor) قرارداد Beacon تعریف می‌شود و تضمین می‌کند که فقط مالک مجاز به ارتقاء پیاده سازی است.

تابع upgradeTo() در داخل خود، یک تابع داخلی به نام _setImplementation(address newImplementation) را فراخوانی می‌کند. این تابع نیز در قرارداد Beacon تعریف شده است. در ابتدا، _setImplementation بررسی می‌کند که آیا آدرس جدید واقعاً به یک قرارداد مربوط است یا نه. اگر این شرط برقرار باشد، آدرس را در متغیر ذخیره سازی داخلی به نام _implementation ثبت می‌کند. این متغیر همان آدرسی است که پراکسی ها برای دسترسی به پیاده سازی از آن استفاده می‌کنند.

حالا که آدرس پیاده سازی در حافظه Beacon تغییر کرده، تمام پراکسی ها هنگام اجرا، آدرس جدید را از Beacon می‌خوانند و عملیات delegatecall را به همان آدرس هدایت می‌کنند. این روش ارتقاء بسیار ساده است، چون فقط کافی است آدرس پیاده سازی را در Beacon به نسخه جدید تغییر دهید و به این ترتیب، همه پراکسی ها به طور خودکار از همان نسخه جدید استفاده می‌کنند. حتی اگر نیاز به بازگشت به نسخه قبلی داشته باشید، می‌توانید آدرس پیاده سازی را دوباره به نسخه قبلی تغییر دهید. فقط باید مراقب باشید که تداخل در فضای ذخیره سازی (storage collisions) ایجاد نشود.

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

ارتقاء یک پروکسی Beacon به صورت تصویری

بررسی کد قرارداد پراکسی

برای اینکه مخاطب دچار سردرگمی نشود، در این بخش عبارت “BeaconProxy” را برای اشاره به قرارداد پراکسی (کد) به کار می‌بریم و از عبارت “الگوی beacon proxy” برای اشاره به الگوی طراحی استفاده می‌کنیم.

در ادامه به بررسی قرارداد پراکسی می‌پردازیم که OpenZeppelin با نام BeaconProxy پیاده سازی کرده و در فایل BeaconProxy.sol قرار دارد. قرارداد BeaconProxy از قرارداد پایه Proxy.sol ارث‌بری می‌کند و چند قابلیت اضافی به آن اضافه می‌شود:

  • آدرس قرارداد Beacon در متغیر داخلی _beacon ذخیره می‌شود

  • تابعی به نام _getBeacon() تعریف شده که مقدار _beacon را بازمی‌گرداند

  • تابع _implementation() بازنویسی شده تا به جای مقدار داخلی، مستقیماً تابع implementation() را روی آدرس _beacon فراخوانی کند

  • یک سازنده (constructor) نیز اضافه شده تا مقدار _beacon را هنگام ایجاد قرارداد تنظیم کند و پارامتر data را برای مقداردهی اولیه پراکسی استفاده نماید

در ادامه، نسخه‌ای از پیاده سازی BeaconProxy توسط OpenZeppelin را بدون کامنت‌های اضافی مشاهده می‌کنید.

تابع _implementation() در قرارداد BeaconProxy بازنویسی شده است، چون قرارداد پایه Proxy.sol برای اجرای delegatecall ابتدا این تابع را فراخوانی می‌کند تا آدرس قرارداد پیاده سازی را به دست آورد. سازنده (constructor) در BeaconProxy دو وظیفه اصلی دارد:

  1. تنظیم آدرس _beacon

  2. مقداردهی اولیه پراکسی با استفاده از پارامتر data

پارامتر data اختیاری است اما نقش مهمی دارد. این داده ها در یک delegatecall به قرارداد پیاده سازی ارسال می‌شوند تا متغیرهای ذخیره سازی پراکسی مقداردهی شوند. برای مثال، در سناریوی بازی که قبلاً مطرح شد، می‌توان با استفاده از این داده ها آمار اولیه بازیکن را هنگام ایجاد حساب (پراکسی) تنظیم کرد. در واقع، پارامتر data نقش سازنده (constructor) را برای پراکسی ایفا می‌کند. یعنی قرارداد پیاده سازی از طریق delegatecall به آن داده ها دسترسی پیدا می‌کند و می‌تواند منطق خود را اجرا کرده و متغیرهای ذخیره سازی پراکسی را پیکربندی کند.

ارتباط ERC-1967 و BeaconProxy.sol

برای اینکه مرورگرهای بلاک (Block Explorer) بتوانند تشخیص دهند که یک قرارداد BeaconProxy در واقع یک پراکسی است، این قرارداد باید مطابق با استاندارد ERC-1967 عمل کند. از آنجا که BeaconProxy به طور خاص یک نوع Beacon پراکسی محسوب می‌شود، باید آدرس Beacon را در یک محل مشخص از فضای ذخیره سازی قرار دهد. این محل دقیقاً برابر است با:

این آدرس از محاسبه عبارت bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) به دست می‌آید.

مشابه آنچه در پراکسی های Upgradeable Transparent دیده می‌شود، BeaconProxy به صورت واقعی از این اسلات حافظه استفاده نمی‌کند. توسعه دهنده این مقدار را صرفاً به عنوان یک سیگنال در قرارداد قرار می‌دهد تا مرورگرهای بلاک بتوانند تشخیص دهند که این قرارداد یک Beacon Proxy است. در عمل، آدرس پیاده سازی در یک متغیر تغییرناپذیر (immutable) ذخیره می‌شود تا مصرف گس بهینه شود. چون آدرس Beacon هیچ‌گاه تغییر نمی‌کند، استفاده از متغیر immutable انتخابی منطقی و بهینه است.

EIP-2930

هنگام استفاده از الگوی Beacon Proxy، حتماً از تراکنش‌های دارای لیست دسترسی (Access List) که در EIP-2930 معرفی شده‌اند، استفاده کنید. این نوع تراکنش‌ها هنگام فراخوانی بین قراردادها و دسترسی به حافظه قرارداد دیگر، باعث صرفه‌جویی در مصرف گس می‌شوند. در این الگو، پراکسی باید به قرارداد Beacon فراخوانی انجام دهد و آدرس پیاده سازی را از حافظه آن بخواند. استفاده از access list باعث می‌شود این عملیات با هزینه کمتری انجام شود. برای مشاهده نتایج بنچمارک مربوط به عملکرد access list در Beacon Proxy می‌توانید به این لینک مراجعه کنید.

استقرار چند BeaconProxy

اگر بخواهید به صورت دستی چندین قرارداد BeaconProxy را مستقر کنید، این کار بسیار وقت‌گیر و پرزحمت خواهد بود. اینجاست که استفاده از Factory Contract اهمیت پیدا می‌کند. قرارداد کارخانه وظیفه دارد پراکسی های جدید را مستقر کند و در سازنده هرکدام، آدرس Beacon را تنظیم نماید.

OpenZeppelin در الگوی Beacon Proxy استفاده از Factory Contract را اجباری نمی‌داند و نمونه‌ای رسمی برای آن ارائه نمی‌کند. با این حال، در عمل استفاده از یک قرارداد کارخانه باعث ساده سازی روند ساخت و استقرار پراکسی های جدید می‌شود.

در ادامه، ما یک نمونه Factory Contract را بررسی می‌کنیم. این قرارداد، آدرس Beacon را در خود نگه می‌دارد و تابعی به نام createBeaconProxy() در آن تعریف شده که با دریافت داده ورودی، یک پراکسی جدید ایجاد می‌کند. این داده ها به سازنده BeaconProxy منتقل می‌شوند تا مقداردهی اولیه انجام گیرد. پس از استقرار موفق پراکسی، این تابع آدرس قرارداد ایجادشده را برمی‌گرداند.

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

این‌ها تمام قراردادهایی هستند که برای طراحی یک الگوی Beacon نیاز دارید:

  • قرارداد پیاده سازی (Implementation): حاوی منطق اصلی برنامه است که تمام پراکسی ها به آن متصل می‌شوند.

  • Beacon: آدرس قرارداد پیاده سازی را در خود نگه می‌دارد و به عنوان مرجع برای پراکسی ها عمل می‌کند.

  • Factory (اختیاری): ساخت پراکسی های جدید را ساده‌تر و ساختارمندتر می‌کند.

  • پراکسی‌ها (Proxy): درخواست‌ها را دریافت می‌کنند، از Beacon آدرس پیاده سازی را می‌گیرند و عملیات را به آن منتقل می‌کنند.

استقرار (Deploying)

شاید در نگاه اول پیاده سازی این سیستم کمی پیچیده به نظر برسد، اما واقعاً ساده‌تر از چیزی است که تصور می‌کنید. تیم OpenZeppelin برای هر دو فریم‌ورک Hardhat و Foundry افزونه‌ای به نام Upgrades Plugin منتشر کرده که روند استقرار و ارتقاء را به‌شکل چشمگیری ساده می‌کند.

کافی است این کتابخانه را نصب کنید و سپس با استفاده از تابع deployBeacon()، قرارداد Beacon را به همراه پارامترهای مورد نیاز مستقر کنید. پس از آن، می‌توانید با فراخوانی deployBeaconProxy() پراکسی های جدید را مستقر نمایید.

فرآیند ارتقاء نیز مشابه است؛ کافی است تابع upgradeBeacon() را همراه با آدرس پیاده سازی جدید صدا بزنید تا کل سیستم به نسخه جدید منتقل شود. علاوه بر این، اگر ترجیح می‌دهید سیستم را به‌صورت دستی مستقر کنید، می‌توانید مراحل زیر را دنبال کنید:

  1. قرارداد پیاده سازی را مستقر کنید

  2. قرارداد Beacon را مستقر کنید و در سازنده آن، آدرس قرارداد پیاده سازی و آدرس حسابی را وارد کنید که اجازه ارتقاء دارد

  3. قرارداد Factory را مستقر کنید

  4. از Factory استفاده کنید تا هر تعداد پراکسی که نیاز دارید ایجاد کنید

ترتیب استقرار Beacon Proxy با Factory

یک نمونه در دنیای واقعی

Beacon Proxy در چه موقعیت‌هایی در دنیای واقعی کاربرد دارد؟ برای پاسخ به این سؤال، می‌توان به تجربه شخصی من در پروژه Kwenta اشاره کرد. من برای این پروژه یک Beacon Proxy پیاده سازی کردم که هم‌اکنون روی شبکه Optimism فعال است و بیش از 20 میلیون دلار ارزش قفل‌شده (TVL) دارد. هدف از این Beacon Proxy، مدیریت بسته‌های واگذاری توکن (Vesting Packages) در Kwenta بود. در بسته های واگذاری، یک قرارداد هوشمند به‌صورت تدریجی توکن های Kwenta را برای افراد خاص و مشارکت‌کنندگان اصلی پروژه آزاد می‌کند. هر فرد یکی از این بسته ها را دریافت می‌کند که میزان توکن ها و مدت زمان آزادسازی آن‌ها (معمولاً بین ۱ تا ۴ سال) متفاوت است. اگر می‌خواهید با مفهوم واگذاری در دنیای رمزارزها بیشتر آشنا شوید، می‌توانید از این لینک استفاده کنید. اما چرا از Beacon Proxy استفاده شد؟

دلیل اصلی، نیاز به قابلیت ارتقاء آسان بود. تیم توسعه باید بسته‌های واگذاری را به‌گونه‌ای طراحی می‌کرد که قابلیت ارتقاء داشته باشند، چون این بسته‌ها مستقیماً به سیستم استیکینگ Kwenta متصل هستند؛ سیستمی که خودش نیز قابل ارتقاء است. اگر در آینده تغییری در سیستم استیکینگ ایجاد شود، احتمال دارد عملکرد بسته‌های واگذاری دچار اختلال شود. به همین دلیل، تیم توسعه با افزودن امکان ارتقاء، این بسته‌ها را در برابر تغییرات آینده مقاوم و قابل انطباق طراحی کرده است.

تمام بسته ها از یک منطق یکسان استفاده می‌کردند—توابعی مثل vest() و stake()—اما پارامترهای اولیه آن‌ها مانند مقدار توکن و مدت زمان واگذاری متفاوت بود. برای رسیدن به این هدف، تیم توسعه بسته های واگذاری را به‌صورت قراردادهای مستقل (siloed) طراحی کرد؛ این رویکرد چند مزیت کلیدی به همراه داشت:

1. ساده‌سازی توسعه:

طراحی یک قرارداد قابل مقداردهی اولیه (initializable) برای هر فرد، کار تیم توسعه را بسیار ساده‌تر از ساخت یک قرارداد بزرگ و پیچیده با نگاشت‌های متعدد برای مدیریت بسته‌های همه افراد کرد. همچنین، چون سیستم طوری طراحی شده بود که هر بسته هنگام ساخت، توکن KWENTA را به‌صورت خودکار استیک می‌کرد، هر فرد پاداش‌های خود را به‌طور مستقل دریافت می‌کرد. در صورتی‌که همه بسته‌ها در یک قرارداد متمرکز قرار می‌گرفتند، این پاداش‌ها با هم ترکیب می‌شدند و مدیریت آن‌ها به‌مراتب دشوارتر و بی‌نظم‌تر می‌بود.

2. انتقال آسان مالکیت:

کاربران می‌توانستند مالکیت هر بسته را به‌راحتی به آدرس‌های دیگر یا حتی به یک کیف پول چندامضایی (multi-sig) منتقل کنند.

3. جلوگیری از محدودیت استیکینگ:

فرآیند واگذاری توکن ها شامل فراخوانی تابع unstake() در قرارداد استیکینگ Kwenta می‌شد. این تابع دارای یک دوره انتظار دو هفته‌ای است.  اگر همه افراد از یک قرارداد مشترک استفاده می‌کردند، استیک خارج‌شده توسط یک نفر، سایرین را از واگذاری بسته‌هایشان تا دو هفته آینده محروم می‌کرد. تیم توسعه با جدا کردن بسته‌ها در قالب قراردادهای مستقل، این مشکل را به‌طور کامل برطرف کرد. در مجموع، تیم توسعه باید بسته‌های واگذاری را طوری طراحی می‌کرد که از بیش از ۱۰ نفر پشتیبانی کنند؛ یعنی حداقل به ۱۰ پراکسی مجزا نیاز داشتند. استفاده از الگوی Beacon Proxy این امکان را فراهم کرد که تمام این پراکسی ها را به شکل کارآمد، قابل ارتقاء و منعطف طراحی و پیاده‌سازی کنیم.

Beacon Proxy تمام نیازها را بدون هیچ محدودیتی برطرف کرد. استفاده از Beacon Proxy این امکان را فراهم کرد که تمام موارد مورد نیاز—از جمله ارتقاءپذیری، استقلال قراردادها، و توسعه ساده—بدون هیچ‌گونه مصالحه‌ای پیاده سازی شود. روش کلون (Clones) می‌توانست به‌راحتی بیش از ۱۰ قرارداد initializable ایجاد کند، اما مشکل اینجاست که کلون‌ها قابل ارتقاء نیستند و در صورت تغییر منطق، باید سیستم را از ابتدا بازطراحی کرد. هر دو الگوی Transparent و UUPS از ارتقاء پشتیبانی می‌کنند، اما توسعه‌دهندگان باید در این روش‌ها هر بسته واگذاری را به‌صورت جداگانه ارتقاء دهند. این کار علاوه‌بر وقت‌گیر بودن، مصرف گس بیشتری نیز به همراه دارد. گزینه دیگر این بود که تیم توسعه از Diamond Proxy استفاده کند، اما ساختار آن برای چنین کاربردی بیش از حد پیچیده بود و با نیازهای پروژه متناسب نبود. در نهایت، Beacon Proxy انتخابی هوشمندانه و متعادل بود؛ چون هم ارتقاء مرکزی را ممکن می‌کرد، هم استقرار و مدیریت چندین پراکسی را ساده نگه می‌داشت.

FactoryBeacon در Kwenta

Kwenta در قالب FactoryBeacon، دو قرارداد UpgradeableBeacon.sol و Factory را با یکدیگر ترکیب کرده است تا بهینه‌سازی انجام دهد. این ترکیب، فرآیند راه‌اندازی را ساده‌تر می‌کند و سطح پیچیدگی کلی را کاهش می‌دهد.

فلوچارت FactoryBeacon در Kwenta

این امکان وجود دارد چون لازم نیست فکتوری به‌صورت یک قرارداد مستقل پیاده سازی شود؛ در واقع، تنها چند خط کد کافی‌ست تا یک BeaconProxy جدید مستقر شود و آدرس بیکن و داده های مقداردهی اولیه‌اش تنظیم شود. در ادامه، یک نمونه از قرارداد ترکیبی فکتوری و بیکن ارائه شده است. با ارث‌بری از قرارداد UpgradeableBeacon، این قرارداد تمام قابلیت‌های یک بیکن معمولی را حفظ می‌کند، در حالی که تابع createBeaconProxy() نقش فکتوری را نیز به آن اضافه می‌کند. علاوه‌براین، دیگر نیازی به ذخیره سازی آدرس بیکن وجود ندارد؛ زیرا می‌توان مستقیماً از address(this) استفاده کرد.

قطعه کد قرارداد ترکیبی Factory و Beacon

با این حال، ساختار کلی «Beacon» همچنان ثابت باقی مانده است. هر کاربر مستقیماً BeaconProxy خود را فراخوانی می‌کند؛ پروکسی که شامل تمام اطلاعات ذخیره سازی مربوط به بسته واگذاری خاص آن فرد است (از جمله میزان واگذاری و مدت‌زمان آن). این BeaconProxy، آدرس پیاده سازی را از FactoryBeacon دریافت می‌کند؛ همان ساختاری که همچنان عملکردی مشابه یک بیکن معمولی دارد. پس از دریافت آدرس پیاده سازی از FactoryBeacon، BeaconProxy فراخوانی delegatecall را به قرارداد VestingBaseV2 انجام می‌دهد که تنها نقش پیاده سازی (implementation) را دارد.

نکته مهم این است که فقط موجودیت adminDAO (یک کیف پول چندامضایی مدیریتی) اجازه دارد FactoryBeacon را فراخوانی کند. تنها ادمین می‌تواند یک بسته واگذاری جدید (BeaconProxy) ایجاد کرده یا نسخه پیاده سازی پروکسی ها را ارتقاء دهد.

نتیجه‌گیری

الگوی Beacon Proxy این امکان را فراهم می‌کند که چندین پروکسی با استفاده از یک پیاده سازی ایجاد شوند و همه آن‌ها به‌صورت هم‌زمان قابل ارتقاء باشند. فکتوری، پروکسی های جدید را مستقر می‌کند و این پروکسی ها با استفاده از delegatecall به آدرسی که از بیکن دریافت شده، متصل می‌شوند. بیکن نقش منبع اصلی آدرس پیاده سازی را ایفا می‌کند.

نکته مهم این است که الگوی Beacon Proxy نسبت به الگوهایی مانند UUPS یا Transparent در مرحله راه اندازی، گس بیشتری مصرف می‌کند؛ زیرا علاوه‌بر هر پروکسی، باید یک فکتوری و یک بیکن نیز مستقر شود. همچنین هر بار فراخوانی به یک پروکسی، نیازمند فراخوانی اضافی به بیکن است که هزینه گس را افزایش می‌دهد. این افزایش مصرف گس، مهم‌ترین نقطه‌ضعف این الگو محسوب می‌شود.

با این حال، اگر پروژه به تعداد زیادی پروکسی نیاز داشته باشد، این هزینه اضافی توجیه‌پذیر است و مزیت اصلی این الگو نیز دقیقاً در چنین مواردی نمود پیدا می‌کند. به همین دلیل، معمولاً استفاده از Beacon Proxy زمانی توصیه می‌شود که بیش از یک پروکسی مورد نیاز باشد. هرچند این الگو امکان ارتقاء هم‌زمان چندین پروکسی را فراهم می‌کند، اما راه‌اندازی آن پیچیده‌تر و پرهزینه‌تر است. اجرای این ساختار نیازمند مصرف گس بیشتر و راه‌اندازی قراردادهای اضافه است که هزینه توسعه و حسابرسی (auditing) را نیز افزایش می‌دهد. بنابراین، الگوی Beacon Proxy تنها زمانی مزیت دارد که پروژه واقعاً به تعداد زیادی پروکسی نیاز داشته باشد.

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

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

پکیج جامع و پروژه محور ASP.NET MVC + طراحی فروشگاه اینترنتی فروش فایل
  • انتشار: ۱۶ تیر ۱۴۰۴

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

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

مشاهده همه

نظرات

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