Optimism معیاری برج کنٹریکٹ واک تھرو
Optimismopens in a new tab ایک Optimistic Rollup ہے۔ Optimistic rollups ٹرانزیکشنز کو Ethereum Mainnet (جسے layer 1 یا L1 بھی کہا جاتا ہے) کے مقابلے میں بہت کم قیمت پر پراسیس کر سکتے ہیں کیونکہ ٹرانزیکشنز کو نیٹ ورک پر ہر نوڈ کے بجائے صرف چند نوڈز کے ذریعے پراسیس کیا جاتا ہے۔ ایک ہی وقت میں، تمام ڈیٹا L1 پر لکھا جاتا ہے تاکہ Mainnet کی تمام سالمیت اور دستیابی کی ضمانتوں کے ساتھ ہر چیز کو ثابت اور دوبارہ تعمیر کیا جا سکے۔
Optimism (یا کسی دوسرے L2) پر L1 اثاثے استعمال کرنے کے لیے، اثاثوں کو bridged کرنے کی ضرورت ہے۔ اس کو حاصل کرنے کا ایک طریقہ یہ ہے کہ صارفین L1 پر اثاثوں (ETH اور ERC-20 tokens سب سے عام ہیں) کو لاک کریں، اور L2 پر استعمال کرنے کے لیے مساوی اثاثے حاصل کریں۔ بالآخر، جو بھی ان کے ساتھ ختم ہوتا ہے وہ انہیں واپس L1 پر برج کرنا چاہے گا۔ ایسا کرتے وقت، اثاثے L2 پر جلا دیے جاتے ہیں اور پھر L1 پر صارف کو واپس جاری کر دیے جاتے ہیں۔
یہ وہ طریقہ ہے جس سے Optimism standard bridgeopens in a new tab کام کرتا ہے۔ اس مضمون میں ہم اس برج کے سورس کوڈ کا جائزہ لیتے ہیں تاکہ یہ دیکھیں کہ یہ کیسے کام کرتا ہے اور اسے اچھی طرح سے لکھے گئے Solidity کوڈ کی مثال کے طور پر مطالعہ کریں۔
کنٹرول فلو
برج کے دو اہم فلو ہیں:
- ڈپازٹ (L1 سے L2 تک)
- واپسی (L2 سے L1 تک)
ڈپازٹ فلو
لیئر 1
- اگر ERC-20 جمع کر رہے ہیں، تو جمع کنندہ برج کو جمع کی جا رہی رقم خرچ کرنے کی اجازت دیتا ہے
- جمع کنندہ L1 برج کو کال کرتا ہے (
depositERC20,depositERC20To,depositETH, یاdepositETHTo) - L1 برج برجڈ اثاثے کا قبضہ لیتا ہے
- ETH: اثاثہ کال کے حصے کے طور پر جمع کنندہ کے ذریعے منتقل کیا جاتا ہے
- ERC-20: اثاثہ برج کے ذریعے جمع کنندہ کی طرف سے فراہم کردہ الاؤنس کا استعمال کرتے ہوئے خود کو منتقل کیا جاتا ہے
- L1 برج L2 برج پر
finalizeDepositکو کال کرنے کے لیے کراس ڈومین میسج میکانزم کا استعمال کرتا ہے
لیئر 2
- L2 برج
finalizeDepositکی کال کی تصدیق کرتا ہے کہ یہ جائز ہے:- کراس ڈومین میسج کنٹریکٹ سے آیا ہے
- اصل میں L1 پر برج سے تھا
- L2 برج چیک کرتا ہے کہ آیا L2 پر ERC-20 ٹوکن کنٹریکٹ درست ہے:
- L2 کنٹریکٹ رپورٹ کرتا ہے کہ اس کا L1 ہم منصب وہی ہے جہاں سے L1 پر ٹوکن آئے تھے
- L2 کنٹریکٹ رپورٹ کرتا ہے کہ یہ درست انٹرفیس کو سپورٹ کرتا ہے (using ERC-165opens in a new tab).
- اگر L2 کنٹریکٹ درست ہے، تو اسے مناسب ایڈریس پر مناسب تعداد میں ٹوکن منٹ کرنے کے لیے کال کریں۔ اگر نہیں، تو صارف کو L1 پر ٹوکن کا دعویٰ کرنے کی اجازت دینے کے لیے واپسی کا عمل شروع کریں۔
واپسی کا فلو
لیئر 2
- واپس لینے والا L2 برج (
withdrawیاwithdrawTo) کو کال کرتا ہے - L2 برج
msg.senderسے تعلق رکھنے والے ٹوکنز کی مناسب تعداد کو جلا دیتا ہے - L2 برج کراس ڈومین میسج میکانزم کا استعمال L1 برج پر
finalizeETHWithdrawalیاfinalizeERC20Withdrawalکو کال کرنے کے لیے کرتا ہے
لیئر 1
- L1 برج
finalizeETHWithdrawalیاfinalizeERC20Withdrawalکی کال کی تصدیق کرتا ہے کہ یہ جائز ہے:- کراس ڈومین میسج میکانزم سے آیا ہے
- اصل میں L2 پر برج سے تھا
- L1 برج مناسب اثاثہ (ETH یا ERC-20) کو مناسب ایڈریس پر منتقل کرتا ہے
لیئر 1 کوڈ
یہ وہ کوڈ ہے جو L1، Ethereum Mainnet پر چلتا ہے۔
IL1ERC20Bridge
یہ انٹرفیس یہاں بیان کیا گیا ہےopens in a new tab۔ اس میں ERC-20 ٹوکنز کو برج کرنے کے لیے درکار فنکشنز اور تعریفیں شامل ہیں۔
1// SPDX-License-Identifier: MITOptimism کا زیادہ تر کوڈ MIT لائسنس کے تحت جاری کیا گیا ہےopens in a new tab.
1pragma solidity >0.5.0 <0.9.0;لکھنے کے وقت Solidity کا تازہ ترین ورژن 0.8.12 ہے۔ جب تک ورژن 0.9.0 جاری نہیں ہو جاتا، ہم نہیں جانتے کہ یہ کوڈ اس کے ساتھ مطابقت رکھتا ہے یا نہیں۔
1/**2 * @title IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * Events *7 **********/89 event ERC20DepositInitiated(سب دکھائیںOptimism برج کی اصطلاح میں ڈپازٹ کا مطلب L1 سے L2 میں منتقلی ہے، اور واپسی کا مطلب L2 سے L1 میں منتقلی ہے۔
1 address indexed _l1Token,2 address indexed _l2Token,زیادہ تر معاملات میں L1 پر ERC-20 کا ایڈریس L2 پر مساوی ERC-20 کے ایڈریس کے برابر نہیں ہوتا ہے۔
آپ ٹوکن ایڈریس کی فہرست یہاں دیکھ سکتے ہیںopens in a new tab۔
chainId 1 والا ایڈریس L1 (Mainnet) پر ہے اور chainId 10 والا ایڈریس L2 (Optimism) پر ہے۔
دیگر دو chainId قدریں Kovan ٹیسٹ نیٹ ورک (42) اور Optimistic Kovan ٹیسٹ نیٹ ورک (69) کے لیے ہیں۔
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );منتقلی میں نوٹس شامل کرنا ممکن ہے، اس صورت میں انہیں ان ایونٹس میں شامل کیا جاتا ہے جو ان کی رپورٹ کرتے ہیں۔
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );وہی برج کنٹریکٹ دونوں سمتوں میں منتقلی کو سنبھالتا ہے۔ L1 برج کے معاملے میں، اس کا مطلب ہے ڈپازٹ کا آغاز اور واپسی کی حتمی شکل۔
12 /********************3 * Public Functions *4 ********************/56 /**7 * @dev get the address of the corresponding L2 bridge contract.8 * @return Address of the corresponding L2 bridge contract.9 */10 function l2TokenBridge() external returns (address);سب دکھائیںاس فنکشن کی واقعی ضرورت نہیں ہے، کیونکہ L2 پر یہ ایک پہلے سے تعینات کنٹریکٹ ہے، لہذا یہ ہمیشہ ایڈریس 0x4200000000000000000000000000000000000010 پر ہوتا ہے۔
یہاں L2 برج کے ساتھ ہم آہنگی کے لیے ہے، کیونکہ L1 برج کا ایڈریس جاننا معمولی بات نہیں ہے۔
1 /**2 * @dev deposit an amount of the ERC20 to the caller's balance on L2.3 * @param _l1Token Address of the L1 ERC20 we are depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @param _amount Amount of the ERC20 to deposit6 * @param _l2Gas Gas limit required to complete the deposit on L2.7 * @param _data Optional data to forward to L2. This data is provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * length, these contracts provide no guarantees about its content.10 */11 function depositERC20(12 address _l1Token,13 address _l2Token,14 uint256 _amount,15 uint32 _l2Gas,16 bytes calldata _data17 ) external;سب دکھائیں_l2Gas پیرامیٹر L2 گیس کی وہ مقدار ہے جو ٹرانزیکشن خرچ کرنے کی اجازت ہے۔
ایک مخصوص (اعلی) حد تک، یہ مفت ہےopens in a new tab، لہذا جب تک ERC-20 کنٹریکٹ منٹنگ کے وقت کچھ واقعی عجیب کام نہیں کرتا، یہ کوئی مسئلہ نہیں ہونا چاہئے۔
یہ فنکشن عام منظر نامے کا خیال رکھتا ہے، جہاں ایک صارف ایک مختلف بلاک چین پر ایک ہی ایڈریس پر اثاثوں کو برج کرتا ہے۔
1 /**2 * @dev deposit an amount of ERC20 to a recipient's balance on L2.3 * @param _l1Token Address of the L1 ERC20 we are depositing4 * @param _l2Token Address of the L1 respective L2 ERC205 * @param _to L2 address to credit the withdrawal to.6 * @param _amount Amount of the ERC20 to deposit.7 * @param _l2Gas Gas limit required to complete the deposit on L2.8 * @param _data Optional data to forward to L2. This data is provided9 * solely as a convenience for external contracts. Aside from enforcing a maximum10 * length, these contracts provide no guarantees about its content.11 */12 function depositERC20To(13 address _l1Token,14 address _l2Token,15 address _to,16 uint256 _amount,17 uint32 _l2Gas,18 bytes calldata _data19 ) external;سب دکھائیںیہ فنکشن تقریباً depositERC20 جیسا ہی ہے، لیکن یہ آپ کو ERC-20 کو ایک مختلف ایڈریس پر بھیجنے دیتا ہے۔
1 /*************************2 * Cross-chain Functions *3 *************************/45 /**6 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the7 * L1 ERC20 token.8 * This call will fail if the initialized withdrawal from L2 has not been finalized.9 *10 * @param _l1Token Address of L1 token to finalizeWithdrawal for.11 * @param _l2Token Address of L2 token where withdrawal was initiated.12 * @param _from L2 address initiating the transfer.13 * @param _to L1 address to credit the withdrawal to.14 * @param _amount Amount of the ERC20 to deposit.15 * @param _data Data provided by the sender on L2. This data is provided16 * solely as a convenience for external contracts. Aside from enforcing a maximum17 * length, these contracts provide no guarantees about its content.18 */19 function finalizeERC20Withdrawal(20 address _l1Token,21 address _l2Token,22 address _from,23 address _to,24 uint256 _amount,25 bytes calldata _data26 ) external;27}سب دکھائیںOptimism میں واپسی (اور L2 سے L1 تک کے دیگر پیغامات) دو مرحلوں کا عمل ہے:
- L2 پر ایک ابتدائی ٹرانزیکشن۔
- L1 پر ایک حتمی یا دعویٰ کرنے والی ٹرانزیکشن۔ یہ ٹرانزیکشن L2 ٹرانزیکشن کے لیے fault challenge periodopens in a new tab کے ختم ہونے کے بعد ہونا ضروری ہے۔
IL1StandardBridge
یہ انٹرفیس یہاں بیان کیا گیا ہےopens in a new tab۔
اس فائل میں ETH کے لیے ایونٹ اور فنکشن کی تعریفیں ہیں۔
یہ تعریفیں ERC-20 کے لیے اوپر IL1ERC20Bridge میں بیان کردہ تعریفوں سے بہت ملتی جلتی ہیں۔
برج انٹرفیس کو دو فائلوں کے درمیان تقسیم کیا گیا ہے کیونکہ کچھ ERC-20 ٹوکنز کو اپنی مرضی کے مطابق پروسیسنگ کی ضرورت ہوتی ہے اور معیاری برج کے ذریعے انہیں ہینڈل نہیں کیا جا سکتا۔
اس طرح کسٹم برج جو ایسے ٹوکن کو ہینڈل کرتا ہے وہ IL1ERC20Bridge کو لاگو کر سکتا ہے اور اسے ETH کو بھی برج کرنے کی ضرورت نہیں ہوتی ہے۔
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34import "./IL1ERC20Bridge.sol";56/**7 * @title IL1StandardBridge8 */9interface IL1StandardBridge is IL1ERC20Bridge {10 /**********11 * Events *12 **********/13 event ETHDepositInitiated(14 address indexed _from,15 address indexed _to,16 uint256 _amount,17 bytes _data18 );سب دکھائیںیہ ایونٹ تقریباً ERC-20 ورژن (ERC20DepositInitiated) جیسا ہی ہے، سوائے L1 اور L2 ٹوکن ایڈریس کے۔
دیگر ایونٹس اور فنکشنز کے لیے بھی یہی سچ ہے۔
1 event ETHWithdrawalFinalized(2 .3 .4 .5 );67 /********************8 * Public Functions *9 ********************/1011 /**12 * @dev Deposit an amount of the ETH to the caller's balance on L2.13 .14 .15 .16 */17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;1819 /**20 * @dev Deposit an amount of ETH to a recipient's balance on L2.21 .22 .23 .24 */25 function depositETHTo(26 address _to,27 uint32 _l2Gas,28 bytes calldata _data29 ) external payable;3031 /*************************32 * Cross-chain Functions *33 *************************/3435 /**36 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the37 * L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called38 * before the withdrawal is finalized.39 .40 .41 .42 */43 function finalizeETHWithdrawal(44 address _from,45 address _to,46 uint256 _amount,47 bytes calldata _data48 ) external;49}سب دکھائیںCrossDomainEnabled
یہ کنٹریکٹopens in a new tab دونوں برجز (L1 اور L2) کے ذریعے وراثت میں ملا ہے تاکہ دوسری لیئر کو پیغامات بھیج سکیں۔
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* Interface Imports */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";یہ انٹرفیسopens in a new tab کنٹریکٹ کو بتاتا ہے کہ کراس ڈومین میسنجر کا استعمال کرتے ہوئے دوسری لیئر کو پیغامات کیسے بھیجیں۔ یہ کراس ڈومین میسنجر ایک بالکل مختلف نظام ہے، اور اس کا اپنا مضمون ہونا چاہیے، جو مجھے امید ہے کہ مستقبل میں لکھوں گا۔
1/**2 * @title CrossDomainEnabled3 * @dev Helper contract for contracts performing cross-domain communications4 *5 * Compiler used: defined by inheriting contract6 */7contract CrossDomainEnabled {8 /*************9 * Variables *10 *************/1112 // Messenger contract used to send and receive messages from the other domain.13 address public messenger;1415 /***************16 * Constructor *17 ***************/1819 /**20 * @param _messenger Address of the CrossDomainMessenger on the current layer.21 */22 constructor(address _messenger) {23 messenger = _messenger;24 }سب دکھائیںایک پیرامیٹر جو کنٹریکٹ کو جاننے کی ضرورت ہے، وہ اس لیئر پر کراس ڈومین میسنجر کا ایڈریس ہے۔ یہ پیرامیٹر ایک بار کنسٹرکٹر میں سیٹ کیا جاتا ہے، اور کبھی تبدیل نہیں ہوتا ہے۔
12 /**********************3 * Function Modifiers *4 **********************/56 /**7 * Enforces that the modified function is only callable by a specific cross-domain account.8 * @param _sourceDomainAccount The only account on the originating domain which is9 * authenticated to call this function.10 */11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {سب دکھائیںکراس ڈومین میسجنگ بلاک چین پر کسی بھی کنٹریکٹ کے ذریعے قابل رسائی ہے جہاں یہ چل رہا ہے (یا تو Ethereum mainnet یا Optimism)۔ لیکن ہمیں ہر طرف برج کی ضرورت ہے تاکہ وہ صرف کچھ پیغامات پر بھروسہ کرے اگر وہ دوسری طرف کے برج سے آتے ہیں۔
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: messenger contract unauthenticated"4 );صرف مناسب کراس ڈومین میسنجر (messenger, جیسا کہ آپ نیچے دیکھتے ہیں) سے آنے والے پیغامات پر بھروسہ کیا جا سکتا ہے۔
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: wrong sender of cross-domain message"5 );کراس ڈومین میسنجر جس طرح سے دوسری لیئر کے ساتھ پیغام بھیجنے والے ایڈریس کو فراہم کرتا ہے وہ ہے .xDomainMessageSender() فنکشنopens in a new tab۔
جب تک اسے اس ٹرانزیکشن میں کال کیا جاتا ہے جو پیغام کے ذریعے شروع کی گئی تھی، یہ یہ معلومات فراہم کر سکتا ہے۔
ہمیں یہ یقینی بنانا ہوگا کہ ہمیں موصول ہونے والا پیغام دوسرے برج سے آیا ہے۔
12 _;3 }45 /**********************6 * Internal Functions *7 **********************/89 /**10 * Gets the messenger, usually from storage. This function is exposed in case a child contract11 * needs to override.12 * @return The address of the cross-domain messenger contract which should be used.13 */14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {15 return ICrossDomainMessenger(messenger);16 }سب دکھائیںیہ فنکشن کراس ڈومین میسنجر کو واپس کرتا ہے۔
ہم متغیر messenger کے بجائے ایک فنکشن استعمال کرتے ہیں تاکہ اس سے وراثت میں ملنے والے کنٹریکٹس کو ایک الگورتھم استعمال کرنے کی اجازت دی جا سکے تاکہ یہ بتایا جا سکے کہ کون سا کراس ڈومین میسنجر استعمال کرنا ہے۔
12 /**3 * Sends a message to an account on another domain4 * @param _crossDomainTarget The intended recipient on the destination domain5 * @param _message The data to send to the target (usually calldata to a function with6 * `onlyFromCrossDomainAccount()`)7 * @param _gasLimit The gasLimit for the receipt of the message on the target domain.8 */9 function sendCrossDomainMessage(10 address _crossDomainTarget,11 uint32 _gasLimit,12 bytes memory _messageسب دکھائیںآخر میں، وہ فنکشن جو دوسری لیئر کو ایک پیغام بھیجتا ہے۔
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignSlitheropens in a new tab ایک جامد تجزیہ کار ہے جسے Optimism ہر کنٹریکٹ پر کمزوریوں اور دیگر ممکنہ مسائل کو تلاش کرنے کے لیے چلاتا ہے۔ اس معاملے میں، مندرجہ ذیل لائن دو کمزوریوں کو متحرک کرتی ہے:
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);2 }3}اس معاملے میں ہم reentrancy کے بارے میں فکر مند نہیں ہیں کیونکہ ہم جانتے ہیں کہ getCrossDomainMessenger() ایک قابل اعتماد ایڈریس واپس کرتا ہے، چاہے Slither کو یہ جاننے کا کوئی طریقہ نہ ہو۔
L1 برج کنٹریکٹ
اس کنٹریکٹ کا سورس کوڈ یہاں ہےopens in a new tab۔
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;انٹرفیس دوسرے کنٹریکٹس کا حصہ ہو سکتے ہیں، لہذا انہیں Solidity ورژن کی ایک وسیع رینج کو سپورٹ کرنا ہوگا۔ لیکن برج خود ہمارا کنٹریکٹ ہے، اور ہم اس بارے میں سخت ہو سکتے ہیں کہ یہ کون سا Solidity ورژن استعمال کرتا ہے۔
1/* Interface Imports */2import { IL1StandardBridge } from "./IL1StandardBridge.sol";3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";IL1ERC20Bridge اور IL1StandardBridge اوپر بیان کیے گئے ہیں۔
1import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";یہ انٹرفیسopens in a new tab ہمیں L2 پر معیاری برج کو کنٹرول کرنے کے لیے پیغامات بنانے دیتا ہے۔
1import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";یہ انٹرفیسopens in a new tab ہمیں ERC-20 کنٹریکٹس کو کنٹرول کرنے دیتا ہے۔ آپ اس کے بارے میں مزید یہاں پڑھ سکتے ہیں۔
1/* Library Imports */2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";جیسا کہ اوپر بیان کیا گیا ہے، یہ کنٹریکٹ انٹر لیئر میسجنگ کے لیے استعمال ہوتا ہے۔
1import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";Lib_PredeployAddressesopens in a new tab میں L2 کنٹریکٹس کے ایڈریس ہیں جن کا ہمیشہ ایک ہی ایڈریس ہوتا ہے۔ اس میں L2 پر معیاری برج شامل ہے۔
1import { Address } from "@openzeppelin/contracts/utils/Address.sol";OpenZeppelin کی ایڈریس یوٹیلیٹیزopens in a new tab۔ اس کا استعمال کنٹریکٹ ایڈریس اور بیرونی طور پر ملکیت والے اکاؤنٹس (EOA) سے تعلق رکھنے والوں کے درمیان فرق کرنے کے لیے کیا جاتا ہے۔
نوٹ کریں کہ یہ ایک بہترین حل نہیں ہے، کیونکہ براہ راست کالز اور کنٹریکٹ کے کنسٹرکٹر سے کی گئی کالز کے درمیان فرق کرنے کا کوئی طریقہ نہیں ہے، لیکن کم از کم یہ ہمیں کچھ عام صارف کی غلطیوں کی نشاندہی کرنے اور انہیں روکنے دیتا ہے۔
1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";ERC-20 معیارopens in a new tab ایک کنٹریکٹ کے لیے ناکامی کی رپورٹ کرنے کے دو طریقے سپورٹ کرتا ہے:
- واپس کریں
falseواپس کریں
دونوں صورتوں کو ہینڈل کرنے سے ہمارا کوڈ مزید پیچیدہ ہو جائے گا، لہذا اس کے بجائے ہم OpenZeppelin کا SafeERC20opens in a new tab استعمال کرتے ہیں، جو یقینی بناتا ہے کہ تمام ناکامیوں کے نتیجے میں ایک ریورٹopens in a new tab ہو۔
1/**2 * @title L1StandardBridge3 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard4 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits5 * and listening to it for newly finalized withdrawals.6 *7 */8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {9 using SafeERC20 for IERC20;سب دکھائیںیہ لائن ہے کہ ہم IERC20 انٹرفیس استعمال کرتے وقت ہر بار SafeERC20 ریپر استعمال کرنے کی وضاحت کیسے کرتے ہیں۔
12 /********************************3 * External Contract References *4 ********************************/56 address public l2TokenBridge;L2StandardBridge کا ایڈریس۔
12 // Maps L1 token to L2 token to balance of the L1 token deposited3 mapping(address => mapping(address => uint256)) public deposits;اس طرح کی ایک ڈبل mappingopens in a new tab وہ طریقہ ہے جس سے آپ ایک دو جہتی اسپارس ارےopens in a new tab کی تعریف کرتے ہیں۔
اس ڈیٹا ڈھانچے میں قدروں کی شناخت deposit[L1 token addr][L2 token addr] کے طور پر کی جاتی ہے۔
پہلے سے طے شدہ قدر صفر ہے۔
صرف وہ سیلز جو ایک مختلف قدر پر سیٹ ہیں اسٹوریج میں لکھے جاتے ہیں۔
12 /***************3 * Constructor *4 ***************/56 // This contract lives behind a proxy, so the constructor parameters will go unused.7 constructor() CrossDomainEnabled(address(0)) {}اسٹوریج میں تمام متغیرات کو کاپی کیے بغیر اس کنٹریکٹ کو اپ گریڈ کرنے کے قابل ہونا چاہتے ہیں۔
ایسا کرنے کے لیے ہم ایک Proxyopens in a new tab کا استعمال کرتے ہیں، ایک کنٹریکٹ جو delegatecallopens in a new tab کا استعمال کرتا ہے تاکہ کالز کو ایک علیحدہ کنٹریکٹ میں منتقل کیا جا سکے جس کا ایڈریس پراکسی کنٹریکٹ کے ذریعے اسٹور کیا جاتا ہے (جب آپ اپ گریڈ کرتے ہیں تو آپ پراکسی کو اس ایڈریس کو تبدیل کرنے کے لیے کہتے ہیں)۔
جب آپ delegatecall استعمال کرتے ہیں تو اسٹوریج کالنگ کنٹریکٹ کا اسٹوریج رہتا ہے، لہذا تمام کنٹریکٹ اسٹیٹ متغیرات کی قدریں غیر متاثر رہتی ہیں۔
اس پیٹرن کا ایک اثر یہ ہے کہ کنٹریکٹ کا اسٹوریج جو delegatecall کا کال کیا گیا ہے استعمال نہیں ہوتا ہے اور اس لیے اسے بھیجی گئی کنسٹرکٹر قدریں کوئی معنی نہیں رکھتیں۔
یہی وجہ ہے کہ ہم CrossDomainEnabled کنسٹرکٹر کو ایک بے معنی قدر فراہم کر سکتے ہیں۔
یہی وجہ ہے کہ نیچے دی گئی شروعات کنسٹرکٹر سے الگ ہے۔
1 /******************2 * Initialization *3 ******************/45 /**6 * @param _l1messenger L1 Messenger address being used for cross-chain communications.7 * @param _l2TokenBridge L2 standard bridge address.8 */9 // slither-disable-next-line external-functionسب دکھائیںیہ Slither ٹیسٹopens in a new tab ان فنکشنز کی نشاندہی کرتا ہے جو کنٹریکٹ کوڈ سے کال نہیں کیے جاتے ہیں اور اس لیے انہیں public کے بجائے external قرار دیا جا سکتا ہے۔
external فنکشنز کی گیس کی لاگت کم ہو سکتی ہے، کیونکہ انہیں کال ڈیٹا میں پیرامیٹرز فراہم کیے جا سکتے ہیں۔
public قرار دیے گئے فنکشنز کو کنٹریکٹ کے اندر سے قابل رسائی ہونا چاہیے۔
کنٹریکٹس اپنے کال ڈیٹا میں ترمیم نہیں کر سکتے، لہذا پیرامیٹرز میموری میں ہونے چاہئیں۔
جب ایسے فنکشن کو بیرونی طور پر کال کیا جاتا ہے، تو کال ڈیٹا کو میموری میں کاپی کرنا ضروری ہوتا ہے، جس میں گیس خرچ ہوتی ہے۔
اس معاملے میں فنکشن صرف ایک بار کال کیا جاتا ہے، لہذا نااہلی ہمارے لیے کوئی معنی نہیں رکھتی۔
1 function initialize(address _l1messenger, address _l2TokenBridge) public {2 require(messenger == address(0), "Contract has already been initialized.");initialize فنکشن کو صرف ایک بار کال کیا جانا چاہیے۔
اگر L1 کراس ڈومین میسنجر یا L2 ٹوکن برج کا ایڈریس تبدیل ہوتا ہے، تو ہم ایک نیا پراکسی اور ایک نیا برج بناتے ہیں جو اسے کال کرتا ہے۔
یہ ہونے کا امکان نہیں ہے سوائے اس کے کہ جب پورا نظام اپ گریڈ کیا جائے، جو کہ ایک بہت ہی نایاب واقعہ ہے۔
نوٹ کریں کہ اس فنکشن میں کوئی ایسا میکانزم نہیں ہے جو اس بات کو محدود کرے کہ اسے کون کال کر سکتا ہے۔
اس کا مطلب ہے کہ نظریاتی طور پر ایک حملہ آور اس وقت تک انتظار کر سکتا ہے جب تک کہ ہم پراکسی اور برج کا پہلا ورژن تعینات نہ کر دیں اور پھر front-runopens in a new tab کریں تاکہ جائز صارف سے پہلے initialize فنکشن تک پہنچ سکیں۔ لیکن اسے روکنے کے دو طریقے ہیں:
- اگر کنٹریکٹس براہ راست EOA کے ذریعے نہیں بلکہ ایک ٹرانزیکشن میں جس میں دوسرا کنٹریکٹ انہیں بناتا ہےopens in a new tab تعینات کیے گئے ہیں تو پورا عمل ایٹمی ہو سکتا ہے، اور کسی بھی دوسری ٹرانزیکشن کے عملدرآمد سے پہلے ختم ہو سکتا ہے۔
- اگر
initializeکی جائز کال ناکام ہو جاتی ہے تو نئے بنائے گئے پراکسی اور برج کو نظر انداز کرنا اور نئے بنانا ہمیشہ ممکن ہوتا ہے۔
1 messenger = _l1messenger;2 l2TokenBridge = _l2TokenBridge;3 }یہ وہ دو پیرامیٹرز ہیں جو برج کو جاننے کی ضرورت ہے۔
12 /**************3 * Depositing *4 **************/56 /** @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious7 * contract via initcode, but it takes care of the user error we want to avoid.8 */9 modifier onlyEOA() {10 // Used to stop deposits from contracts (avoid accidentally lost tokens)11 require(!Address.isContract(msg.sender), "Account not EOA");12 _;13 }سب دکھائیںیہی وجہ ہے کہ ہمیں OpenZeppelin کی Address یوٹیلیٹیز کی ضرورت تھی۔
1 /**2 * @dev This function can be called with no data3 * to deposit an amount of ETH to the caller's balance on L2.4 * Since the receive function doesn't take data, a conservative5 * default amount is forwarded to L2.6 */7 receive() external payable onlyEOA {8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));9 }سب دکھائیںیہ فنکشن جانچ کے مقاصد کے لیے موجود ہے۔ نوٹ کریں کہ یہ انٹرفیس کی تعریفوں میں ظاہر نہیں ہوتا - یہ عام استعمال کے لیے نہیں ہے۔
1 /**2 * @inheritdoc IL1StandardBridge3 */4 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {5 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);6 }78 /**9 * @inheritdoc IL1StandardBridge10 */11 function depositETHTo(12 address _to,13 uint32 _l2Gas,14 bytes calldata _data15 ) external payable {16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);17 }سب دکھائیںیہ دو فنکشنز _initiateETHDeposit کے ارد گرد ریپر ہیں، وہ فنکشن جو اصل ETH ڈپازٹ کو ہینڈل کرتا ہے۔
1 /**2 * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of3 * the deposit.4 * @param _from Account to pull the deposit from on L1.5 * @param _to Account to give the deposit to on L2.6 * @param _l2Gas Gas limit required to complete the deposit on L2.7 * @param _data Optional data to forward to L2. This data is provided8 * solely as a convenience for external contracts. Aside from enforcing a maximum9 * length, these contracts provide no guarantees about its content.10 */11 function _initiateETHDeposit(12 address _from,13 address _to,14 uint32 _l2Gas,15 bytes memory _data16 ) internal {17 // Construct calldata for finalizeDeposit call18 bytes memory message = abi.encodeWithSelector(سب دکھائیںکراس ڈومین پیغامات جس طرح کام کرتے ہیں وہ یہ ہے کہ منزل کا کنٹریکٹ پیغام کے ساتھ اس کے کال ڈیٹا کے طور پر کال کیا جاتا ہے۔
Solidity کنٹریکٹس ہمیشہ اپنے کال ڈیٹا کی تشریح
ABI وضاحتوںopens in a new tab کے مطابق کرتے ہیں۔
Solidity فنکشن abi.encodeWithSelectoropens in a new tab وہ کال ڈیٹا بناتا ہے۔
1 IL2ERC20Bridge.finalizeDeposit.selector,2 address(0),3 Lib_PredeployAddresses.OVM_ETH,4 _from,5 _to,6 msg.value,7 _data8 );یہاں پیغام یہ ہے کہ ان پیرامیٹرز کے ساتھ finalizeDeposit فنکشنopens in a new tab کو کال کریں:
| پیرامیٹر | قدر | مطلب |
|---|---|---|
| _l1Token | address(0) | L1 پر ETH (جو کہ ERC-20 ٹوکن نہیں ہے) کے لیے کھڑے ہونے کی خاص قدر |
| _l2Token | Lib_PredeployAddresses.OVM_ETH | Optimism پر ETH کا انتظام کرنے والا L2 کنٹریکٹ، 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (یہ کنٹریکٹ صرف اندرونی Optimism استعمال کے لیے ہے) |
| _from | _from | L1 پر وہ ایڈریس جو ETH بھیجتا ہے |
| _to | _to | L2 پر وہ ایڈریس جو ETH وصول کرتا ہے |
| رقم | msg.value | بھیجے گئے wei کی رقم (جو پہلے ہی برج کو بھیجی جا چکی ہے) |
| _data | _data | ڈپازٹ کے ساتھ منسلک کرنے کے لیے اضافی ڈیٹا |
1 // Send calldata into L22 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);کراس ڈومین میسنجر کے ذریعے پیغام بھیجیں۔
1 // slither-disable-next-line reentrancy-events2 emit ETHDepositInitiated(_from, _to, msg.value, _data);3 }اس منتقلی کے بارے میں سننے والی کسی بھی وکندریقرت ایپلیکیشن کو مطلع کرنے کے لیے ایک ایونٹ خارج کریں۔
1 /**2 * @inheritdoc IL1ERC20Bridge3 */4 function depositERC20(5 .6 .7 .8 ) external virtual onlyEOA {9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);10 }1112 /**13 * @inheritdoc IL1ERC20Bridge14 */15 function depositERC20To(16 .17 .18 .19 ) external virtual {20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);21 }سب دکھائیںیہ دو فنکشنز _initiateERC20Deposit کے ارد گرد ریپر ہیں، وہ فنکشن جو اصل ERC-20 ڈپازٹ کو ہینڈل کرتا ہے۔
1 /**2 * @dev Performs the logic for deposits by informing the L2 Deposited Token3 * contract of the deposit and calling a handler to lock the L1 funds. (e.g., transferFrom)4 *5 * @param _l1Token Address of the L1 ERC20 we are depositing6 * @param _l2Token Address of the L1 respective L2 ERC207 * @param _from Account to pull the deposit from on L18 * @param _to Account to give the deposit to on L29 * @param _amount Amount of the ERC20 to deposit.10 * @param _l2Gas Gas limit required to complete the deposit on L2.11 * @param _data Optional data to forward to L2. This data is provided12 * solely as a convenience for external contracts. Aside from enforcing a maximum13 * length, these contracts provide no guarantees about its content.14 */15 function _initiateERC20Deposit(16 address _l1Token,17 address _l2Token,18 address _from,19 address _to,20 uint256 _amount,21 uint32 _l2Gas,22 bytes calldata _data23 ) internal {سب دکھائیںیہ فنکشن اوپر _initiateETHDeposit سے ملتا جلتا ہے، چند اہم فرق کے ساتھ۔
پہلا فرق یہ ہے کہ یہ فنکشن ٹوکن ایڈریس اور منتقل کرنے کی رقم پیرامیٹرز کے طور پر وصول کرتا ہے۔
ETH کے معاملے میں برج کو کال میں پہلے ہی برج اکاؤنٹ (msg.value) میں اثاثے کی منتقلی شامل ہے۔
1 // When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future2 // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if3 // _from is an EOA or address(0).4 // slither-disable-next-line reentrancy-events, reentrancy-benign5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);ERC-20 ٹوکن کی منتقلی ETH سے ایک مختلف عمل کی پیروی کرتی ہے:
- صارف (
_from) برج کو مناسب ٹوکن منتقل کرنے کی اجازت دیتا ہے۔ - صارف برج کو ٹوکن کنٹریکٹ کے ایڈریس، رقم وغیرہ کے ساتھ کال کرتا ہے۔
- برج ڈپازٹ کے عمل کے حصے کے طور پر ٹوکن (خود کو) منتقل کرتا ہے۔
پہلا قدم آخری دو سے الگ ٹرانزیکشن میں ہو سکتا ہے۔
تاہم، فرنٹ رننگ کوئی مسئلہ نہیں ہے کیونکہ دو فنکشنز جو _initiateERC20Deposit (depositERC20 اور depositERC20To) کو کال کرتے ہیں وہ اس فنکشن کو صرف msg.sender کے ساتھ _from پیرامیٹر کے طور پر کال کرتے ہیں۔
1 // Construct calldata for _l2Token.finalizeDeposit(_to, _amount)2 bytes memory message = abi.encodeWithSelector(3 IL2ERC20Bridge.finalizeDeposit.selector,4 _l1Token,5 _l2Token,6 _from,7 _to,8 _amount,9 _data10 );1112 // Send calldata into L213 // slither-disable-next-line reentrancy-events, reentrancy-benign14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);1516 // slither-disable-next-line reentrancy-benign17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;سب دکھائیںجمع شدہ ٹوکن کی رقم deposits ڈیٹا ڈھانچے میں شامل کریں۔
L2 پر متعدد ایڈریس ہو سکتے ہیں جو ایک ہی L1 ERC-20 ٹوکن کے مطابق ہوں، لہذا ڈپازٹ کا ٹریک رکھنے کے لیے برج کے L1 ERC-20 ٹوکن کے بیلنس کا استعمال کافی نہیں ہے۔
12 // slither-disable-next-line reentrancy-events3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);4 }56 /*************************7 * Cross-chain Functions *8 *************************/910 /**11 * @inheritdoc IL1StandardBridge12 */13 function finalizeETHWithdrawal(14 address _from,15 address _to,16 uint256 _amount,17 bytes calldata _dataسب دکھائیںL2 برج L2 کراس ڈومین میسنجر کو ایک پیغام بھیجتا ہے جس کی وجہ سے L1 کراس ڈومین میسنجر اس فنکشن کو کال کرتا ہے (ایک بار جب وہ ٹرانزیکشن جو پیغام کو حتمی شکل دیتا ہےopens in a new tab L1 پر جمع ہو جاتا ہے، یقیناً)۔
1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {یقینی بنائیں کہ یہ ایک جائز پیغام ہے، جو کراس ڈومین میسنجر سے آرہا ہے اور L2 ٹوکن برج سے شروع ہو رہا ہے۔ یہ فنکشن برج سے ETH نکالنے کے لیے استعمال ہوتا ہے، لہذا ہمیں یہ یقینی بنانا ہوگا کہ اسے صرف مجاز کالر کے ذریعے ہی کال کیا جائے۔
1 // slither-disable-next-line reentrancy-events2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));ETH منتقل کرنے کا طریقہ یہ ہے کہ وصول کنندہ کو msg.value میں wei کی رقم کے ساتھ کال کریں۔
1 require(success, "TransferHelper::safeTransferETH: ETH transfer failed");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);واپسی کے بارے میں ایک ایونٹ خارج کریں۔
1 }23 /**4 * @inheritdoc IL1ERC20Bridge5 */6 function finalizeERC20Withdrawal(7 address _l1Token,8 address _l2Token,9 address _from,10 address _to,11 uint256 _amount,12 bytes calldata _data13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {سب دکھائیںیہ فنکشن اوپر finalizeETHWithdrawal سے ملتا جلتا ہے، ERC-20 ٹوکنز کے لیے ضروری تبدیلیوں کے ساتھ۔
1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;deposits ڈیٹا ڈھانچے کو اپ ڈیٹ کریں۔
12 // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer3 // slither-disable-next-line reentrancy-events4 IERC20(_l1Token).safeTransfer(_to, _amount);56 // slither-disable-next-line reentrancy-events7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);8 }91011 /*****************************12 * Temporary - Migrating ETH *13 *****************************/1415 /**16 * @dev Adds ETH balance to the account. This is meant to allow for ETH17 * to be migrated from an old gateway to a new gateway.18 * NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the19 * old contract20 */21 function donateETH() external payable {}22}سب دکھائیںبرج کا ایک پہلے کا نفاذ تھا۔
جب ہم نفاذ سے اس کی طرف منتقل ہوئے، تو ہمیں تمام اثاثے منتقل کرنے پڑے۔
ERC-20 ٹوکن صرف منتقل کیے جا سکتے ہیں۔
تاہم، ایک کنٹریکٹ میں ETH منتقل کرنے کے لیے آپ کو اس کنٹریکٹ کی منظوری کی ضرورت ہے، جو کہ donateETH ہمیں فراہم کرتا ہے۔
L2 پر ERC-20 ٹوکنز
ایک ERC-20 ٹوکن کو معیاری برج میں فٹ ہونے کے لیے، اسے معیاری برج، اور صرف معیاری برج کو ٹوکن منٹ کرنے کی اجازت دینی ہوگی۔ یہ ضروری ہے کیونکہ برجز کو یہ یقینی بنانا ہوگا کہ Optimism پر گردش کرنے والے ٹوکنز کی تعداد L1 برج کنٹریکٹ کے اندر لاک کیے گئے ٹوکنز کی تعداد کے برابر ہو۔ اگر L2 پر بہت زیادہ ٹوکنز ہیں تو کچھ صارفین اپنے اثاثوں کو L1 پر واپس برج نہیں کر پائیں گے۔ ایک قابل اعتماد برج کے بجائے، ہم بنیادی طور پر فرکشنل ریزرو بینکنگopens in a new tab کو دوبارہ بنائیں گے۔ اگر L1 پر بہت زیادہ ٹوکنز ہیں، تو ان میں سے کچھ ٹوکنز برج کنٹریکٹ کے اندر ہمیشہ کے لیے لاک ہو جائیں گے کیونکہ L2 ٹوکنز کو جلائے بغیر انہیں جاری کرنے کا کوئی طریقہ نہیں ہے۔
IL2StandardERC20
L2 پر ہر ERC-20 ٹوکن جو معیاری برج کا استعمال کرتا ہے اسے یہ انٹرفیسopens in a new tab فراہم کرنا ہوگا، جس میں وہ فنکشنز اور ایونٹس ہیں جن کی معیاری برج کو ضرورت ہے۔
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";معیاری ERC-20 انٹرفیسopens in a new tab میں mint اور burn فنکشنز شامل نہیں ہیں۔
ان طریقوں کی ضرورت ERC-20 معیارopens in a new tab کے ذریعہ نہیں ہے، جو ٹوکن بنانے اور تباہ کرنے کے میکانزم کو غیر متعین چھوڑ دیتا ہے۔
1import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";ERC-165 انٹرفیسopens in a new tab اس بات کی وضاحت کے لیے استعمال ہوتا ہے کہ ایک کنٹریکٹ کون سے فنکشنز فراہم کرتا ہے۔ آپ معیار یہاں پڑھ سکتے ہیںopens in a new tab۔
1interface IL2StandardERC20 is IERC20, IERC165 {2 function l1Token() external returns (address);یہ فنکشن L1 ٹوکن کا ایڈریس فراہم کرتا ہے جو اس کنٹریکٹ سے برجڈ ہے۔ نوٹ کریں کہ ہمارے پاس مخالف سمت میں اسی طرح کا فنکشن نہیں ہے۔ ہمیں کسی بھی L1 ٹوکن کو برج کرنے کے قابل ہونے کی ضرورت ہے، چاہے L2 سپورٹ کی منصوبہ بندی کی گئی ہو یا نہیں جب اسے لاگو کیا گیا تھا۔
12 function mint(address _to, uint256 _amount) external;34 function burn(address _from, uint256 _amount) external;56 event Mint(address indexed _account, uint256 _amount);7 event Burn(address indexed _account, uint256 _amount);8}ٹوکن منٹ (بنانے) اور جلانے (تباہ کرنے) کے لیے فنکشنز اور ایونٹس۔ برج کو واحد ادارہ ہونا چاہئے جو ان فنکشنز کو چلا سکتا ہے تاکہ یہ یقینی بنایا جا سکے کہ ٹوکنز کی تعداد درست ہے (L1 پر لاک کیے گئے ٹوکنز کی تعداد کے برابر)۔
L2StandardERC20
یہ IL2StandardERC20 انٹرفیس کا ہمارا نفاذ ہےopens in a new tab۔
جب تک آپ کو کسی قسم کی اپنی مرضی کی منطق کی ضرورت نہ ہو، آپ کو یہ استعمال کرنا چاہیے۔
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";OpenZeppelin ERC-20 کنٹریکٹopens in a new tab۔ Optimism پہیے کو دوبارہ ایجاد کرنے میں یقین نہیں رکھتا، خاص طور پر جب پہیہ اچھی طرح سے آڈٹ کیا گیا ہو اور اثاثوں کو رکھنے کے لیے کافی قابل اعتماد ہونے کی ضرورت ہو۔
1import "./IL2StandardERC20.sol";23contract L2StandardERC20 is IL2StandardERC20, ERC20 {4 address public l1Token;5 address public l2Bridge;یہ دو اضافی کنفیگریشن پیرامیٹرز ہیں جن کی ہمیں ضرورت ہے اور ERC-20 عام طور پر نہیں کرتا ہے۔
12 /**3 * @param _l2Bridge Address of the L2 standard bridge.4 * @param _l1Token Address of the corresponding L1 token.5 * @param _name ERC20 name.6 * @param _symbol ERC20 symbol.7 */8 constructor(9 address _l2Bridge,10 address _l1Token,11 string memory _name,12 string memory _symbol13 ) ERC20(_name, _symbol) {14 l1Token = _l1Token;15 l2Bridge = _l2Bridge;16 }سب دکھائیںپہلے اس کنٹریکٹ کے لیے کنسٹرکٹر کو کال کریں جس سے ہم وراثت میں ہیں (ERC20(_name, _symbol)) اور پھر اپنے متغیرات سیٹ کریں۔
12 modifier onlyL2Bridge() {3 require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");4 _;5 }678 // slither-disable-next-line external-function9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC16511 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^12 IL2StandardERC20.mint.selector ^13 IL2StandardERC20.burn.selector;14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;15 }سب دکھائیںیہ وہ طریقہ ہے جس سے ERC-165opens in a new tab کام کرتا ہے۔ ہر انٹرفیس معاون فنکشنز کی ایک تعداد ہے، اور اس کی شناخت ان فنکشنز کے ABI فنکشن سلیکٹرزopens in a new tab کے exclusive oropens in a new tab کے طور پر کی جاتی ہے۔
L2 برج ERC-165 کو ایک سینیٹی چیک کے طور پر استعمال کرتا ہے تاکہ یہ یقینی بنایا جا سکے کہ جس ERC-20 کنٹریکٹ کو یہ اثاثے بھیجتا ہے وہ IL2StandardERC20 ہے۔
نوٹ: supportsInterface کو غلط جواب دینے سے بدمعاش کنٹریکٹ کو روکنے کے لیے کچھ بھی نہیں ہے، لہذا یہ ایک سینیٹی چیک میکانزم ہے، سیکیورٹی میکانزم نہیں۔
1 // slither-disable-next-line external-function2 function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {3 _mint(_to, _amount);45 emit Mint(_to, _amount);6 }78 // slither-disable-next-line external-function9 function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {10 _burn(_from, _amount);1112 emit Burn(_from, _amount);13 }14}سب دکھائیںصرف L2 برج کو اثاثے منٹ اور برن کرنے کی اجازت ہے۔
_mintاور_burn` دراصل OpenZeppelin ERC-20 کنٹریکٹ میں بیان کیے گئے ہیں۔
وہ کنٹریکٹ صرف انہیں بیرونی طور پر ظاہر نہیں کرتا، کیونکہ ٹوکن منٹ اور برن کرنے کی شرائط اتنی ہی متنوع ہیں جتنی ERC-20 استعمال کرنے کے طریقے۔
L2 برج کوڈ
یہ وہ کوڈ ہے جو Optimism پر برج کو چلاتا ہے۔ اس کنٹریکٹ کا ماخذ یہاں ہےopens in a new tab۔
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34/* Interface Imports */5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";IL2ERC20Bridgeopens in a new tab انٹرفیس اوپر دیکھے گئے L1 کے مساوی سے بہت ملتا جلتا ہے۔ دو اہم فرق ہیں:
- L1 پر آپ ڈپازٹ شروع کرتے ہیں اور واپسیوں کو حتمی شکل دیتے ہیں۔ یہاں آپ واپسی شروع کرتے ہیں اور ڈپازٹ کو حتمی شکل دیتے ہیں۔
- L1 پر ETH اور ERC-20 ٹوکنز کے درمیان فرق کرنا ضروری ہے۔ L2 پر ہم دونوں کے لیے ایک ہی فنکشنز استعمال کر سکتے ہیں کیونکہ اندرونی طور پر Optimism پر ETH بیلنس کو ایڈریس 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000opens in a new tab والے ERC-20 ٹوکن کے طور پر ہینڈل کیا جاتا ہے۔
1/* Library Imports */2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";56/* Contract Imports */7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";89/**10 * @title L2StandardBridge11 * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to12 * enable ETH and ERC20 transitions between L1 and L2.13 * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard14 * bridge.15 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L116 * bridge to release L1 funds.17 */18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {19 /********************************20 * External Contract References *21 ********************************/2223 address public l1TokenBridge;سب دکھائیںL1 برج کے ایڈریس کا ٹریک رکھیں۔ نوٹ کریں کہ L1 کے مساوی کے برعکس، یہاں ہمیں اس متغیر کی ضرورت ہے۔ L1 برج کا ایڈریس پہلے سے معلوم نہیں ہے۔
12 /***************3 * Constructor *4 ***************/56 /**7 * @param _l2CrossDomainMessenger Cross-domain messenger used by this contract.8 * @param _l1TokenBridge Address of the L1 bridge deployed to the main chain.9 */10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)11 CrossDomainEnabled(_l2CrossDomainMessenger)12 {13 l1TokenBridge = _l1TokenBridge;14 }1516 /***************17 * Withdrawing *18 ***************/1920 /**21 * @inheritdoc IL2ERC20Bridge22 */23 function withdraw(24 address _l2Token,25 uint256 _amount,26 uint32 _l1Gas,27 bytes calldata _data28 ) external virtual {29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);30 }3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function withdrawTo(36 address _l2Token,37 address _to,38 uint256 _amount,39 uint32 _l1Gas,40 bytes calldata _data41 ) external virtual {42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);43 }سب دکھائیںیہ دو فنکشنز واپسی شروع کرتے ہیں۔ نوٹ کریں کہ L1 ٹوکن ایڈریس کی وضاحت کرنے کی کوئی ضرورت نہیں ہے۔ L2 ٹوکنز سے توقع کی جاتی ہے کہ وہ ہمیں L1 کے مساوی ایڈریس بتائیں گے۔
12 /**3 * @dev Performs the logic for withdrawals by burning the token and informing4 * the L1 token Gateway of the withdrawal.5 * @param _l2Token Address of L2 token where withdrawal is initiated.6 * @param _from Account to pull the withdrawal from on L2.7 * @param _to Account to give the withdrawal to on L1.8 * @param _amount Amount of the token to withdraw.9 * @param _l1Gas Unused, but included for potential forward compatibility considerations.10 * @param _data Optional data to forward to L1. This data is provided11 * solely as a convenience for external contracts. Aside from enforcing a maximum12 * length, these contracts provide no guarantees about its content.13 */14 function _initiateWithdrawal(15 address _l2Token,16 address _from,17 address _to,18 uint256 _amount,19 uint32 _l1Gas,20 bytes calldata _data21 ) internal {22 // When a withdrawal is initiated, we burn the withdrawer's funds to prevent subsequent L223 // usage24 // slither-disable-next-line reentrancy-events25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);سب دکھائیںنوٹ کریں کہ ہم _from پیرامیٹر پر انحصار نہیں کر رہے ہیں بلکہ msg.sender پر کر رہے ہیں جسے جعلی بنانا بہت مشکل ہے (جہاں تک میں جانتا ہوں، ناممکن)۔
12 // Construct calldata for l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)3 // slither-disable-next-line reentrancy-events4 address l1Token = IL2StandardERC20(_l2Token).l1Token();5 bytes memory message;67 if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {L1 پر ETH اور ERC-20 کے درمیان فرق کرنا ضروری ہے۔
1 message = abi.encodeWithSelector(2 IL1StandardBridge.finalizeETHWithdrawal.selector,3 _from,4 _to,5 _amount,6 _data7 );8 } else {9 message = abi.encodeWithSelector(10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,11 l1Token,12 _l2Token,13 _from,14 _to,15 _amount,16 _data17 );18 }1920 // Send message up to L1 bridge21 // slither-disable-next-line reentrancy-events22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);2324 // slither-disable-next-line reentrancy-events25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);26 }2728 /************************************29 * Cross-chain Function: Depositing *30 ************************************/3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function finalizeDeposit(36 address _l1Token,37 address _l2Token,38 address _from,39 address _to,40 uint256 _amount,41 bytes calldata _dataسب دکھائیںاس فنکشن کو L1StandardBridge کے ذریعے کال کیا جاتا ہے۔
1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {یقینی بنائیں کہ پیغام کا ماخذ جائز ہے۔
یہ اہم ہے کیونکہ یہ فنکشن _mint کو کال کرتا ہے اور اس کا استعمال ایسے ٹوکن دینے کے لیے کیا جا سکتا ہے جو L1 پر برج کے ملکیت والے ٹوکنز کے تحت نہیں آتے ہیں۔
1 // Check the target token is compliant and2 // verify the deposited token on L1 matches the L2 deposited token representation here3 if (4 // slither-disable-next-line reentrancy-events5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&6 _l1Token == IL2StandardERC20(_l2Token).l1Token()سینیٹی چیکس:
- درست انٹرفیس سپورٹ کیا جاتا ہے
- L2 ERC-20 کنٹریکٹ کا L1 ایڈریس ٹوکنز کے L1 ماخذ سے میل کھاتا ہے
1 ) {2 // When a deposit is finalized, we credit the account on L2 with the same amount of3 // tokens.4 // slither-disable-next-line reentrancy-events5 IL2StandardERC20(_l2Token).mint(_to, _amount);6 // slither-disable-next-line reentrancy-events7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);اگر سینیٹی چیکس پاس ہو جاتے ہیں، تو ڈپازٹ کو حتمی شکل دیں:
- ٹوکن منٹ کریں
- مناسب ایونٹ خارج کریں
1 } else {2 // Either the L2 token which is being deposited-into disagrees about the correct address3 // of its L1 token, or does not support the correct interface.4 // This should only happen if there is a malicious L2 token, or if a user somehow5 // specified the wrong L2 token address to deposit into.6 // In either case, we stop the process here and construct a withdrawal7 // message so that users can get their funds out in some cases.8 // There is no way to prevent malicious token contracts altogether, but this does limit9 // user error and mitigate some forms of malicious contract behavior.سب دکھائیںاگر کسی صارف نے غلط L2 ٹوکن ایڈریس کا استعمال کرکے قابل شناخت غلطی کی ہے، تو ہم ڈپازٹ کو منسوخ کرنا چاہتے ہیں اور L1 پر ٹوکن واپس کرنا چاہتے ہیں۔ L2 سے ایسا کرنے کا واحد طریقہ یہ ہے کہ ایک پیغام بھیجا جائے جسے فالٹ چیلنج کی مدت کا انتظار کرنا پڑے گا، لیکن یہ صارف کے لیے ٹوکن کو مستقل طور پر کھونے سے بہت بہتر ہے۔
1 bytes memory message = abi.encodeWithSelector(2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,3 _l1Token,4 _l2Token,5 _to, // switched the _to and _from here to bounce back the deposit to the sender6 _from,7 _amount,8 _data9 );1011 // Send message up to L1 bridge12 // slither-disable-next-line reentrancy-events13 sendCrossDomainMessage(l1TokenBridge, 0, message);14 // slither-disable-next-line reentrancy-events15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);16 }17 }18}سب دکھائیںنتیجہ
معیاری برج اثاثوں کی منتقلی کے لیے سب سے زیادہ لچکدار میکانزم ہے۔ تاہم، چونکہ یہ بہت عام ہے، اس لیے یہ استعمال کرنے کے لیے ہمیشہ سب سے آسان میکانزم نہیں ہے۔ خاص طور پر واپسی کے لیے، زیادہ تر صارفین تھرڈ پارٹی برجزopens in a new tab کا استعمال کرنا پسند کرتے ہیں جو چیلنج کی مدت کا انتظار نہیں کرتے اور واپسی کو حتمی شکل دینے کے لیے Merkle پروف کی ضرورت نہیں ہوتی۔
یہ برجز عام طور پر L1 پر اثاثے رکھ کر کام کرتے ہیں، جو وہ فوری طور پر ایک چھوٹی سی فیس کے لیے فراہم کرتے ہیں (اکثر معیاری برج کی واپسی کے لیے گیس کی لاگت سے کم)۔ جب برج (یا اسے چلانے والے لوگ) L1 اثاثوں کی کمی کا اندازہ لگاتے ہیں تو یہ L2 سے کافی اثاثے منتقل کرتا ہے۔ چونکہ یہ بہت بڑی واپسی ہیں، واپسی کی لاگت ایک بڑی رقم پر تقسیم کی جاتی ہے اور یہ ایک بہت چھوٹا فیصد ہے۔
امید ہے کہ اس مضمون نے آپ کو لیئر 2 کے کام کرنے کے طریقے، اور واضح اور محفوظ Solidity کوڈ لکھنے کے بارے میں مزید سمجھنے میں مدد کی ہوگی۔
میرے مزید کام کے لیے یہاں دیکھیںopens in a new tab۔
صفحہ کی آخری تازہ کاری: 22 اکتوبر، 2025