الگوی Transparent Upgradeable Proxy در سالیدیتی یکی از روش های طراحی است که امکان ارتقای قرارداد پراکسی را فراهم می کند، بدون اینکه تداخلی در شناسه توابع (function selector) ایجاد شود.
برای اینکه یک پراکسی روی شبکه اتریوم عملکرد درستی داشته باشد، باید دو ویژگی کلیدی داشته باشد:
-
یک جایگاه مشخص در حافظه (storage slot) برای ذخیره آدرس قرارداد پیاده سازی
-
یک سازوکار که مدیر بتواند با استفاده از آن آدرس پیاده سازی را تغییر دهد
استاندارد ERC-1967 محل ذخیره آدرس قرارداد پیاده سازی را تعیین می کند. این استاندارد کمک می کند تا برخورد داده ها در حافظه (storage collision) به حداقل برسد. با این حال، استاندارد ERC-1967 روشی برای تغییر این آدرس مشخص نمی کند.
اگر تابعی مانند updateImplementation(address _newImplementation)
را مستقیماً در پراکسی قرار دهیم، ممکن است به مشکل برخورد کنیم. این تابع ممکن است با یکی از توابع موجود در قرارداد پیاده سازی تداخل داشته باشد. چنین تداخلی می تواند منجر به رفتارهای ناپایدار و ناخواسته شود.
تداخل در شناسه تابع (Function Selector Clashing)
اگر داخل قرارداد پراکسی توابع عمومی برای بهروزرسانی آدرس پیاده سازی تعریف کنیم، احتمال بروز تداخل در شناسه تابع (function selector) به وجود می آید.
برای درک بهتر، در ادامه یک مثال ساده را بررسی می کنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
contract ProxyUnsafe { function changeImplementation( address newImplementation ) public { // some code... } fallback(bytes calldata data) external payable (bytes memory) { (bool ok, bytes memory data) = getImplementation().delegatecall(data); require(ok, "delegatecall failed"); return data; } } contract Implementation { // an identical function is declared here -- they will clash function changeImplementation( address newImplementation ) public { } //... } |
به خاطر داشته باشید: فراخوانی fallback همیشه در آخر انجام می شود. قبل از اینکه تابع fallback فعال شود، قرارداد پراکسی ابتدا بررسی می کند که آیا شناسه تابع ۴ بایتی با changeImplementation
یا هر تابع عمومی دیگری که در پراکسی تعریف شده مطابقت دارد یا نه.
بنابراین، اگر یک تابع عمومی در قرارداد پراکسی تعریف کنیم، دو نوع تداخل در شناسه تابع ممکن است اتفاق بیفتد:
-
تداخل مستقیم در امضای تابع: اگر قرارداد پیاده سازی (implementation) تابعی با همان امضا (signature) داشته باشد، آن تابع عملاً قابل فراخوانی نخواهد بود. دلیلش این است که قرارداد پراکسی ابتدا تابع خودش را اجرا می کند، نه fallback. و اگر fallback فراخوانی نشود، هیچ فراخوانی از نوع delegatecall به قرارداد پیاده سازی صورت نمی گیرد.
-
تداخل اتفاقی در شناسه تابع: اگر قرارداد پیاده سازی تابعی داشته باشد که شناسه آن دقیقاً با شناسه یکی از توابع عمومی پراکسی برابر باشد، آن تابع نیز قابل اجرا نخواهد بود. دلیل این اتفاق آن است که شناسه تابع ممکن است بهصورت تصادفی یکسان شود، حتی اگر امضای تابع متفاوت باشد. وقتی دو تابع متفاوت شناسه یکسانی داشته باشند، احتمال وقوع چنین تداخلی حدود ۱ در ۴.۲۹ میلیارد خواهد بود. چون شناسه تابع از ۴ بایت تشکیل می شود، در مجموع حدود ۴.۲۹ میلیارد شناسه ممکن وجود دارد. این احتمال بسیار کم است، اما صفر نیست. برای نمونه، تابع
clash550254402()
همان شناسه ای را دارد که تابعproxyAdmin()
دارد.
این مسئله نشان میدهد که حتی توابعی که از نظر نام و ورودی با هم تفاوت دارند، اگر شناسه یکسانی داشته باشند، می توانند باعث اختلال در عملکرد پراکسی شوند.
الگوی Transparent Upgradeable Proxy چگونه بهطور کامل از تداخل در شناسه تابع جلوگیری می کند
الگوی Transparent Upgradeable Proxy بهگونهای عمل میکند که احتمال تداخل در شناسه توابع (Function Selector Clashing) را بهطور کامل از بین میبرد.
این الگو تصریح میکند که فقط تابع fallback باید در پراکسی بهصورت عمومی تعریف شود و هیچ تابع عمومی دیگری نباید وجود داشته باشد.
اما در این حالت، یک سوال پیش میآید:
وقتی فقط یک تابع fallback داریم، چطور میتوانیم تابع ارتقای پراکسی را فراخوانی کنیم؟
پاسخ این است که با بررسی msg.sender
مشخص میکنیم آیا فراخوانی توسط مدیر (admin) انجام شده یا نه.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
contract Proxy is ERC1967 { address immutable admin; constructor(address admin_) { admin = admin_ } fallback() external payable { if (msg.sender == admin) { // upgrade logic } else { // delegatecall to implementation } } } |
پیامد این ساختار آن است که مدیر (admin) نمی تواند مستقیماً از پراکسی استفاده کند، چون تمام فراخوانی های او به جای اینکه از طریق delegatecall
به قرارداد پیاده سازی هدایت شوند، از مسیر دیگری عبور می کنند. با این حال، یک مکانیزم دیگر وجود دارد که در ادامه به آن میپردازیم. با استفاده از آن، مدیر همچنان میتواند پراکسی را فراخوانی کند و پراکسی نیز مانند یک تراکنش عادی، درخواست را به قرارداد پیاده سازی منتقل کند.
مدیر تغییرناپذیر (Immutable Admin)
در قطعه کدی که پیشتر بررسی کردیم، مدیر (admin) بهصورت تغییرناپذیر (immutable) تعریف شده است. این یعنی از نظر فنی، قرارداد با استاندارد ERC-1967 کاملاً همراستا نیست. طبق این استاندارد، آدرس مدیر باید در یک جایگاه خاص از حافظه ذخیره شود:
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
یا بهصورت معادل محاسباتی:bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
برای حفظ سازگاری با ERC-1967، پراکسی Transparent Upgradeable آدرس مدیر را در همین جایگاه حافظه ذخیره میکند، اما عملاً از این متغیر استفاده نمیکند.
وجود یک آدرس در این جایگاه، به ابزارهای تحلیل زنجیره (مثل اکسپلوررها) نشان میدهد که این قرارداد یک پراکسی است؛ که یکی از اهداف استاندارد ERC-1967 نیز دقیقاً همین بوده است. با این حال، اگر پراکسی بخواهد در هر فراخوانی این مقدار را از حافظه بخواند، حدود ۲۱۰۰ گس به هر فراخوانی اضافه میشود. به همین دلیل، استفاده از یک متغیر تغییرناپذیر (immutable) برای مدیر از نظر مصرف گس بهمراتب بهینهتر است.
تغییر مدیر پراکسی
اگرچه توسعه دهنده، متغیر مدیر را بهصورت تغییرناپذیر (immutable) تعریف میکند، اما همچنان باید راهی برای تغییر آدرس مدیر در صورت نیاز وجود داشته باشد. در نگاه اول، این کار غیرممکن به نظر میرسد، چون پراکسی از متغیر immutable برای تعیین مدیر استفاده میکند.
اما الگوی Transparent Upgradeable Proxy برای حل این مشکل، از یک راهکار دو مرحلهای استفاده میکند. در گام نخست، بهجای یک آدرس معمولی، یک قرارداد دیگر به نام ProxyAdmin
بهعنوان مدیر پراکسی معرفی میشود.
از آنجا که آدرس یک قرارداد هوشمند هرگز تغییر نمیکند، میتوان بدون مشکل، آدرس مدیر را بهصورت یک متغیر تغییرناپذیر (immutable) در پراکسی ذخیره کرد. این ویژگی کاملاً با معماری Transparent Upgradeable Proxy سازگار است.
در مرحله دوم، قرارداد ProxyAdmin
به عنوان واسط عمل میکند و مالک آن به عنوان “مدیر واقعی” شناخته میشود. این قرارداد، فراخوانی های مالک را دریافت میکند و آنها را به پراکسی منتقل میسازد. در واقع، مدیر واقعی ابتدا با ProxyAdmin
ارتباط برقرار میکند و سپس این قرارداد، درخواستها را به پراکسی منتقل میکند. به همین دلیل، اگر مالکیت ProxyAdmin
را تغییر دهیم، در عمل فردی را که اختیار ارتقای پراکسی را دارد تغییر دادهایم. این ساختار باعث میشود مدیریت پراکسی انعطافپذیر باقی بماند، بدون آنکه نیاز به بازنویسی متغیر immutable در خود پراکسی داشته باشیم.
AdminProxy
در ادامه، کد مربوط بهAdminProxy
از کتابخانه OpenZeppelin را مشاهده میکنید (توضیحات کامنتی حذف شدهاند). نکته مهم این است که این قرارداد تنها یک تابع دارد: upgradeAndCall()
.
این تابع فقط میتواند متد upgradeToAndCall()
را روی قرارداد پراکسی فراخوانی کند و هیچ کار دیگری انجام نمیدهد. این طراحی ساده و محدود باعث میشود کنترل ارتقای قرارداد پراکسی تنها از طریق همین مسیر مشخص انجام شود و از هرگونه دسترسی ناخواسته یا پیچیدگی اضافی جلوگیری گردد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pragma solidity ^0.8.20; import {ITransparentUpgradeableProxy} from "./TransparentUpgradeableProxy.sol"; import {Ownable} from "../../access/Ownable.sol"; contract ProxyAdmin is Ownable { string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; constructor(address initialOwner) Ownable(initialOwner) {} function upgradeAndCall( ITransparentUpgradeableProxy proxy, address implementation, bytes memory data ) public payable virtual onlyOwner { proxy.upgradeToAndCall{value: msg.value}(implementation, data); } } |
یک باور نادرست رایج وجود دارد که میگوید مدیر (admin) در الگوی Transparent Proxy نمیتواند از قرارداد استفاده کند، چون فراخوانی های او بهجای اجرای تابع، به عملیات ارتقا هدایت میشوند. اما در واقع، مالک قرارداد AdminProxy میتواند بدون هیچ مشکلی از پراکسی استفاده کند؛ همانطور که دیاگرام زیر نشان میدهد.
در ادامه نیز خواهیم دید که یک مکانیزم مشخص برای انجام فراخوانی دلخواه به پراکسی از طریق ProxyAdmin وجود دارد. این قابلیت دقیقاً همان چیزی است که نام تابع upgradeToAndCall()
به آن اشاره میکند.
غیرقابل ارتقا کردن پراکسی
اگر مالک (owner) قرارداد ProxyAdmin
به آدرس صفر (address(0)
) یا به یک قرارداد هوشمند دیگر تغییر پیدا کند که نتواند بهدرستی از تابع upgradeAndCall()
استفاده کند یا مالکیت را تغییر دهد، در آن صورت پراکسی transparent دیگر قابل ارتقا نخواهد بود.
برای مثال، اگر مالک AdminProxy
را طوری تنظیم کنیم که خودش یک قرارداد AdminProxy
دیگر باشد، این وضعیت بهوجود میآید. در چنین شرایطی، مسیر ارتقا مسدود میشود و عملاً پراکسی به حالت غیرقابل تغییر درمیآید. این ویژگی میتواند بهعنوان یک اقدام امنیتی نهایی برای قفل کردن وضعیت قرارداد استفاده شود.
جزئیات پیاده سازی
در کتابخانه OpenZeppelin، الگوی Transparent Upgradeable Proxy با استفاده از سه قرارداد بهصورت لایهلایه پیاده سازی شده است:
-
Proxy.sol
-
ERC1967Proxy.sol
-
TransparentUpgradeableProxy.sol
این ساختار سلسلهمراتبی باعث میشود هر بخش از عملکرد پراکسی بهصورت جداگانه، ماژولار و قابل نگهداری باقی بماند.
پایهایترین قرارداد: Proxy.sol
قرارداد پایه در این ساختار، Proxy.sol
است. این قرارداد با دریافت آدرس پیاده سازی (implementation)، فراخوانیها را با استفاده از دستور delegatecall
به آن منتقل میکند. تابع _implementation()
که وظیفه مشخصکردن آدرس مقصد برای delegatecall
را دارد، در Proxy.sol
فقط تعریف شده اما پیاده سازی نشده است. این تابع در قرارداد فرزند یعنی ERC1967Proxy
بازنویسی (override) و پیاده سازی میشود تا مقدار آدرس پیاده سازی را از جایگاه حافظه مرتبط با استاندارد ERC-1967 برگرداند. این طراحی اجازه میدهد که قرارداد پایه بدون وابستگی به جزئیات ذخیرهسازی عمل کند و قراردادهای فرزند کنترل کامل بر مکانیزم پیاده سازی داشته باشند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
abstract contract Proxy { function _delegate(address implementation) internal virtual { assembly { calldatacopy(0, 0, calldatasize()) let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) returndatacopy(0, 0, returndatasize()) switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } function _implementation() internal view virtual returns (address); function _fallback() internal virtual { _delegate(_implementation()); } fallback() external payable virtual { _fallback(); } } |
فرزند Proxy.sol: قرارداد ERC1967Proxy.sol
قرارداد ERC1967Proxy.sol از Proxy.sol
ارثبری میکند و عملکردهای مرتبط با استاندارد ERC-1967 را به آن اضافه میکند. مهمترین تغییر در این قرارداد، پیاده سازی تابع داخلی _implementation()
است که در قرارداد پایه فقط بهصورت تعریفشده وجود داشت. در این نسخه، این تابع مقدار آدرسی را باز میگرداند که در جایگاه حافظهای ذخیره شده که استاندارد ERC-1967 تعیین کرده است. سازنده (constructor) این قرارداد نیز آدرس پیاده سازی را در همان جایگاه حافظه مشخصشده توسط ERC-1967 ذخیره میکند.
با این حال، پراکسی Transparent Upgradeable از این تابع استفاده نمیکند. در عوض، آدرس پیاده سازی را با استفاده از یک متغیر تغییرناپذیر (immutable variable) مدیریت میکند تا عملکرد بهینهتر و امنیت بالاتری داشته باشد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
pragma solidity ^0.8.20; import {Proxy} from "../Proxy.sol"; import {ERC1967Utils} from "./ERC1967Utils.sol"; contract ERC1967Proxy is Proxy { constructor(address implementation, bytes memory _data) payable { ERC1967Utils.upgradeToAndCall(implementation, _data); } // reads from bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) function _implementation() internal view virtual override returns (address) { return ERC1967Utils.getImplementation(); } } |
فرزند ERC1967Proxy.sol: قرارداد TransparentUpgradeableProxy.sol
در نهایت، قرارداد TransparentUpgradeableProxy.sol از ERC1967Proxy.sol
ارثبری میکند و منطق نهایی الگوی پراکسی شفاف را پیاده سازی میکند. در سازنده (constructor) این قرارداد، ابتدا یک نمونه از ProxyAdmin
مستقر میشود (deploy)، و سپس اولین متغیر قرارداد که بهصورت تغییرناپذیر (immutable) تعریف شده، یعنی admin، به آدرس همین ProxyAdmin
اختصاص داده میشود.
با این کار، پراکسی میتواند رفتار متفاوتی برای مدیر (admin) و کاربران عادی داشته باشد. همچنین استفاده از متغیر immutable برای admin باعث بهینه شدن مصرف گس و افزایش امنیت قرارداد میشود، چرا که آدرس admin در لحظه استقرار ثابت و غیرقابل تغییر باقی میماند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
contract TransparentUpgradeableProxy is ERC1967Proxy { address private immutable _admin; error ProxyDeniedAdminAccess(); constructor(address _logic, address initialOwner, bytes memory _data) payable ERC1967Proxy(_logic, _data) { _admin = address(new ProxyAdmin(initialOwner)); // Set the storage value and emit an event for ERC-1967 compatibility ERC1967Utils.changeAdmin(_proxyAdmin()); } function _proxyAdmin() internal view virtual returns (address) { return _admin; } function _fallback() internal virtual override { if (msg.sender == _proxyAdmin()) { if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) { revert ProxyDeniedAdminAccess(); } else { _dispatchUpgradeToAndCall(); } } else { super._fallback(); } } function _dispatchUpgradeToAndCall() private { (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); ERC1967Utils.upgradeToAndCall(newImplementation, data); } } |
فرض کنیم msg.sender
برابر با _proxyAdmin
باشد. در این حالت، فراخوانی به تابع داخلی _dispatchUpgradeToAndCall()
هدایت میشود. اما پیش از آن، تابع _fallback()
بررسی میکند که آیا شناسه تابع (selector) ارسالشده با شناسه تابع upgradeToAndCall
مطابقت دارد یا نه. نکته مهم اینجاست که این «شناسه تابع» در واقع یک شناسه واقعی نیست؛ چرا که پراکسی شفاف هیچ تابع عمومی بهجز fallback
ندارد. با این حال، برای اینکه ProxyAdmin
بتواند یک فراخوانی سطح بالا (high-level) از طریق اینترفیس سالیدیتی به تابع upgradeToAndCall
انجام دهد، پراکسی باید داده های calldata
کدگذاریشده مطابق ABI مربوط به upgradeToAndCall()
را بپذیرد.
به یاد داشته باشید که ProxyAdmin
در حال انجام یک فراخوانی اینترفیس به تابع upgradeToAndCall
در داخل پراکسی است، حتی با اینکه پراکسی عملاً هیچ تابع عمومی به جز fallback
ندارد. در ادامه کد ProxyAdmin
نمایش داده میشود تا نحوه انجام این فراخوانی دقیقتر بررسی شود:
در ادامه، ویدیویی نمایش داده میشود که هر سه بخش کد را بهصورت کنار هم نشان میدهد و دقیقاً توضیح میدهد که چگونه قراردادهای مختلف در زنجیره ارثبری — یعنی Proxy
، ERC1967Proxy
و TransparentUpgradeableProxy
— با یکدیگر تعامل دارند:
چرا از upgradeToAndCall()
استفاده میکنیم و نه فقط upgradeTo()
؟
وقتی میخواهیم قرارداد پیاده سازی (implementation) را ارتقا دهیم، این امکان وجود دارد که همزمان با ارتقا، یک فراخوانی هم به قرارداد جدید انجام دهیم — به شکلی که انگار ProxyAdmin
فرستنده (msg.sender
) تراکنش بوده و آن فراخوانی از طریق delegatecall
به قرارداد جدید منتقل شده است، درست مانند یک تعامل عادی با پراکسی. اما این رفتار درون fallback
اتفاق نمیافتد، چون فراخوانی های مربوط به ProxyAdmin
به منطق ارتقا هدایت میشوند، نه به مسیر معمول delegatecall
.
به همین دلیل از تابع upgradeToAndCall()
استفاده میشود. این تابع نهتنها آدرس قرارداد پیاده سازی را بهروزرسانی میکند، بلکه بلافاصله پس از آن، یک فراخوانی (call) به قرارداد جدید نیز انجام میدهد — برای مثال، جهت مقداردهی اولیه (initialize) یا تنظیم پارامترهای مورد نیاز.
در کتابخانه ERC1967Utils.sol که در ساختار TransparentUpgradeableProxy
نیز استفاده میشود، این قابلیت از طریق یک تابع داخلی فراهم میشود. این تابع، جایگاه حافظه مربوط به آدرس پیاده سازی را بهروزرسانی میکند و امکان مدیریت استاندارد و ایمن فرآیند ارتقا را فراهم میسازد.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/** * @dev Performs implementation upgrade with additional setup call if data is nonempty. * This function is payable only if the setup call is performed, otherwise `msg.value` is rejected * to avoid stuck value in the contract. * * Emits an {IERC1967-Upgraded} event. */ function upgradeToAndCall(address newImplementation, bytes memory data) internal { _setImplementation(newImplementation); emit IERC1967.Upgraded(newImplementation); if (data.length > 0) { Address.functionDelegateCall(newImplementation, data); } else { _checkNonPayable(); } } |
جزئیات فنی تابع upgradeToAndCall()
تابع upgradeToAndCall()
تنها زمانی یک delegatecall
به قرارداد پیاده سازی انجام میدهد که طول داده (data.length
) بیشتر از صفر باشد. این یعنی اگر هیچ دادهای همراه درخواست ارسال نشود، صرفاً آدرس پیاده سازی بهروزرسانی میشود و فراخوانیای به آن صورت نمیگیرد.
اما زمانی که دادهای وجود داشته باشد، این تابع در همان تراکنش، پس از ارتقای پیاده سازی، بلافاصله یک delegatecall
به قرارداد جدید انجام میدهد. در واقع، رفتار این تابع دقیقاً مشابه حالتی است که ProxyAdmin
پراکسی را با یک calldata
خاص فراخوانی کند، و سپس پراکسی نیز آن فراخوانی را با delegatecall
به قرارداد پیاده سازی منتقل کند.
نتیجه این طراحی چیست؟
به این ترتیب، قرارداد ProxyAdmin میتواند هرگونه فراخوانی دلخواه (arbitrary call) را از طریق پراکسی به قرارداد پیاده سازی انجام دهد. این قابلیت در بسیاری از پروژههای پیشرفته که در حوزه آموزش برنامه نویسی و طراحی الگوهای پیشرفته در سالیدیتی مطرح هستند،
نکته مهم این است که تابع upgradeToAndCall()
نیازی ندارد که قرارداد جدید، واقعاً متفاوت از نسخه قبلی باشد. میتوان حتی به همان نسخه قبلی “ارتقا” داد. این ویژگی، آزادی عمل زیادی به توسعه دهنده میدهد — برای مثال در اجرای مجدد توابع initialize
.
در این فرایند، از دید قرارداد پراکسی، msg.sender
همان ProxyAdmin
خواهد بود. این یعنی تمام فراخوانیها از طرف ProxyAdmin
تلقی میشوند، نه کاربر عادی.
آیا این یک مشکل امنیتی است؟
خیر، این موضوع مشکلی ایجاد نمیکند. زیرا ProxyAdmin از ابتدا دارای دسترسی کامل برای تغییر قرارداد پیاده سازی بوده است. بنابراین امکان فراخوانی مستقیم توابع از طریق پراکسی همراستا با سطح دسترسی آن است. مالک ProxyAdmin
بهطور کامل کنترل قرارداد پراکسی را در اختیار دارد.
تنها محدودیت
تابع _setImplementation
که مسئول ثبت قرارداد پیاده سازی جدید است، بررسی میکند که آدرس ارسالی حاوی کد باشد. بهعبارت دیگر، پراکسی اجازه ندارد به قراردادی ارتقا پیدا کند که هیچ بایتکدی نداشته باشد (یعنی یک آدرس خالی یا اشتباه). این بررسی با چک کردن code.length > 0
انجام میشود.
1 2 3 4 5 6 7 8 9 |
/** * @dev Stores a new address in the ERC-1967 implementation slot. */ function _setImplementation(address newImplementation) private { if (newImplementation.code.length == 0) { revert ERC1967InvalidImplementation(newImplementation); } StorageSlot.getAddressSlot(IMPLEMENTATION_SLOT).value = newImplementation; } |
خلاصه الگوی Transparent Upgradeable Proxy
- الگوی Transparent Upgradeable Proxy با هدف جلوگیری از تداخل در شناسه توابع (function selector clashing) میان پراکسی و قرارداد پیاده سازی ایجاد شده است.
- در این ساختار، تنها تابع عمومی موجود در قرارداد پراکسی، همان تابع
fallback
است. تمامی فراخوانی ها از سوی آدرس هایی غیر از مدیر (admin) بهصورتdelegatecall
به قرارداد پیاده سازی منتقل میشوند. - امکان ارتقای قرارداد تنها از طریق همین تابع
fallback
و فقط توسط مدیر (admin) وجود دارد. - برای کاهش مصرف گس، آدرس مدیر بهصورت یک متغیر تغییرناپذیر (immutable) در پراکسی ذخیره میشود. توسعه دهنده، برای رعایت سازگاری با استاندارد ERC-1967، آدرس مدیر را در جایگاه حافظه تعیینشده توسط این استاندارد نیز ثبت میکند — اگرچه پراکسی هیچگاه این مقدار را نمیخواند.
- از آنجایی که آدرس admin قابل تغییر نیست، یک قرارداد هوشمند به نام
AdminProxy
بهعنوان مدیر پراکسی تعیین میشود. این قرارداد تنها یک تابع عمومی به نامupgradeAndCall()
دارد که فقط توسط مالکAdminProxy
قابل اجراست. آدرس مالکAdminProxy
قابل تغییر است و این یعنی میتوان کنترل ارتقای پراکسی را با تغییر مالکیت آن جابهجا کرد. در نتیجه، هر کسی که مالکAdminProxy
باشد، میتواند آدرس قرارداد پیاده سازی درTransparentUpgradeableProxy
را بهروزرسانی کند.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۱۵ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس