الگوی Beacon Proxy یکی از روشهای بهروزرسانی قراردادهای هوشمند در سالیدیتی است. در این روش، چندین پراکسی (Proxy) از یک قرارداد پیادهسازی مشترک استفاده میکنند. مزیت اصلی این روش این است که با انجام تنها یک تراکنش، میتوان تمام این پراکسی ها را به نسخه جدیدی از قرارداد پیاده سازی ارتقا داد.
در این مقاله، نحوه عملکرد این الگوی پراکسی را بهصورت کامل بررسی میکنیم.
پیشنیازها
در این مقاله فرض میکنیم که شما با نحوه عملکرد یک پراکسی مینیمال (Minimal Proxy) آشنا هستید. و احتمالاً با الگوهای UUPS یا Transparent نیز آشنایی دارید.
انگیزه استفاده از Beacon Proxy
در حالت معمول، الگوی پراکسی از یک قرارداد پیاده سازی و یک قرارداد پراکسی استفاده میکند. اما این امکان وجود دارد که چندین پراکسی از یک قرارداد پیاده سازی مشترک استفاده کنند.
برای درک دلیل این کار، فرض کنید یک بازی کاملاً آنچین (Fully On-Chain) طراحی کردهاید. این بازی قصد دارد هر حساب کاربری را بهصورت یک قرارداد مستقل ذخیره کند. با این روش، انتقال حساب به کیف پول دیگر بسیار ساده خواهد بود و یک کیف پول میتواند چندین حساب مختلف داشته باشد.
در چنین ساختاری، هر پراکسی اطلاعات حساب خودش را در متغیرهای ذخیره سازی جداگانه نگهداری میکند.
برای پیاده سازی این ایده، میتوان از یکی از دو روش زیر استفاده کرد:
-
استفاده از استاندارد Minimal Proxy (استاندارد EIP-1167) و ساخت هر حساب بهعنوان یک کلون
-
استفاده از الگوهای پراکسی UUPS یا Transparent و ساخت یک پراکسی مجزا برای هر حساب
در بیشتر مواقع، هر دو روش گفته شده قابل استفاده هستند. اما اگر بخواهید قابلیت جدیدی به حساب ها اضافه کنید، چه میشود؟ در مورد استاندارد Minimal Proxy (EIP-1167)، به دلیل غیرقابل ارتقاء بودن کلون ها، باید کل سیستم را دوباره پیاده سازی کرده و همه کاربران را به نسخه جدید منتقل کنید، که این کار نیاز به هماهنگی اجتماعی دارد و پرهزینه است.
پراکسی های سنتی مانند UUPS یا Transparent قابلیت ارتقاء دارند، اما در این حالت باید تک تک پراکسی ها را بهصورت جداگانه ارتقاء دهید. اگر تعداد حساب ها زیاد باشد، این کار زمانبر و پرهزینه خواهد شد. چه از کلون استفاده کنید و چه از پراکسی، ارتقاء زمانی که تعدادشان زیاد باشد دردسرساز میشود.
الگوی Beacon برای حل همین مشکل طراحی شده است. با استفاده از این الگو، میتوانید یک قرارداد پیادهسازی جدید منتشر کنید و تمام پراکسی ها را بهصورت همزمان به آن ارتقاء دهید. در واقع Beacon این امکان را به شما میدهد که نسخه جدیدی از حساب کاربری ایجاد کرده و همه پراکسی های متصل به آن را تنها با یک تراکنش به نسخه جدید ارتقاء دهید.
از دید کلی، این استاندارد به شما اجازه میدهد تا به تعداد نامحدود پراکسی برای یک قرارداد پیاده سازی ایجاد کنید و همچنان امکان ارتقاء ساده و متمرکز را حفظ کنید.
Beacon چگونه کار میکند
همانطور که از نام این الگو مشخص است، این استاندارد به یک قرارداد مرکزی به نام “Beacon” نیاز دارد. در کتابخانه OpenZeppelin، این قرارداد با نام UpgradeableBeacon
شناخته میشود و در فایل UpgradeableBeacon.sol
پیاده سازی شده است.
Beacon به عنوان یک قرارداد هوشمند، از طریق یک تابع عمومی، آدرس نسخه فعلی پیاده سازی را به پراکسی ها ارائه میدهد. پراکسی ها برای تعیین محل دقیق پیاده سازی، به همین آدرس تکیه میکنند. به همین دلیل به آن “Beacon” گفته میشود، چون مانند یک راهنما عمل میکند.
وقتی تراکنشی به یکی از پراکسی ها ارسال میشود، آن پراکسی ابتدا تابع implementation()
را در Beacon فراخوانی میکند تا آدرس نسخه فعلی پیاده سازی را به دست آورد. سپس با استفاده از دستور delegatecall، اجرای تراکنش را به همان آدرس منتقل میکند.
این ساختار باعث میشود که Beacon نقش مرجع و راهنمای اصلی را در تعیین آدرس پیاده سازی ایفا کند.
هر پراکسی جدیدی که اضافه میشود دقیقاً از همین الگو پیروی میکند: ابتدا از طریق فراخوانی تابع implementation()
در Beacon، آدرس قرارداد پیاده سازی را دریافت میکند و سپس با استفاده از delegatecall
، کنترل را به آن آدرس منتقل میکند.
نکته مهم این است که پراکسی ها میدانند باید تابع implementation()
را از کجا فراخوانی کنند، چون آدرس Beacon را در یک متغیر تغییرناپذیر (immutable) در خود ذخیره کردهاند. در ادامه، مکانیزم این بخش را دقیقتر بررسی خواهیم کرد.
این الگو مقیاسپذیری بسیار بالایی دارد، چون هر پراکسی جدید بهسادگی فقط آدرس پیادهسازی را از Beacon میخواند و بدون نیاز به منطق پیچیده، از طریق delegatecall
به آن متصل میشود.
با وجود اینکه الگوی Beacon Proxy شامل تعداد بیشتری قرارداد میشود، اما خود پراکسی در این الگو سادهتر از پراکسی های قابل ارتقاء UUPS یا Transparent هستند. در الگوی Beacon، هر پراکسی همیشه فقط یک وظیفه دارد: فراخوانی آدرس مشخص Beacon برای دریافت آدرس پیاده سازی فعلی. به همین دلیل، نیازی ندارد با جزئیاتی مثل تعیین مدیر (admin) یا نحوه تغییر آدرس پیاده سازی درگیر شود. این ساختار باعث میشود پراکسی ها ساده و سبک باقی بمانند، در حالی که منطق ارتقاء در لایه Beacon متمرکز میشود.
ارتقاء همزمان چند پراکسی
از آنجا که تمام پراکسی ها آدرس قرارداد پیاده سازی را از متغیر ذخیرهشده در Beacon دریافت میکنند، تنها با تغییر این آدرس در حافظه Beacon، میتوان تمام پراکسی ها را بهصورت آنی به نسخه جدید هدایت کرد. این فرآیند عملاً باعث میشود همه آنها بلافاصله به آدرس جدید delegatecall کنند.
برای ارتقاء همزمان تمام پراکسی ها، کافی است مراحل زیر را انجام دهید:
-
یک قرارداد پیاده سازی جدید منتشر کنید
-
آدرس این قرارداد جدید را در حافظه Beacon ثبت کنید
برای انجام مرحله دوم، باید تابع upgradeTo(address newImplementation)
را در قرارداد Beacon صدا بزنید و آدرس نسخه جدید را بهعنوان آرگومان وارد کنید. تابع upgradeTo()
یکی از دو تابع عمومی قرارداد UpgradeableBeacon.sol
است. تابع دیگر، implementation()
است که پیشتر درباره آن صحبت کردیم. نکته مهم این است که تابع upgradeTo()
دارای محدودیت دسترسی onlyOwner
است. این محدودیت هنگام ساخت (constructor) قرارداد Beacon تعریف میشود و تضمین میکند که فقط مالک مجاز به ارتقاء پیاده سازی است.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/** * @dev Upgrades the beacon to a new implementation. * * Emits an {Upgraded} event. * * Requirements: * * - msg.sender must be the owner of the contract. * - `newImplementation` must be a contract. */ function upgradeTo(address newImplementation) public virtual onlyOwner { _setImplementation(newImplementation); } |
تابع upgradeTo()
در داخل خود، یک تابع داخلی به نام _setImplementation(address newImplementation)
را فراخوانی میکند. این تابع نیز در قرارداد Beacon تعریف شده است. در ابتدا، _setImplementation
بررسی میکند که آیا آدرس جدید واقعاً به یک قرارداد مربوط است یا نه. اگر این شرط برقرار باشد، آدرس را در متغیر ذخیره سازی داخلی به نام _implementation
ثبت میکند. این متغیر همان آدرسی است که پراکسی ها برای دسترسی به پیاده سازی از آن استفاده میکنند.
1 2 3 4 5 6 7 8 9 10 11 |
/** * @dev Sets the implementation contract address for this beacon * * Requirements: * * - `newImplementation` must be a contract. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract"); _implementation = newImplementation; } |
حالا که آدرس پیاده سازی در حافظه Beacon تغییر کرده، تمام پراکسی ها هنگام اجرا، آدرس جدید را از Beacon میخوانند و عملیات delegatecall
را به همان آدرس هدایت میکنند. این روش ارتقاء بسیار ساده است، چون فقط کافی است آدرس پیاده سازی را در Beacon به نسخه جدید تغییر دهید و به این ترتیب، همه پراکسی ها به طور خودکار از همان نسخه جدید استفاده میکنند. حتی اگر نیاز به بازگشت به نسخه قبلی داشته باشید، میتوانید آدرس پیاده سازی را دوباره به نسخه قبلی تغییر دهید. فقط باید مراقب باشید که تداخل در فضای ذخیره سازی (storage collisions) ایجاد نشود.
این ساختار در بسیاری از پروژههای حرفهای برنامه نویسی سالیدیتی، بهعنوان روشی کارآمد و مقیاسپذیر برای مدیریت چند قرارداد مشابه به کار میرود.
بررسی کد قرارداد پراکسی
برای اینکه مخاطب دچار سردرگمی نشود، در این بخش عبارت “BeaconProxy” را برای اشاره به قرارداد پراکسی (کد) به کار میبریم و از عبارت “الگوی beacon proxy” برای اشاره به الگوی طراحی استفاده میکنیم.
در ادامه به بررسی قرارداد پراکسی میپردازیم که OpenZeppelin با نام BeaconProxy
پیاده سازی کرده و در فایل BeaconProxy.sol
قرار دارد. قرارداد BeaconProxy
از قرارداد پایه Proxy.sol
ارثبری میکند و چند قابلیت اضافی به آن اضافه میشود:
-
آدرس قرارداد Beacon در متغیر داخلی
_beacon
ذخیره میشود -
تابعی به نام
_getBeacon()
تعریف شده که مقدار_beacon
را بازمیگرداند -
تابع
_implementation()
بازنویسی شده تا به جای مقدار داخلی، مستقیماً تابعimplementation()
را روی آدرس_beacon
فراخوانی کند -
یک سازنده (constructor) نیز اضافه شده تا مقدار
_beacon
را هنگام ایجاد قرارداد تنظیم کند و پارامترdata
را برای مقداردهی اولیه پراکسی استفاده نماید
در ادامه، نسخهای از پیاده سازی BeaconProxy
توسط OpenZeppelin را بدون کامنتهای اضافی مشاهده میکنید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
contract BeaconProxy is Proxy { address private immutable _beacon; constructor(address beacon, bytes memory data) payable { ERC1967Utils.upgradeBeaconToAndCall(beacon, data); _beacon = beacon; } function _implementation() internal view virtual override returns (address) { return IBeacon(_getBeacon()).implementation(); } function _getBeacon() internal view virtual returns (address) { return _beacon; } } |
تابع _implementation()
در قرارداد BeaconProxy
بازنویسی شده است، چون قرارداد پایه Proxy.sol
برای اجرای delegatecall
ابتدا این تابع را فراخوانی میکند تا آدرس قرارداد پیاده سازی را به دست آورد. سازنده (constructor) در BeaconProxy
دو وظیفه اصلی دارد:
-
تنظیم آدرس
_beacon
-
مقداردهی اولیه پراکسی با استفاده از پارامتر
data
پارامتر data
اختیاری است اما نقش مهمی دارد. این داده ها در یک delegatecall
به قرارداد پیاده سازی ارسال میشوند تا متغیرهای ذخیره سازی پراکسی مقداردهی شوند. برای مثال، در سناریوی بازی که قبلاً مطرح شد، میتوان با استفاده از این داده ها آمار اولیه بازیکن را هنگام ایجاد حساب (پراکسی) تنظیم کرد. در واقع، پارامتر data
نقش سازنده (constructor) را برای پراکسی ایفا میکند. یعنی قرارداد پیاده سازی از طریق delegatecall
به آن داده ها دسترسی پیدا میکند و میتواند منطق خود را اجرا کرده و متغیرهای ذخیره سازی پراکسی را پیکربندی کند.
1 2 3 4 5 6 7 8 9 10 |
function upgradeBeaconToAndCall(address newBeacon, bytes memory data) internal { _setBeacon(newBeacon); emit IERC1967.BeaconUpgraded(newBeacon); if (data.length > 0) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } else { _checkNonPayable(); } } |
ارتباط ERC-1967 و BeaconProxy.sol
برای اینکه مرورگرهای بلاک (Block Explorer) بتوانند تشخیص دهند که یک قرارداد BeaconProxy
در واقع یک پراکسی است، این قرارداد باید مطابق با استاندارد ERC-1967 عمل کند. از آنجا که BeaconProxy
به طور خاص یک نوع Beacon پراکسی محسوب میشود، باید آدرس Beacon را در یک محل مشخص از فضای ذخیره سازی قرار دهد. این محل دقیقاً برابر است با:
1 |
0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50 |
این آدرس از محاسبه عبارت 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
منتقل میشوند تا مقداردهی اولیه انجام گیرد. پس از استقرار موفق پراکسی، این تابع آدرس قرارداد ایجادشده را برمیگرداند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; /// @dev THIS CONTRACT IS FOR TEACHING PURPOSES ONLY contract ExampleFactory { address public immutable beacon; constructor(address Beacon) { beacon = Beacon; } function createBeaconProxy(bytes memory data) external returns (address) { BeaconProxy proxy = new BeaconProxy(beacon, data); return address(proxy); } } |
اینها تمام قراردادهایی هستند که برای طراحی یک الگوی Beacon نیاز دارید:
-
قرارداد پیاده سازی (Implementation): حاوی منطق اصلی برنامه است که تمام پراکسی ها به آن متصل میشوند.
-
Beacon: آدرس قرارداد پیاده سازی را در خود نگه میدارد و به عنوان مرجع برای پراکسی ها عمل میکند.
-
Factory (اختیاری): ساخت پراکسی های جدید را سادهتر و ساختارمندتر میکند.
-
پراکسیها (Proxy): درخواستها را دریافت میکنند، از Beacon آدرس پیاده سازی را میگیرند و عملیات را به آن منتقل میکنند.
استقرار (Deploying)
شاید در نگاه اول پیاده سازی این سیستم کمی پیچیده به نظر برسد، اما واقعاً سادهتر از چیزی است که تصور میکنید. تیم OpenZeppelin برای هر دو فریمورک Hardhat و Foundry افزونهای به نام Upgrades Plugin منتشر کرده که روند استقرار و ارتقاء را بهشکل چشمگیری ساده میکند.
کافی است این کتابخانه را نصب کنید و سپس با استفاده از تابع deployBeacon()
، قرارداد Beacon را به همراه پارامترهای مورد نیاز مستقر کنید. پس از آن، میتوانید با فراخوانی deployBeaconProxy()
پراکسی های جدید را مستقر نمایید.
فرآیند ارتقاء نیز مشابه است؛ کافی است تابع upgradeBeacon()
را همراه با آدرس پیاده سازی جدید صدا بزنید تا کل سیستم به نسخه جدید منتقل شود. علاوه بر این، اگر ترجیح میدهید سیستم را بهصورت دستی مستقر کنید، میتوانید مراحل زیر را دنبال کنید:
-
قرارداد پیاده سازی را مستقر کنید
-
قرارداد Beacon را مستقر کنید و در سازنده آن، آدرس قرارداد پیاده سازی و آدرس حسابی را وارد کنید که اجازه ارتقاء دارد
-
قرارداد Factory را مستقر کنید
-
از 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 را با یکدیگر ترکیب کرده است تا بهینهسازی انجام دهد. این ترکیب، فرآیند راهاندازی را سادهتر میکند و سطح پیچیدگی کلی را کاهش میدهد.
این امکان وجود دارد چون لازم نیست فکتوری بهصورت یک قرارداد مستقل پیاده سازی شود؛ در واقع، تنها چند خط کد کافیست تا یک BeaconProxy جدید مستقر شود و آدرس بیکن و داده های مقداردهی اولیهاش تنظیم شود. در ادامه، یک نمونه از قرارداد ترکیبی فکتوری و بیکن ارائه شده است. با ارثبری از قرارداد UpgradeableBeacon، این قرارداد تمام قابلیتهای یک بیکن معمولی را حفظ میکند، در حالی که تابع createBeaconProxy()
نقش فکتوری را نیز به آن اضافه میکند. علاوهبراین، دیگر نیازی به ذخیره سازی آدرس بیکن وجود ندارد؛ زیرا میتوان مستقیماً از address(this)
استفاده کرد.
با این حال، ساختار کلی «Beacon» همچنان ثابت باقی مانده است. هر کاربر مستقیماً BeaconProxy خود را فراخوانی میکند؛ پروکسی که شامل تمام اطلاعات ذخیره سازی مربوط به بسته واگذاری خاص آن فرد است (از جمله میزان واگذاری و مدتزمان آن). این BeaconProxy، آدرس پیاده سازی را از FactoryBeacon دریافت میکند؛ همان ساختاری که همچنان عملکردی مشابه یک بیکن معمولی دارد. پس از دریافت آدرس پیاده سازی از FactoryBeacon، BeaconProxy فراخوانی delegatecall
را به قرارداد VestingBaseV2 انجام میدهد که تنها نقش پیاده سازی (implementation) را دارد.
نکته مهم این است که فقط موجودیت adminDAO (یک کیف پول چندامضایی مدیریتی) اجازه دارد FactoryBeacon را فراخوانی کند. تنها ادمین میتواند یک بسته واگذاری جدید (BeaconProxy) ایجاد کرده یا نسخه پیاده سازی پروکسی ها را ارتقاء دهد.
نتیجهگیری
الگوی Beacon Proxy این امکان را فراهم میکند که چندین پروکسی با استفاده از یک پیاده سازی ایجاد شوند و همه آنها بهصورت همزمان قابل ارتقاء باشند. فکتوری، پروکسی های جدید را مستقر میکند و این پروکسی ها با استفاده از delegatecall
به آدرسی که از بیکن دریافت شده، متصل میشوند. بیکن نقش منبع اصلی آدرس پیاده سازی را ایفا میکند.
نکته مهم این است که الگوی Beacon Proxy نسبت به الگوهایی مانند UUPS یا Transparent در مرحله راه اندازی، گس بیشتری مصرف میکند؛ زیرا علاوهبر هر پروکسی، باید یک فکتوری و یک بیکن نیز مستقر شود. همچنین هر بار فراخوانی به یک پروکسی، نیازمند فراخوانی اضافی به بیکن است که هزینه گس را افزایش میدهد. این افزایش مصرف گس، مهمترین نقطهضعف این الگو محسوب میشود.
با این حال، اگر پروژه به تعداد زیادی پروکسی نیاز داشته باشد، این هزینه اضافی توجیهپذیر است و مزیت اصلی این الگو نیز دقیقاً در چنین مواردی نمود پیدا میکند. به همین دلیل، معمولاً استفاده از Beacon Proxy زمانی توصیه میشود که بیش از یک پروکسی مورد نیاز باشد. هرچند این الگو امکان ارتقاء همزمان چندین پروکسی را فراهم میکند، اما راهاندازی آن پیچیدهتر و پرهزینهتر است. اجرای این ساختار نیازمند مصرف گس بیشتر و راهاندازی قراردادهای اضافه است که هزینه توسعه و حسابرسی (auditing) را نیز افزایش میدهد. بنابراین، الگوی Beacon Proxy تنها زمانی مزیت دارد که پروژه واقعاً به تعداد زیادی پروکسی نیاز داشته باشد.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۱۶ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس