مرکزی مواد پر جائیں

آپٹیمزم (Optimism) کے معیاری برج کنٹریکٹ کا جائزہ

Solidity
برج
لیئر 2
درمیانی
اوری پومرانٹز
۳۰ مارچ، ۲۰۲۲
39 منٹ کی پڑھائی

آپٹیمزم (Optimism) (opens in a new tab) ایک آپٹیمسٹک رول اپ (Optimistic Rollup) ہے۔ آپٹیمسٹک رول اپس ایتھیریم مین نیٹ (جسے لیئر 1 یا L1 بھی کہا جاتا ہے) کے مقابلے میں بہت کم قیمت پر ٹرانزیکشنز پروسیس کر سکتے ہیں کیونکہ ٹرانزیکشنز نیٹ ورک کے ہر نوڈ کے بجائے صرف چند نوڈز کے ذریعے پروسیس کی جاتی ہیں۔ ایک ہی وقت میں، تمام ڈیٹا L1 پر لکھا جاتا ہے تاکہ مین نیٹ کی سالمیت اور دستیابی کی تمام ضمانتوں کے ساتھ ہر چیز کو ثابت اور دوبارہ تشکیل دیا جا سکے۔

آپٹیمزم (یا کسی دوسرے L2) پر L1 اثاثے استعمال کرنے کے لیے، اثاثوں کو برج (bridged) کرنے کی ضرورت ہوتی ہے۔ اسے حاصل کرنے کا ایک طریقہ یہ ہے کہ صارفین L1 پر اثاثے (ETH اور ERC-20 ٹوکنز سب سے عام ہیں) لاک کریں، اور L2 پر استعمال کرنے کے لیے مساوی اثاثے حاصل کریں۔ بالآخر، جس کے پاس بھی یہ اثاثے ہوں گے وہ انہیں واپس L1 پر برج کرنا چاہے گا۔ ایسا کرتے وقت، اثاثے L2 پر برن (burn) کر دیے جاتے ہیں اور پھر L1 پر صارف کو واپس جاری کر دیے جاتے ہیں۔

آپٹیمزم کا معیاری برج (opens in a new tab) اسی طرح کام کرتا ہے۔ اس مضمون میں ہم اس برج کے سورس کوڈ کا جائزہ لیں گے تاکہ یہ دیکھا جا سکے کہ یہ کیسے کام کرتا ہے اور اسے اچھی طرح سے لکھے گئے Solidity کوڈ کی ایک مثال کے طور پر پڑھیں گے۔

کنٹرول فلوز (Control flows)

برج کے دو اہم فلوز ہیں:

  • ڈپازٹ (L1 سے L2 تک)
  • ودڈراول (L2 سے L1 تک)

ڈپازٹ فلو

لیئر 1

  1. اگر کوئی ERC-20 ڈپازٹ کر رہا ہے، تو ڈپازٹ کرنے والا برج کو جمع کی جانے والی رقم خرچ کرنے کا الاؤنس (allowance) دیتا ہے۔
  2. ڈپازٹ کرنے والا L1 برج کو کال کرتا ہے (depositERC20، depositERC20To، depositETH، یا depositETHTo)
  3. L1 برج برج کیے گئے اثاثے کا قبضہ لے لیتا ہے
    • ETH: اثاثہ ڈپازٹ کرنے والے کے ذریعے کال کے حصے کے طور پر منتقل کیا جاتا ہے
    • ERC-20: اثاثہ برج کے ذریعے ڈپازٹ کرنے والے کے فراہم کردہ الاؤنس کا استعمال کرتے ہوئے خود کو منتقل کیا جاتا ہے
  4. L1 برج کراس ڈومین میسج میکانزم کا استعمال کرتے ہوئے L2 برج پر finalizeDeposit کو کال کرتا ہے

لیئر 2

  1. L2 برج تصدیق کرتا ہے کہ finalizeDeposit کی کال جائز ہے:
    • کراس ڈومین میسج کنٹریکٹ سے آئی ہے
    • اصل میں L1 پر موجود برج سے تھی
  2. L2 برج چیک کرتا ہے کہ آیا L2 پر ERC-20 ٹوکن کنٹریکٹ درست ہے:
  3. اگر L2 کنٹریکٹ درست ہے، تو اسے مناسب ایڈریس پر مناسب تعداد میں ٹوکنز منٹ (mint) کرنے کے لیے کال کریں۔ اگر نہیں، تو صارف کو L1 پر ٹوکنز کلیم کرنے کی اجازت دینے کے لیے ودڈراول کا عمل شروع کریں۔

ودڈراول فلو

لیئر 2

  1. ودڈرا کرنے والا L2 برج کو کال کرتا ہے (withdraw یا withdrawTo)
  2. L2 برج msg.sender سے تعلق رکھنے والے مناسب تعداد میں ٹوکنز کو برن کرتا ہے
  3. L2 برج کراس ڈومین میسج میکانزم کا استعمال کرتے ہوئے L1 برج پر finalizeETHWithdrawal یا finalizeERC20Withdrawal کو کال کرتا ہے

لیئر 1

  1. L1 برج تصدیق کرتا ہے کہ finalizeETHWithdrawal یا finalizeERC20Withdrawal کی کال جائز ہے:
    • کراس ڈومین میسج میکانزم سے آئی ہے
    • اصل میں L2 پر موجود برج سے تھی
  2. L1 برج مناسب اثاثہ (ETH یا ERC-20) مناسب ایڈریس پر منتقل کرتا ہے

لیئر 1 کوڈ

یہ وہ کوڈ ہے جو L1، یعنی ایتھیریم مین نیٹ پر چلتا ہے۔

IL1ERC20Bridge

یہ انٹرفیس یہاں بیان کیا گیا ہے (opens in a new tab)۔ اس میں ERC-20 ٹوکنز کو برج کرنے کے لیے درکار فنکشنز اور تعریفیں شامل ہیں۔

1// SPDX-License-Identifier: MIT

آپٹیمزم کا زیادہ تر کوڈ MIT لائسنس کے تحت جاری کیا گیا ہے (opens in a new tab)۔

1pragma solidity >0.5.0 <0.9.0;

لکھتے وقت Solidity کا تازہ ترین ورژن 0.8.12 ہے۔ جب تک ورژن 0.9.0 جاری نہیں ہوتا، ہم نہیں جانتے کہ یہ کوڈ اس کے ساتھ مطابقت رکھتا ہے یا نہیں۔

1/* *
2 * @title IL1ERC20Bridge */
3interface IL1ERC20Bridge {
4 /* *********
5 * ایونٹس *
6 ********* */
7
8 event ERC20DepositInitiated(

آپٹیمزم برج کی اصطلاح میں ڈپازٹ کا مطلب 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 (مین نیٹ) پر ہے اور chainId 10 والا ایڈریس L2 (آپٹیمزم) پر ہے۔ دیگر دو chainId ویلیوز کوون (Kovan) ٹیسٹ نیٹ ورک (42) اور آپٹیمسٹک کوون ٹیسٹ نیٹ ورک (69) کے لیے ہیں۔

1 address indexed _from,
2 address _to,
3 uint256 _amount,
4 bytes _data
5 );

ٹرانسفرز میں نوٹس شامل کرنا ممکن ہے، اس صورت میں انہیں ان ایونٹس میں شامل کیا جاتا ہے جو انہیں رپورٹ کرتے ہیں۔

1 event ERC20WithdrawalFinalized(
2 address indexed _l1Token,
3 address indexed _l2Token,
4 address indexed _from,
5 address _to,
6 uint256 _amount,
7 bytes _data
8 );

ایک ہی برج کنٹریکٹ دونوں سمتوں میں ٹرانسفرز کو ہینڈل کرتا ہے۔ L1 برج کے معاملے میں، اس کا مطلب ڈپازٹس کا آغاز اور ودڈراولز کی تکمیل ہے۔

1
2 /* *******************
3 * پبلک فنکشنز *
4 ******************* */
5
6 /* *
7 * @dev متعلقہ L2 برج کنٹریکٹ کا ایڈریس حاصل کریں۔
8 * @return متعلقہ L2 برج کنٹریکٹ کا ایڈریس۔ */
9 function l2TokenBridge() external returns (address);

اس فنکشن کی واقعی ضرورت نہیں ہے، کیونکہ L2 پر یہ پہلے سے تعینات (predeployed) کنٹریکٹ ہے، اس لیے یہ ہمیشہ 0x4200000000000000000000000000000000000010 ایڈریس پر ہوتا ہے۔ یہ یہاں L2 برج کے ساتھ ہم آہنگی کے لیے ہے، کیونکہ L1 برج کا ایڈریس جاننا معمولی بات نہیں ہے۔

1 /* *
2 * @dev کالر کے L2 بیلنس میں ERC20 کی ایک رقم جمع (deposit) کریں۔
3 * @param _l1Token اس L1 ERC20 کا ایڈریس جو ہم جمع کر رہے ہیں
4 * @param _l2Token L1 کے متعلقہ L2 ERC20 کا ایڈریس
5 * @param _amount جمع کرنے کے لیے ERC20 کی رقم
6 * @param _l2Gas L2 پر ڈپازٹ مکمل کرنے کے لیے درکار گیس لمٹ (Gas limit)۔
7 * @param _data L2 کو بھیجنے کے لیے اختیاری ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
8 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
9 function depositERC20(
10 address _l1Token,
11 address _l2Token,
12 uint256 _amount,
13 uint32 _l2Gas,
14 bytes calldata _data
15 ) external;

_l2Gas پیرامیٹر L2 گیس کی وہ مقدار ہے جو ٹرانزیکشن کو خرچ کرنے کی اجازت ہے۔ ایک مخصوص (اعلی) حد تک، یہ مفت ہے (opens in a new tab)، لہذا جب تک کہ ERC-20 کنٹریکٹ منٹنگ کے وقت کچھ واقعی عجیب نہ کرے، یہ کوئی مسئلہ نہیں ہونا چاہیے۔ یہ فنکشن اس عام صورتحال کا خیال رکھتا ہے، جہاں ایک صارف مختلف بلاک چین پر اسی ایڈریس پر اثاثوں کو برج کرتا ہے۔

1 /* *
2 * @dev وصول کنندہ کے L2 بیلنس میں ERC20 کی ایک رقم جمع (deposit) کریں۔
3 * @param _l1Token اس L1 ERC20 کا ایڈریس جو ہم جمع کر رہے ہیں
4 * @param _l2Token L1 کے متعلقہ L2 ERC20 کا ایڈریس
5 * @param _to L2 ایڈریس جس پر رقم بھیجنی ہے۔
6 * @param _amount جمع کرنے کے لیے ERC20 کی رقم۔
7 * @param _l2Gas L2 پر ڈپازٹ مکمل کرنے کے لیے درکار گیس لمٹ۔
8 * @param _data L2 کو بھیجنے کے لیے اختیاری ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
9 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
10 function depositERC20To(
11 address _l1Token,
12 address _l2Token,
13 address _to,
14 uint256 _amount,
15 uint32 _l2Gas,
16 bytes calldata _data
17 ) external;

یہ فنکشن تقریباً depositERC20 جیسا ہی ہے، لیکن یہ آپ کو ERC-20 کو مختلف ایڈریس پر بھیجنے کی اجازت دیتا ہے۔

1 /* ************************
2 * کراس چین فنکشنز *
3 ************************ */
4
5 /* *
6 * @dev L2 سے L1 میں واپسی (withdrawal) مکمل کریں، اور وصول کنندہ کے L1 ERC20 ٹوکن کے بیلنس میں فنڈز جمع کریں۔
7 * یہ کال ناکام ہو جائے گی اگر L2 سے شروع کی گئی واپسی کو حتمی شکل (finalized) نہیں دی گئی ہے۔
8 *
9 * @param _l1Token L1 ٹوکن کا ایڈریس جس کے لیے finalizeWithdrawal کرنا ہے۔
10 * @param _l2Token L2 ٹوکن کا ایڈریس جہاں سے واپسی شروع کی گئی تھی۔
11 * @param _from L2 ایڈریس جو ٹرانسفر شروع کر رہا ہے۔
12 * @param _to L1 ایڈریس جس پر واپسی کی رقم جمع کرنی ہے۔
13 * @param _amount جمع کرنے کے لیے ERC20 کی رقم۔
14 * @param _data L2 پر بھیجنے والے کی طرف سے فراہم کردہ ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
15 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
16 function finalizeERC20Withdrawal(
17 address _l1Token,
18 address _l2Token,
19 address _from,
20 address _to,
21 uint256 _amount,
22 bytes calldata _data
23 ) external;
24}

آپٹیمزم میں ودڈراولز (اور L2 سے L1 تک کے دیگر پیغامات) دو مراحل پر مشتمل عمل ہیں:

  1. L2 پر ایک ابتدائی ٹرانزیکشن۔
  2. L1 پر ایک حتمی یا کلیم کرنے والی ٹرانزیکشن۔ اس ٹرانزیکشن کو L2 ٹرانزیکشن کے لیے فالٹ چیلنج پیریڈ (opens in a new tab) ختم ہونے کے بعد ہونا چاہیے۔

IL1StandardBridge

یہ انٹرفیس یہاں بیان کیا گیا ہے (opens in a new tab)۔ اس فائل میں ETH کے لیے ایونٹ اور فنکشن کی تعریفیں شامل ہیں۔ یہ تعریفیں اوپر ERC-20 کے لیے IL1ERC20Bridge میں بیان کردہ تعریفوں سے بہت ملتی جلتی ہیں۔

برج انٹرفیس کو دو فائلوں کے درمیان تقسیم کیا گیا ہے کیونکہ کچھ ERC-20 ٹوکنز کو کسٹم پروسیسنگ کی ضرورت ہوتی ہے اور انہیں معیاری برج کے ذریعے ہینڈل نہیں کیا جا سکتا۔ اس طرح کسٹم برج جو ایسے ٹوکن کو ہینڈل کرتا ہے وہ IL1ERC20Bridge کو نافذ کر سکتا ہے اور اسے ETH کو بھی برج کرنے کی ضرورت نہیں ہوتی۔

1// SPDX-License-Identifier: MIT
2pragma solidity >0.5.0 <0.9.0;
3
4import "./IL1ERC20Bridge.sol";
5
6/* *
7 * @title IL1StandardBridge */
8interface IL1StandardBridge is IL1ERC20Bridge {
9 /* *********
10 * ایونٹس *
11 ********* */
12 event ETHDepositInitiated(
13 address indexed _from,
14 address indexed _to,
15 uint256 _amount,
16 bytes _data
17 );

یہ ایونٹ تقریباً ERC-20 ورژن (ERC20DepositInitiated) جیسا ہی ہے، سوائے L1 اور L2 ٹوکن ایڈریسز کے۔ دیگر ایونٹس اور فنکشنز کے لیے بھی یہی سچ ہے۔

1 event ETHWithdrawalFinalized(
2 .
3 .
4 .
5 );
6
7 /* *******************
8 * پبلک فنکشنز *
9 ******************* */
10
11 /* *
12 * @dev کالر کے L2 بیلنس میں ETH کی ایک رقم جمع (deposit) کریں۔
13 .
14 .
15 . */
16 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;
17
18 /* *
19 * @dev وصول کنندہ کے L2 بیلنس میں ETH کی ایک رقم جمع (deposit) کریں۔
20 .
21 .
22 . */
23 function depositETHTo(
24 address _to,
25 uint32 _l2Gas,
26 bytes calldata _data
27 ) external payable;
28
29 /* ************************
30 * کراس چین فنکشنز *
31 ************************ */
32
33 /* *
34 * @dev L2 سے L1 میں واپسی (withdrawal) مکمل کریں، اور وصول کنندہ کے L1 ETH ٹوکن کے بیلنس میں فنڈز جمع کریں۔ چونکہ صرف xDomainMessenger اس فنکشن کو کال کر سکتا ہے، اس لیے اسے کبھی بھی
35 * واپسی کے حتمی ہونے سے پہلے کال نہیں کیا جائے گا۔
36 .
37 .
38 . */
39 function finalizeETHWithdrawal(
40 address _from,
41 address _to,
42 uint256 _amount,
43 bytes calldata _data
44 ) external;
45}

CrossDomainEnabled

یہ کنٹریکٹ (opens in a new tab) دونوں برجز (L1 اور L2) کے ذریعے دوسری لیئر کو پیغامات بھیجنے کے لیے وراثت میں ملا ہے۔

1// SPDX-License-Identifier: MIT
2pragma solidity >0.5.0 <0.9.0;
3
4/* انٹرفیس امپورٹس */
5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";

یہ انٹرفیس (opens in a new tab) کنٹریکٹ کو بتاتا ہے کہ کراس ڈومین میسنجر کا استعمال کرتے ہوئے دوسری لیئر کو پیغامات کیسے بھیجنے ہیں۔ یہ کراس ڈومین میسنجر ایک بالکل الگ سسٹم ہے، اور اس پر ایک الگ مضمون بنتا ہے، جو مجھے امید ہے کہ میں مستقبل میں لکھوں گا۔

1/* *
2 * @title CrossDomainEnabled
3 * @dev کراس ڈومین کمیونیکیشن کرنے والے کنٹریکٹس کے لیے ہیلپر کنٹریکٹ
4 *
5 * استعمال شدہ کمپائلر: وراثت میں ملنے والے (inheriting) کنٹریکٹ کے ذریعے بیان کیا گیا ہے */
6contract CrossDomainEnabled {
7 /* ************
8 * ویری ایبلز *
9 ************ */
10
11 // میسنجر کنٹریکٹ جو دوسرے ڈومین سے پیغامات بھیجنے اور وصول کرنے کے لیے استعمال ہوتا ہے۔
12 address public messenger;
13
14 /* **************
15 * کنسٹرکٹر *
16 ************** */
17
18 /* *
19 * @param _messenger موجودہ لیئر پر CrossDomainMessenger کا ایڈریس۔ */
20 constructor(address _messenger) {
21 messenger = _messenger;
22 }

ایک پیرامیٹر جو کنٹریکٹ کو جاننے کی ضرورت ہے، وہ اس لیئر پر کراس ڈومین میسنجر کا ایڈریس ہے۔ یہ پیرامیٹر کنسٹرکٹر میں ایک بار سیٹ کیا جاتا ہے، اور کبھی تبدیل نہیں ہوتا۔

1
2 /* *********************
3 * فنکشن موڈیفائرز *
4 ********************* */
5
6 /* *
7 * یہ یقینی بناتا ہے کہ موڈیفائیڈ فنکشن کو صرف ایک مخصوص کراس ڈومین اکاؤنٹ ہی کال کر سکتا ہے۔
8 * @param _sourceDomainAccount اصل ڈومین پر واحد اکاؤنٹ جو
9 * اس فنکشن کو کال کرنے کا مجاز ہے۔ */
10 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {

کراس ڈومین میسجنگ اس بلاک چین پر موجود کسی بھی کنٹریکٹ کے ذریعے قابل رسائی ہے جہاں یہ چل رہا ہے (یا تو ایتھیریم مین نیٹ یا آپٹیمزم)۔ لیکن ہمیں ہر طرف کے برج کی ضرورت ہے کہ وہ صرف مخصوص پیغامات پر بھروسہ کرے اگر وہ دوسری طرف کے برج سے آتے ہیں۔

1 require(
2 msg.sender == address(getCrossDomainMessenger()),
3 "OVM_XCHAIN: messenger contract unauthenticated"
4 );

صرف مناسب کراس ڈومین میسنجر (messenger، جیسا کہ آپ نیچے دیکھتے ہیں) کے پیغامات پر بھروسہ کیا جا سکتا ہے۔

1
2 require(
3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
4 "OVM_XCHAIN: wrong sender of cross-domain message"
5 );

جس طرح کراس ڈومین میسنجر وہ ایڈریس فراہم کرتا ہے جس نے دوسری لیئر کے ساتھ پیغام بھیجا تھا وہ .xDomainMessageSender() فنکشن (opens in a new tab) ہے۔ جب تک اسے اس ٹرانزیکشن میں کال کیا جاتا ہے جو پیغام کے ذریعے شروع کی گئی تھی، یہ یہ معلومات فراہم کر سکتا ہے۔

ہمیں یہ یقینی بنانا ہوگا کہ ہمیں موصول ہونے والا پیغام دوسرے برج سے آیا ہے۔

1
2 _;
3 }
4
5 /* *********************
6 * انٹرنل فنکشنز *
7 ********************* */
8
9 /* *
10 * میسنجر حاصل کرتا ہے، عام طور پر اسٹوریج سے۔ یہ فنکشن اس صورت میں ظاہر کیا گیا ہے کہ اگر کسی چائلڈ کنٹریکٹ کو
11 * اوور رائیڈ (override) کرنے کی ضرورت ہو۔
12 * @return کراس ڈومین میسنجر کنٹریکٹ کا ایڈریس جسے استعمال کیا جانا چاہیے۔ */
13 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
14 return ICrossDomainMessenger(messenger);
15 }

یہ فنکشن کراس ڈومین میسنجر واپس کرتا ہے۔ ہم messenger متغیر کے بجائے ایک فنکشن استعمال کرتے ہیں تاکہ اس سے وراثت میں ملنے والے کنٹریکٹس کو یہ بتانے کے لیے ایک الگورتھم استعمال کرنے کی اجازت دی جا سکے کہ کون سا کراس ڈومین میسنجر استعمال کرنا ہے۔

1
2 /* *
3 * کسی دوسرے ڈومین پر موجود اکاؤنٹ کو پیغام بھیجتا ہے
4 * @param _crossDomainTarget منزل کے ڈومین پر مطلوبہ وصول کنندہ
5 * @param _message ہدف کو بھیجا جانے والا ڈیٹا (عام طور پر `onlyFromCrossDomainAccount()` والے فنکشن کے لیے calldata)
6 * @param _gasLimit ہدف کے ڈومین پر پیغام کی وصولی کے لیے gasLimit۔ */
7 function sendCrossDomainMessage(
8 address _crossDomainTarget,
9 uint32 _gasLimit,
10 bytes memory _message

آخر میں، وہ فنکشن جو دوسری لیئر کو پیغام بھیجتا ہے۔

1 ) internal {
2 // slither-disable-next-line reentrancy-events, reentrancy-benign

Slither (opens in a new tab) ایک جامد تجزیہ کار (static analyzer) ہے جسے آپٹیمزم ہر کنٹریکٹ پر کمزوریوں اور دیگر ممکنہ مسائل کو تلاش کرنے کے لیے چلاتا ہے۔ اس صورت میں، درج ذیل لائن دو کمزوریوں کو متحرک کرتی ہے:

  1. ری اینٹرنسی ایونٹس (Reentrancy events) (opens in a new tab)
  2. بے ضرر ری اینٹرنسی (Benign reentrancy) (opens in a new tab)
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
2 }
3}

اس صورت میں ہم ری اینٹرنسی کے بارے میں فکر مند نہیں ہیں، ہم جانتے ہیں کہ getCrossDomainMessenger() ایک قابل اعتماد ایڈریس واپس کرتا ہے، یہاں تک کہ اگر Slither کے پاس یہ جاننے کا کوئی طریقہ نہیں ہے۔

L1 برج کنٹریکٹ

اس کنٹریکٹ کا سورس کوڈ یہاں ہے (opens in a new tab)۔

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;

انٹرفیسز دیگر کنٹریکٹس کا حصہ ہو سکتے ہیں، اس لیے انہیں Solidity کے ورژنز کی ایک وسیع رینج کو سپورٹ کرنا ہوتا ہے۔ لیکن برج بذات خود ہمارا کنٹریکٹ ہے، اور ہم اس بارے میں سخت ہو سکتے ہیں کہ یہ کون سا Solidity ورژن استعمال کرتا ہے۔

1/* انٹرفیس امپورٹس */
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/* لائبریری امپورٹس */
2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";

جیسا کہ اوپر بیان کیا گیا ہے، یہ کنٹریکٹ انٹرلیئر میسجنگ کے لیے استعمال ہوتا ہے۔

1import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";

Lib_PredeployAddresses (opens 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) کنٹریکٹ کے لیے ناکامی کی اطلاع دینے کے دو طریقوں کو سپورٹ کرتا ہے:

  1. ریورٹ (Revert)
  2. false واپس کرنا

دونوں صورتوں کو ہینڈل کرنے سے ہمارا کوڈ مزید پیچیدہ ہو جائے گا، اس لیے اس کے بجائے ہم OpenZeppelin کا SafeERC20 (opens in a new tab) استعمال کرتے ہیں، جو اس بات کو یقینی بناتا ہے کہ تمام ناکامیوں کا نتیجہ ریورٹ کی صورت میں نکلے (opens in a new tab)۔

1/* *
2 * @title L1StandardBridge
3 * @dev L1 ETH اور ERC20 برج ایک ایسا کنٹریکٹ ہے جو جمع شدہ L1 فنڈز اور معیاری
4 * ٹوکنز کو اسٹور کرتا ہے جو L2 پر استعمال ہو رہے ہیں۔ یہ متعلقہ L2 برج کو ہم آہنگ (synchronize) کرتا ہے، اسے ڈپازٹس کے بارے میں مطلع کرتا ہے
5 * اور نئی حتمی شکل دی گئی واپسیوں (withdrawals) کے لیے اسے سنتا ہے۔
6 * */
7contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
8 using SafeERC20 for IERC20;

یہ لائن بتاتی ہے کہ جب بھی ہم IERC20 انٹرفیس استعمال کرتے ہیں تو ہم SafeERC20 ریپر (wrapper) استعمال کرنے کی وضاحت کیسے کرتے ہیں۔

1
2 /* *******************************
3 * ایکسٹرنل کنٹریکٹ ریفرنسز *
4 ******************************* */
5
6 address public l2TokenBridge;

L2StandardBridge کا ایڈریس۔

1
2 // L1 ٹوکن کو L2 ٹوکن سے میپ کرتا ہے تاکہ جمع شدہ L1 ٹوکن کا بیلنس معلوم کیا جا سکے
3 mapping(address => mapping(address => uint256)) public deposits;

اس طرح کی ڈبل میپنگ (mapping) (opens in a new tab) وہ طریقہ ہے جس سے آپ دو جہتی اسپارس ایرے (two-dimensional sparse array) (opens in a new tab) کی وضاحت کرتے ہیں۔ اس ڈیٹا اسٹرکچر میں ویلیوز کی شناخت deposit[L1 token addr][L2 token addr] کے طور پر کی جاتی ہے۔ ڈیفالٹ ویلیو صفر ہے۔ صرف وہ سیلز جو مختلف ویلیو پر سیٹ ہوتے ہیں انہیں اسٹوریج میں لکھا جاتا ہے۔

1
2 /* **************
3 * کنسٹرکٹر *
4 ************** */
5
6 // یہ کنٹریکٹ پراکسی کے پیچھے کام کرتا ہے، اس لیے کنسٹرکٹر کے پیرامیٹرز استعمال نہیں ہوں گے۔
7 constructor() CrossDomainEnabled(address(0)) {}

ہم اس کنٹریکٹ کو اپ گریڈ کرنے کے قابل ہونا چاہتے ہیں بغیر اسٹوریج میں موجود تمام متغیرات کو کاپی کیے. ایسا کرنے کے لیے ہم ایک Proxy (opens in a new tab) استعمال کرتے ہیں، ایک کنٹریکٹ جو کالز کو ایک الگ کنٹریکٹ میں منتقل کرنے کے لیے delegatecall (opens in a new tab) کا استعمال کرتا ہے جس کا ایڈریس پراکسی کنٹریکٹ کے ذریعے اسٹور کیا جاتا ہے (جب آپ اپ گریڈ کرتے ہیں تو آپ پراکسی کو وہ ایڈریس تبدیل کرنے کے لیے کہتے ہیں)۔ جب آپ delegatecall استعمال کرتے ہیں تو اسٹوریج کال کرنے والے کنٹریکٹ کا اسٹوریج ہی رہتا ہے، اس لیے تمام کنٹریکٹ اسٹیٹ متغیرات کی ویلیوز متاثر نہیں ہوتیں۔

اس پیٹرن کا ایک اثر یہ ہے کہ اس کنٹریکٹ کا اسٹوریج جو delegatecall کا کال کیا گیا ہے استعمال نہیں ہوتا اور اس لیے اسے پاس کی گئی کنسٹرکٹر ویلیوز سے کوئی فرق نہیں پڑتا۔ یہی وجہ ہے کہ ہم CrossDomainEnabled کنسٹرکٹر کو ایک بے معنی ویلیو فراہم کر سکتے ہیں۔ یہی وجہ ہے کہ ذیل میں دی گئی انیشلائزیشن کنسٹرکٹر سے الگ ہے۔

1 /* *****************
2 * انیشلائزیشن *
3 ***************** */
4
5 /* *
6 * @param _l1messenger کراس چین کمیونیکیشن کے لیے استعمال ہونے والا L1 میسنجر کا ایڈریس۔
7 * @param _l2TokenBridge L2 اسٹینڈرڈ برج کا ایڈریس۔ */
8 // slither-disable-next-line external-function

یہ Slither ٹیسٹ (opens in a new tab) ان فنکشنز کی نشاندہی کرتا ہے جنہیں کنٹریکٹ کوڈ سے کال نہیں کیا جاتا اور اس لیے انہیں public کے بجائے external قرار دیا جا سکتا ہے۔ external فنکشنز کی گیس لاگت کم ہو سکتی ہے، کیونکہ انہیں کال ڈیٹا (calldata) میں پیرامیٹرز فراہم کیے جا سکتے ہیں۔ public قرار دیے گئے فنکشنز کو کنٹریکٹ کے اندر سے قابل رسائی ہونا چاہیے۔ کنٹریکٹس اپنے کال ڈیٹا میں ترمیم نہیں کر سکتے، اس لیے پیرامیٹرز کو میموری میں ہونا چاہیے۔ جب ایسے فنکشن کو بیرونی طور پر کال کیا جاتا ہے، تو کال ڈیٹا کو میموری میں کاپی کرنا ضروری ہوتا ہے، جس پر گیس خرچ ہوتی ہے۔ اس صورت میں فنکشن کو صرف ایک بار کال کیا جاتا ہے، اس لیے یہ غیر موثر ہونا ہمارے لیے اہمیت نہیں رکھتا۔

1 function initialize(address _l1messenger, address _l2TokenBridge) public {
2 require(messenger == address(0), "Contract has already been initialized.");

initialize فنکشن کو صرف ایک بار کال کیا جانا چاہیے۔ اگر L1 کراس ڈومین میسنجر یا L2 ٹوکن برج کا ایڈریس تبدیل ہوتا ہے، تو ہم ایک نیا پراکسی اور ایک نیا برج بناتے ہیں جو اسے کال کرتا ہے۔ ایسا ہونے کا امکان نہیں ہے سوائے اس کے کہ جب پورا سسٹم اپ گریڈ ہو، جو کہ ایک بہت ہی نایاب واقعہ ہے۔

نوٹ کریں کہ اس فنکشن میں کوئی ایسا میکانزم نہیں ہے جو اس بات کو محدود کرے کہ اسے کون کال کر سکتا ہے۔ اس کا مطلب یہ ہے کہ نظریاتی طور پر ایک حملہ آور اس وقت تک انتظار کر سکتا ہے جب تک کہ ہم پراکسی اور برج کا پہلا ورژن تعینات نہ کر دیں اور پھر جائز صارف سے پہلے initialize فنکشن تک پہنچنے کے لیے فرنٹ رن (front-run) (opens in a new tab) کرے۔ لیکن اسے روکنے کے دو طریقے ہیں:

  1. اگر کنٹریکٹس براہ راست EOA کے ذریعے نہیں بلکہ ایک ایسی ٹرانزیکشن میں تعینات کیے جاتے ہیں جس میں کوئی دوسرا کنٹریکٹ انہیں بناتا ہے (opens in a new tab) تو پورا عمل ایٹمی (atomic) ہو سکتا ہے، اور کسی بھی دوسری ٹرانزیکشن کے عمل میں آنے سے پہلے ختم ہو سکتا ہے۔
  2. اگر initialize کی جائز کال ناکام ہو جاتی ہے تو نئے بنائے گئے پراکسی اور برج کو نظر انداز کرنا اور نئے بنانا ہمیشہ ممکن ہے۔
1 messenger = _l1messenger;
2 l2TokenBridge = _l2TokenBridge;
3 }

یہ وہ دو پیرامیٹرز ہیں جو برج کو جاننے کی ضرورت ہے۔

1
2 /* *************
3 * ڈپازٹنگ *
4 ************* */
5
6 /* * @dev موڈیفائر جس کے لیے بھیجنے والے کا EOA ہونا ضروری ہے۔ اس چیک کو ایک بدنیتی پر مبنی
7 * کنٹریکٹ initcode کے ذریعے بائی پاس کر سکتا ہے، لیکن یہ اس صارف کی غلطی کو روکتا ہے جس سے ہم بچنا چاہتے ہیں۔ */
8 modifier onlyEOA() {
9 // کنٹریکٹس سے ڈپازٹس کو روکنے کے لیے استعمال کیا جاتا ہے (غلطی سے ٹوکنز کے ضائع ہونے سے بچنے کے لیے)
10 require(!Address.isContract(msg.sender), "Account not EOA");
11 _;
12 }

یہی وجہ ہے کہ ہمیں OpenZeppelin کی Address یوٹیلیٹیز کی ضرورت تھی۔

1 /* *
2 * @dev اس فنکشن کو بغیر کسی ڈیٹا کے کال کیا جا سکتا ہے
3 * تاکہ کالر کے L2 بیلنس میں ETH کی ایک رقم جمع کی جا سکے۔
4 * چونکہ receive فنکشن ڈیٹا نہیں لیتا، اس لیے ایک محتاط
5 * ڈیفالٹ رقم L2 کو بھیج دی جاتی ہے۔ */
6 receive() external payable onlyEOA {
7 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));
8 }

یہ فنکشن ٹیسٹنگ کے مقاصد کے لیے موجود ہے۔ غور کریں کہ یہ انٹرفیس کی تعریفوں میں ظاہر نہیں ہوتا - یہ عام استعمال کے لیے نہیں ہے۔

1 /* *
2 * @inheritdoc IL1StandardBridge */
3 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {
4 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);
5 }
6
7 /* *
8 * @inheritdoc IL1StandardBridge */
9 function depositETHTo(
10 address _to,
11 uint32 _l2Gas,
12 bytes calldata _data
13 ) external payable {
14 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);
15 }

یہ دو فنکشنز _initiateETHDeposit کے گرد ریپرز (wrappers) ہیں، وہ فنکشن جو اصل ETH ڈپازٹ کو ہینڈل کرتا ہے۔

1 /* *
2 * @dev ETH کو اسٹور کر کے اور L2 ETH گیٹ وے کو ڈپازٹ کے بارے میں مطلع کر کے ڈپازٹس کی لاجک انجام دیتا ہے۔
3 * @param _from وہ اکاؤنٹ جس سے L1 پر ڈپازٹ لیا جائے گا۔
4 * @param _to وہ اکاؤنٹ جسے L2 پر ڈپازٹ دیا جائے گا۔
5 * @param _l2Gas L2 پر ڈپازٹ مکمل کرنے کے لیے درکار گیس لمٹ۔
6 * @param _data L2 کو بھیجنے کے لیے اختیاری ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
7 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
8 function _initiateETHDeposit(
9 address _from,
10 address _to,
11 uint32 _l2Gas,
12 bytes memory _data
13 ) internal {
14 // finalizeDeposit کال کے لیے calldata بنائیں
15 bytes memory message = abi.encodeWithSelector(

کراس ڈومین پیغامات کے کام کرنے کا طریقہ یہ ہے کہ منزل کے کنٹریکٹ کو پیغام کے ساتھ اس کے کال ڈیٹا کے طور پر کال کیا جاتا ہے۔ Solidity کنٹریکٹس ہمیشہ اپنے کال ڈیٹا کی تشریح ABI تصریحات (opens in a new tab) کے مطابق کرتے ہیں۔ Solidity فنکشن abi.encodeWithSelector (opens in a new tab) وہ کال ڈیٹا بناتا ہے۔

1 IL2ERC20Bridge.finalizeDeposit.selector,
2 address(0),
3 Lib_PredeployAddresses.OVM_ETH,
4 _from,
5 _to,
6 msg.value,
7 _data
8 );

یہاں پیغام ان پیرامیٹرز کے ساتھ finalizeDeposit فنکشن (opens in a new tab) کو کال کرنا ہے:

پیرامیٹرویلیومطلب
_l1Tokenaddress(0)L1 پر ETH (جو کہ ERC-20 ٹوکن نہیں ہے) کی نمائندگی کرنے کے لیے خصوصی ویلیو
_l2TokenLib_PredeployAddresses.OVM_ETHL2 کنٹریکٹ جو آپٹیمزم پر ETH کا انتظام کرتا ہے، 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (یہ کنٹریکٹ صرف اندرونی آپٹیمزم کے استعمال کے لیے ہے)
_from_fromL1 پر وہ ایڈریس جو ETH بھیجتا ہے
_to_toL2 پر وہ ایڈریس جو ETH وصول کرتا ہے
amountmsg.valueبھیجے گئے wei کی مقدار (جو پہلے ہی برج کو بھیجی جا چکی ہے)
_data_dataڈپازٹ کے ساتھ منسلک کرنے کے لیے اضافی ڈیٹا
1 // calldata کو L2 میں بھیجیں
2 // slither-disable-next-line reentrancy-events
3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

کراس ڈومین میسنجر کے ذریعے پیغام بھیجیں۔

1 // slither-disable-next-line reentrancy-events
2 emit ETHDepositInitiated(_from, _to, msg.value, _data);
3 }

کسی بھی ڈی سینٹرلائزڈ ایپلیکیشن کو مطلع کرنے کے لیے ایک ایونٹ ایمٹ (emit) کریں جو اس ٹرانسفر کو سنتی ہے۔

1 /* *
2 * @inheritdoc IL1ERC20Bridge */
3 function depositERC20(
4 .
5 .
6 .
7 ) external virtual onlyEOA {
8 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);
9 }
10
11 /* *
12 * @inheritdoc IL1ERC20Bridge */
13 function depositERC20To(
14 .
15 .
16 .
17 ) external virtual {
18 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);
19 }

یہ دو فنکشنز _initiateERC20Deposit کے گرد ریپرز ہیں، وہ فنکشن جو اصل ERC-20 ڈپازٹ کو ہینڈل کرتا ہے۔

1 /* *
2 * @dev L2 Deposited Token کنٹریکٹ کو ڈپازٹ کے بارے میں مطلع کر کے اور L1 فنڈز کو لاک کرنے کے لیے ہینڈلر کو کال کر کے (جیسے transferFrom) ڈپازٹس کی لاجک انجام دیتا ہے۔
3 *
4 * @param _l1Token اس L1 ERC20 کا ایڈریس جو ہم جمع کر رہے ہیں
5 * @param _l2Token L1 کے متعلقہ L2 ERC20 کا ایڈریس
6 * @param _from وہ اکاؤنٹ جس سے L1 پر ڈپازٹ لیا جائے گا
7 * @param _to وہ اکاؤنٹ جسے L2 پر ڈپازٹ دیا جائے گا
8 * @param _amount جمع کرنے کے لیے ERC20 کی رقم۔
9 * @param _l2Gas L2 پر ڈپازٹ مکمل کرنے کے لیے درکار گیس لمٹ۔
10 * @param _data L2 کو بھیجنے کے لیے اختیاری ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
11 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
12 function _initiateERC20Deposit(
13 address _l1Token,
14 address _l2Token,
15 address _from,
16 address _to,
17 uint256 _amount,
18 uint32 _l2Gas,
19 bytes calldata _data
20 ) internal {

یہ فنکشن اوپر دیے گئے _initiateETHDeposit سے ملتا جلتا ہے، جس میں چند اہم فرق ہیں۔ پہلا فرق یہ ہے کہ یہ فنکشن ٹوکن ایڈریسز اور ٹرانسفر کی جانے والی رقم کو پیرامیٹرز کے طور پر وصول کرتا ہے۔ ETH کے معاملے میں برج کی کال میں پہلے ہی برج اکاؤنٹ میں اثاثے کی منتقلی شامل ہوتی ہے (msg.value

1 // جب L1 پر ڈپازٹ شروع کیا جاتا ہے، تو L1 برج مستقبل کی
2 // واپسیوں (withdrawals) کے لیے فنڈز اپنے پاس ٹرانسفر کر لیتا ہے۔ safeTransferFrom یہ بھی چیک کرتا ہے کہ آیا کنٹریکٹ میں کوڈ موجود ہے، لہذا یہ ناکام ہو جائے گا اگر
3 // _from ایک EOA یا address(0) ہو۔
4 // slither-disable-next-line reentrancy-events, reentrancy-benign
5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);

ERC-20 ٹوکن ٹرانسفرز ETH سے مختلف عمل کی پیروی کرتے ہیں:

  1. صارف (_from) برج کو مناسب ٹوکنز منتقل کرنے کا الاؤنس دیتا ہے۔
  2. صارف ٹوکن کنٹریکٹ کے ایڈریس، رقم وغیرہ کے ساتھ برج کو کال کرتا ہے۔
  3. برج ڈپازٹ کے عمل کے حصے کے طور پر ٹوکنز (خود کو) منتقل کرتا ہے۔

پہلا قدم آخری دو سے الگ ٹرانزیکشن میں ہو سکتا ہے۔ تاہم، فرنٹ رننگ کوئی مسئلہ نہیں ہے کیونکہ وہ دو فنکشنز جو _initiateERC20Deposit کو کال کرتے ہیں (depositERC20 اور depositERC20To) وہ صرف msg.sender کو _from پیرامیٹر کے طور پر استعمال کرتے ہوئے اس فنکشن کو کال کرتے ہیں۔

1 // _l2Token.finalizeDeposit(_to, _amount) کے لیے calldata بنائیں
2 bytes memory message = abi.encodeWithSelector(
3 IL2ERC20Bridge.finalizeDeposit.selector,
4 _l1Token,
5 _l2Token,
6 _from,
7 _to,
8 _amount,
9 _data
10 );
11
12 // calldata کو L2 میں بھیجیں
13 // slither-disable-next-line reentrancy-events, reentrancy-benign
14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);
15
16 // slither-disable-next-line reentrancy-benign
17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;

ٹوکنز کی جمع شدہ رقم کو deposits ڈیٹا اسٹرکچر میں شامل کریں۔ L2 پر ایک سے زیادہ ایڈریسز ہو سکتے ہیں جو ایک ہی L1 ERC-20 ٹوکن سے مطابقت رکھتے ہوں، اس لیے ڈپازٹس کا ٹریک رکھنے کے لیے L1 ERC-20 ٹوکن کے برج کے بیلنس کا استعمال کرنا کافی نہیں ہے۔

1
2 // slither-disable-next-line reentrancy-events
3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);
4 }
5
6 /* ************************
7 * کراس چین فنکشنز *
8 ************************ */
9
10 /* *
11 * @inheritdoc IL1StandardBridge */
12 function finalizeETHWithdrawal(
13 address _from,
14 address _to,
15 uint256 _amount,
16 bytes calldata _data

L2 برج L2 کراس ڈومین میسنجر کو ایک پیغام بھیجتا ہے جس کی وجہ سے L1 کراس ڈومین میسنجر اس فنکشن کو کال کرتا ہے (یقیناً، ایک بار جب پیغام کو حتمی شکل دینے والی ٹرانزیکشن (opens in a new tab) L1 پر جمع ہو جاتی ہے)۔

1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {

یقینی بنائیں کہ یہ ایک جائز پیغام ہے، جو کراس ڈومین میسنجر سے آ رہا ہے اور L2 ٹوکن برج سے شروع ہو رہا ہے۔ یہ فنکشن برج سے ETH نکالنے کے لیے استعمال ہوتا ہے، اس لیے ہمیں یہ یقینی بنانا ہوگا کہ اسے صرف مجاز کالر کے ذریعے کال کیا جائے۔

1 // slither-disable-next-line reentrancy-events
2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));

ETH منتقل کرنے کا طریقہ یہ ہے کہ وصول کنندہ کو msg.value میں wei کی مقدار کے ساتھ کال کی جائے۔

1 require(success, "TransferHelper::safeTransferETH: ETH transfer failed");
2
3 // slither-disable-next-line reentrancy-events
4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);

ودڈراول کے بارے میں ایک ایونٹ ایمٹ کریں۔

1 }
2
3 /* *
4 * @inheritdoc IL1ERC20Bridge */
5 function finalizeERC20Withdrawal(
6 address _l1Token,
7 address _l2Token,
8 address _from,
9 address _to,
10 uint256 _amount,
11 bytes calldata _data
12 ) external onlyFromCrossDomainAccount(l2TokenBridge) {

یہ فنکشن اوپر دیے گئے finalizeETHWithdrawal سے ملتا جلتا ہے، جس میں ERC-20 ٹوکنز کے لیے ضروری تبدیلیاں کی گئی ہیں۔

1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;

deposits ڈیٹا اسٹرکچر کو اپ ڈیٹ کریں۔

1
2 // جب L1 پر واپسی (withdrawal) کو حتمی شکل دی جاتی ہے، تو L1 برج فنڈز نکالنے والے کو ٹرانسفر کر دیتا ہے
3 // slither-disable-next-line reentrancy-events
4 IERC20(_l1Token).safeTransfer(_to, _amount);
5
6 // slither-disable-next-line reentrancy-events
7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);
8 }
9
10
11 /* ****************************
12 * عارضی - ETH کی منتقلی (Migrating ETH) *
13 **************************** */
14
15 /* *
16 * @dev اکاؤنٹ میں ETH بیلنس کا اضافہ کرتا ہے۔ اس کا مقصد ETH کو
17 * پرانے گیٹ وے سے نئے گیٹ وے پر منتقل کرنے کی اجازت دینا ہے۔
18 * نوٹ: اسے صرف ایک اپ گریڈ کے لیے چھوڑا گیا ہے تاکہ ہم پرانے کنٹریکٹ سے
19 * منتقل شدہ ETH وصول کر سکیں۔ */
20 function donateETH() external payable {}
21}

برج کا ایک ابتدائی نفاذ (implementation) تھا۔ جب ہم اس نفاذ سے اس کی طرف منتقل ہوئے، تو ہمیں تمام اثاثوں کو منتقل کرنا پڑا۔ ERC-20 ٹوکنز کو صرف منتقل کیا جا سکتا ہے۔ تاہم، کسی کنٹریکٹ میں ETH منتقل کرنے کے لیے آپ کو اس کنٹریکٹ کی منظوری کی ضرورت ہوتی ہے، جو کہ donateETH ہمیں فراہم کرتا ہے۔

L2 پر ERC-20 ٹوکنز

کسی ERC-20 ٹوکن کو معیاری برج میں فٹ ہونے کے لیے، اسے معیاری برج، اور صرف معیاری برج کو ٹوکن منٹ کرنے کی اجازت دینے کی ضرورت ہے۔ یہ ضروری ہے کیونکہ برجز کو یہ یقینی بنانے کی ضرورت ہوتی ہے کہ آپٹیمزم پر گردش کرنے والے ٹوکنز کی تعداد L1 برج کنٹریکٹ کے اندر مقفل ٹوکنز کی تعداد کے برابر ہے۔ اگر L2 پر بہت زیادہ ٹوکنز ہوں تو کچھ صارفین اپنے اثاثوں کو واپس L1 پر برج کرنے سے قاصر ہوں گے۔ ایک قابل اعتماد برج کے بجائے، ہم بنیادی طور پر فریکشنل ریزرو بینکنگ (fractional reserve banking) (opens in a new tab) کو دوبارہ بنائیں گے۔ اگر L1 پر بہت زیادہ ٹوکنز ہوں، تو ان میں سے کچھ ٹوکنز ہمیشہ کے لیے برج کنٹریکٹ کے اندر مقفل رہیں گے کیونکہ L2 ٹوکنز کو برن کیے بغیر انہیں جاری کرنے کا کوئی طریقہ نہیں ہے۔

IL2StandardERC20

L2 پر ہر ERC-20 ٹوکن جو معیاری برج استعمال کرتا ہے اسے یہ انٹرفیس (opens in a new tab) فراہم کرنے کی ضرورت ہوتی ہے، جس میں وہ فنکشنز اور ایونٹس ہوتے ہیں جن کی معیاری برج کو ضرورت ہوتی ہے۔

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4import { 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 سپورٹ کی منصوبہ بندی کی گئی تھی یا نہیں۔

1
2 function mint(address _to, uint256 _amount) external;
3
4 function burn(address _from, uint256 _amount) external;
5
6 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: MIT
2pragma solidity ^0.8.9;
3
4import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

OpenZeppelin ERC-20 کنٹریکٹ (opens in a new tab)۔ آپٹیمزم پہیے کو دوبارہ ایجاد کرنے پر یقین نہیں رکھتا، خاص طور پر جب پہیے کا اچھی طرح سے آڈٹ کیا گیا ہو اور اسے اثاثے رکھنے کے لیے کافی قابل اعتماد ہونے کی ضرورت ہو۔

1import "./IL2StandardERC20.sol";
2
3contract L2StandardERC20 is IL2StandardERC20, ERC20 {
4 address public l1Token;
5 address public l2Bridge;

یہ وہ دو اضافی کنفیگریشن پیرامیٹرز ہیں جن کی ہمیں ضرورت ہوتی ہے اور ERC-20 کو عام طور پر نہیں ہوتی۔

1
2 /* *
3 * @param _l2Bridge L2 اسٹینڈرڈ برج کا ایڈریس۔
4 * @param _l1Token متعلقہ L1 ٹوکن کا ایڈریس۔
5 * @param _name ERC20 کا نام۔
6 * @param _symbol ERC20 کی علامت (symbol)۔ */
7 constructor(
8 address _l2Bridge,
9 address _l1Token,
10 string memory _name,
11 string memory _symbol
12 ) ERC20(_name, _symbol) {
13 l1Token = _l1Token;
14 l2Bridge = _l2Bridge;
15 }

پہلے اس کنٹریکٹ کے لیے کنسٹرکٹر کو کال کریں جس سے ہم وراثت میں لیتے ہیں (ERC20(_name, _symbol)) اور پھر اپنے متغیرات سیٹ کریں۔

1
2 modifier onlyL2Bridge() {
3 require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");
4 _;
5 }
6
7
8 // slither-disable-next-line external-function
9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {
10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC165
11 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^
12 IL2StandardERC20.mint.selector ^
13 IL2StandardERC20.burn.selector;
14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;
15 }

اس طرح ERC-165 (opens in a new tab) کام کرتا ہے۔ ہر انٹرفیس سپورٹڈ فنکشنز کی ایک تعداد ہے، اور ان فنکشنز کے ABI فنکشن سلیکٹرز (opens in a new tab) کے exclusive or (opens in a new tab) کے طور پر پہچانا جاتا ہے۔

L2 برج ERC-165 کو ایک سینیٹی چیک (sanity check) کے طور پر استعمال کرتا ہے تاکہ یہ یقینی بنایا جا سکے کہ جس ERC-20 کنٹریکٹ کو یہ اثاثے بھیجتا ہے وہ ایک IL2StandardERC20 ہے۔

نوٹ: بدمعاش کنٹریکٹ کو supportsInterface کے غلط جوابات فراہم کرنے سے روکنے کے لیے کچھ نہیں ہے، اس لیے یہ ایک سینیٹی چیک میکانزم ہے، سیکیورٹی میکانزم نہیں۔

1 // slither-disable-next-line external-function
2 function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {
3 _mint(_to, _amount);
4
5 emit Mint(_to, _amount);
6 }
7
8 // slither-disable-next-line external-function
9 function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {
10 _burn(_from, _amount);
11
12 emit Burn(_from, _amount);
13 }
14}

صرف L2 برج کو اثاثے منٹ اور برن کرنے کی اجازت ہے۔

_mint اور _burn دراصل OpenZeppelin ERC-20 کنٹریکٹ میں بیان کیے گئے ہیں۔ وہ کنٹریکٹ انہیں بیرونی طور پر ظاہر نہیں کرتا، کیونکہ ٹوکنز کو منٹ اور برن کرنے کی شرائط اتنی ہی مختلف ہیں جتنے ERC-20 کو استعمال کرنے کے طریقے۔

L2 برج کوڈ

یہ وہ کوڈ ہے جو آپٹیمزم پر برج چلاتا ہے۔ اس کنٹریکٹ کا سورس یہاں ہے (opens in a new tab)۔

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4/* انٹرفیس امپورٹس */
5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";
6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";
7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";

IL2ERC20Bridge (opens in a new tab) انٹرفیس اس L1 مساوی سے بہت ملتا جلتا ہے جو ہم نے اوپر دیکھا۔ دو اہم فرق ہیں:

  1. L1 پر آپ ڈپازٹس شروع کرتے ہیں اور ودڈراولز کو حتمی شکل دیتے ہیں۔ یہاں آپ ودڈراولز شروع کرتے ہیں اور ڈپازٹس کو حتمی شکل دیتے ہیں۔
  2. L1 پر ETH اور ERC-20 ٹوکنز کے درمیان فرق کرنا ضروری ہے۔ L2 پر ہم دونوں کے لیے ایک ہی فنکشنز استعمال کر سکتے ہیں کیونکہ اندرونی طور پر آپٹیمزم پر ETH بیلنسز کو 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (opens in a new tab) ایڈریس کے ساتھ ERC-20 ٹوکن کے طور پر ہینڈل کیا جاتا ہے۔
1/* لائبریری امپورٹس */
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";
5
6/* کنٹریکٹ امپورٹس */
7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";
8
9/* *
10 * @title L2StandardBridge
11 * @dev L2 اسٹینڈرڈ برج ایک ایسا کنٹریکٹ ہے جو L1 اسٹینڈرڈ برج کے ساتھ مل کر کام کرتا ہے تاکہ
12 * L1 اور L2 کے درمیان ETH اور ERC20 کی منتقلی کو ممکن بنایا جا سکے۔
13 * یہ کنٹریکٹ نئے ٹوکنز کے لیے منٹر (minter) کے طور پر کام کرتا ہے جب اسے L1 اسٹینڈرڈ برج میں
14 * ڈپازٹس کے بارے میں معلوم ہوتا ہے۔
15 * یہ کنٹریکٹ واپسی (withdrawal) کے لیے مطلوبہ ٹوکنز کو برن (burn) کرنے کا کام بھی کرتا ہے، اور L1
16 * برج کو L1 فنڈز جاری کرنے کی اطلاع دیتا ہے۔ */
17contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
18 /* *******************************
19 * ایکسٹرنل کنٹریکٹ ریفرنسز *
20 ******************************* */
21
22 address public l1TokenBridge;

L1 برج کے ایڈریس کا ٹریک رکھیں۔ نوٹ کریں کہ L1 مساوی کے برعکس، یہاں ہمیں اس متغیر کی ضرورت ہے۔ L1 برج کا ایڈریس پہلے سے معلوم نہیں ہوتا۔

1
2 /* **************
3 * کنسٹرکٹر *
4 ************** */
5
6 /* *
7 * @param _l2CrossDomainMessenger اس کنٹریکٹ کے ذریعے استعمال ہونے والا کراس ڈومین میسنجر۔
8 * @param _l1TokenBridge مین چین پر تعینات (deployed) L1 برج کا ایڈریس۔ */
9 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)
10 CrossDomainEnabled(_l2CrossDomainMessenger)
11 {
12 l1TokenBridge = _l1TokenBridge;
13 }
14
15 /* **************
16 * ود ڈرائنگ (Withdrawing) *
17 ************** */
18
19 /* *
20 * @inheritdoc IL2ERC20Bridge */
21 function withdraw(
22 address _l2Token,
23 uint256 _amount,
24 uint32 _l1Gas,
25 bytes calldata _data
26 ) external virtual {
27 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);
28 }
29
30 /* *
31 * @inheritdoc IL2ERC20Bridge */
32 function withdrawTo(
33 address _l2Token,
34 address _to,
35 uint256 _amount,
36 uint32 _l1Gas,
37 bytes calldata _data
38 ) external virtual {
39 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);
40 }

یہ دو فنکشنز ودڈراولز شروع کرتے ہیں۔ نوٹ کریں کہ L1 ٹوکن ایڈریس بتانے کی کوئی ضرورت نہیں ہے۔ L2 ٹوکنز سے توقع کی جاتی ہے کہ وہ ہمیں L1 مساوی کا ایڈریس بتائیں گے۔

1
2 /* *
3 * @dev ٹوکن کو برن کر کے اور L1 ٹوکن گیٹ وے کو واپسی (withdrawal) کے بارے میں مطلع کر کے
4 * واپسی کی لاجک انجام دیتا ہے۔
5 * @param _l2Token L2 ٹوکن کا ایڈریس جہاں سے واپسی شروع کی گئی ہے۔
6 * @param _from وہ اکاؤنٹ جس سے L2 پر واپسی لی جائے گی۔
7 * @param _to وہ اکاؤنٹ جسے L1 پر واپسی دی جائے گی۔
8 * @param _amount نکالنے کے لیے ٹوکن کی رقم۔
9 * @param _l1Gas غیر استعمال شدہ، لیکن ممکنہ مستقبل کی مطابقت (forward compatibility) کے پیش نظر شامل کیا گیا ہے۔
10 * @param _data L1 کو بھیجنے کے لیے اختیاری ڈیٹا۔ یہ ڈیٹا بیرونی کنٹریکٹس کی سہولت کے لیے
11 * فراہم کیا گیا ہے۔ زیادہ سے زیادہ لمبائی نافذ کرنے کے علاوہ، یہ کنٹریکٹس اس کے مواد کی کوئی ضمانت نہیں دیتے۔ */
12 function _initiateWithdrawal(
13 address _l2Token,
14 address _from,
15 address _to,
16 uint256 _amount,
17 uint32 _l1Gas,
18 bytes calldata _data
19 ) internal {
20 // جب واپسی شروع کی جاتی ہے، تو ہم نکالنے والے کے فنڈز کو برن کر دیتے ہیں تاکہ بعد میں L2 پر
21 // استعمال کو روکا جا سکے
22 // slither-disable-next-line reentrancy-events
23 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);

غور کریں کہ ہم _from پیرامیٹر پر انحصار نہیں کر رہے ہیں بلکہ msg.sender پر کر رہے ہیں جسے جعلی بنانا بہت مشکل ہے (جہاں تک میں جانتا ہوں، ناممکن ہے)۔

1
2 // l1TokenBridge.finalizeERC20Withdrawal(_to, _amount) کے لیے calldata بنائیں
3 // slither-disable-next-line reentrancy-events
4 address l1Token = IL2StandardERC20(_l2Token).l1Token();
5 bytes memory message;
6
7 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 _data
7 );
8 } else {
9 message = abi.encodeWithSelector(
10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
11 l1Token,
12 _l2Token,
13 _from,
14 _to,
15 _amount,
16 _data
17 );
18 }
19
20 // L1 برج کو پیغام بھیجیں
21 // slither-disable-next-line reentrancy-events
22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);
23
24 // slither-disable-next-line reentrancy-events
25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);
26 }
27
28 /* ***********************************
29 * کراس چین فنکشن: ڈپازٹنگ *
30 *********************************** */
31
32 /* *
33 * @inheritdoc IL2ERC20Bridge */
34 function finalizeDeposit(
35 address _l1Token,
36 address _l2Token,
37 address _from,
38 address _to,
39 uint256 _amount,
40 bytes calldata _data

اس فنکشن کو L1StandardBridge کے ذریعے کال کیا جاتا ہے۔

1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {

یقینی بنائیں کہ پیغام کا ذریعہ جائز ہے۔ یہ اہم ہے کیونکہ یہ فنکشن _mint کو کال کرتا ہے اور اسے ایسے ٹوکنز دینے کے لیے استعمال کیا جا سکتا ہے جو L1 پر برج کی ملکیت والے ٹوکنز کے ذریعے کور نہیں ہوتے۔

1 // چیک کریں کہ ٹارگٹ ٹوکن مطابقت رکھتا ہے (compliant) اور
2 // تصدیق کریں کہ L1 پر جمع کیا گیا ٹوکن یہاں L2 کے جمع شدہ ٹوکن کی نمائندگی سے میل کھاتا ہے
3 if (
4 // slither-disable-next-line reentrancy-events
5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
6 _l1Token == IL2StandardERC20(_l2Token).l1Token()

سینیٹی چیکس (Sanity checks):

  1. درست انٹرفیس سپورٹڈ ہے
  2. L2 ERC-20 کنٹریکٹ کا L1 ایڈریس ٹوکنز کے L1 ذریعہ سے میل کھاتا ہے
1 ) {
2 // جب ڈپازٹ کو حتمی شکل دی جاتی ہے، تو ہم L2 پر اکاؤنٹ میں اتنی ہی رقم کے
3 // ٹوکنز جمع کر دیتے ہیں۔
4 // slither-disable-next-line reentrancy-events
5 IL2StandardERC20(_l2Token).mint(_to, _amount);
6 // slither-disable-next-line reentrancy-events
7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);

اگر سینیٹی چیکس پاس ہو جاتے ہیں، تو ڈپازٹ کو حتمی شکل دیں:

  1. ٹوکنز منٹ کریں
  2. مناسب ایونٹ ایمٹ کریں
1 } else {
2 // یا تو وہ L2 ٹوکن جس میں ڈپازٹ کیا جا رہا ہے، اپنے L1 ٹوکن کے درست ایڈریس کے بارے میں متفق نہیں ہے
3 // یا درست انٹرفیس کو سپورٹ نہیں کرتا۔
4 // ایسا صرف اس صورت میں ہونا چاہیے جب کوئی بدنیتی پر مبنی L2 ٹوکن ہو، یا اگر صارف نے کسی طرح
5 // ڈپازٹ کرنے کے لیے غلط L2 ٹوکن ایڈریس بتا دیا ہو۔
6 // دونوں صورتوں میں، ہم یہاں عمل کو روک دیتے ہیں اور ایک واپسی (withdrawal) کا
7 // پیغام بناتے ہیں تاکہ صارفین کچھ صورتوں میں اپنے فنڈز نکال سکیں۔
8 // بدنیتی پر مبنی ٹوکن کنٹریکٹس کو مکمل طور پر روکنے کا کوئی طریقہ نہیں ہے، لیکن یہ
9 // صارف کی غلطی کو محدود کرتا ہے اور بدنیتی پر مبنی کنٹریکٹ کے کچھ رویوں کو کم کرتا ہے۔

اگر کسی صارف نے غلط L2 ٹوکن ایڈریس استعمال کر کے کوئی قابل شناخت غلطی کی ہے، تو ہم ڈپازٹ منسوخ کرنا اور L1 پر ٹوکنز واپس کرنا چاہتے ہیں۔ L2 سے ہم ایسا کرنے کا واحد طریقہ یہ ہے کہ ایک پیغام بھیجیں جسے فالٹ چیلنج پیریڈ کا انتظار کرنا پڑے گا، لیکن یہ صارف کے لیے ٹوکنز کو مستقل طور پر کھونے سے کہیں بہتر ہے۔

1 bytes memory message = abi.encodeWithSelector(
2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
3 _l1Token,
4 _l2Token,
5 _to, // ڈپازٹ کو بھیجنے والے کو واپس بھیجنے (bounce back) کے لیے یہاں _to اور _from کو تبدیل کر دیا گیا ہے
6 _from,
7 _amount,
8 _data
9 );
10
11 // L1 برج کو پیغام بھیجیں
12 // slither-disable-next-line reentrancy-events
13 sendCrossDomainMessage(l1TokenBridge, 0, message);
14 // slither-disable-next-line reentrancy-events
15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);
16 }
17 }
18}

نتیجہ

معیاری برج اثاثوں کی منتقلی کے لیے سب سے لچکدار میکانزم ہے۔ تاہم، چونکہ یہ بہت عام (generic) ہے اس لیے اسے استعمال کرنا ہمیشہ سب سے آسان میکانزم نہیں ہوتا۔ خاص طور پر ودڈراولز کے لیے، زیادہ تر صارفین تھرڈ پارٹی برجز (opens in a new tab) استعمال کرنے کو ترجیح دیتے ہیں جو چیلنج پیریڈ کا انتظار نہیں کرتے اور ودڈراول کو حتمی شکل دینے کے لیے مرکل پروف (Merkle proof) کی ضرورت نہیں ہوتی۔

یہ برجز عام طور پر L1 پر اثاثے رکھ کر کام کرتے ہیں، جو وہ فوری طور پر ایک چھوٹی سی فیس کے عوض فراہم کرتے ہیں (اکثر معیاری برج ودڈراول کے لیے گیس کی لاگت سے کم)۔ جب برج (یا اسے چلانے والے لوگ) L1 اثاثوں کی کمی کی توقع کرتے ہیں تو یہ L2 سے کافی اثاثے منتقل کرتا ہے۔ چونکہ یہ بہت بڑے ودڈراولز ہوتے ہیں، اس لیے ودڈراول کی لاگت ایک بڑی رقم پر تقسیم ہو جاتی ہے اور یہ بہت کم فیصد ہوتی ہے۔

امید ہے کہ اس مضمون نے آپ کو یہ سمجھنے میں مدد کی ہوگی کہ لیئر 2 کیسے کام کرتی ہے، اور ایسا Solidity کوڈ کیسے لکھا جائے جو واضح اور محفوظ ہو۔

میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔

صفحہ کی آخری اپ ڈیٹ: ۳ مارچ، ۲۰۲۶

کیا یہ ٹیوٹوریل مددگار تھا؟