মূল কন্টেন্টে যান

অপ্টিমিজম স্ট্যান্ডার্ড ব্রিজ কন্ট্রাক্ট ওয়াকথ্রু

Solidity
ব্রিজ
লেয়ার ২
ইন্টারমিডিয়েট
ওরি পোমেরান্টজ
30 মার্চ, 2022
31 মিনিট পড়া

Optimism (opens in a new tab) হলো একটি অপ্টিমেস্টিক রোলআপ। অপ্টিমেস্টিক রোলআপ ইথিরিয়াম মেইননেট (যা লেয়ার 1 বা L1 নামেও পরিচিত)-এর চেয়ে অনেক কম খরচে লেনদেন প্রসেস করতে পারে কারণ নেটওয়ার্ক-এর প্রতিটি নোড-এর পরিবর্তে শুধুমাত্র কয়েকটি নোড দ্বারা লেনদেন প্রসেস করা হয়। একই সাথে, সমস্ত ডেটা L1-এ লেখা হয় যাতে মেইননেট-এর সমস্ত ইন্টিগ্রিটি এবং অ্যাভেইলেবিলিটি গ্যারান্টির সাথে সবকিছু প্রমাণ এবং পুনর্গঠন করা যায়।

Optimism (বা অন্য কোনো L2)-এ L1 অ্যাসেট ব্যবহার করতে, অ্যাসেটগুলোকে ব্রিজ করতে হবে। এটি অর্জনের একটি উপায় হলো ব্যবহারকারীদের L1-এ অ্যাসেট (ETH এবং ERC-20 টোকেন সবচেয়ে সাধারণ) লক করা এবং L2-এ ব্যবহার করার জন্য সমতুল্য অ্যাসেট গ্রহণ করা। শেষ পর্যন্ত, যার কাছেই এগুলো থাকুক না কেন, সে হয়তো এগুলোকে আবার L1-এ ব্রিজ করতে চাইতে পারে। এটি করার সময়, অ্যাসেটগুলো L2-এ বার্ন করা হয় এবং তারপর L1-এ ব্যবহারকারীর কাছে ফেরত দেওয়া হয়।

এভাবেই Optimism স্ট্যান্ডার্ড ব্রিজ (opens in a new tab) কাজ করে। এই আর্টিকেলে আমরা সেই ব্রিজের সোর্স কোড নিয়ে আলোচনা করব যাতে এটি কীভাবে কাজ করে তা দেখা যায় এবং ভালোভাবে লেখা Solidity কোডের উদাহরণ হিসেবে এটি অধ্যয়ন করা যায়।

কন্ট্রোল ফ্লো

ব্রিজের দুটি প্রধান ফ্লো রয়েছে:

  • ডিপোজিট (L1 থেকে L2-তে)
  • উত্তোলন (L2 থেকে L1-এ)

ডিপোজিট ফ্লো

লেয়ার 1

  1. যদি কোনো ERC-20 ডিপোজিট করা হয়, তবে ডিপোজিটর ব্রিজকে ডিপোজিট করা পরিমাণ খরচ করার অনুমতি দেয়
  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 টোকেন কন্ট্রাক্টটি সঠিক কি না:
    • L2 কন্ট্রাক্ট রিপোর্ট করে যে এর L1 কাউন্টারপার্টটি ঠিক সেই কন্ট্রাক্ট যেখান থেকে L1-এ টোকেনগুলো এসেছে
    • L2 কন্ট্রাক্ট রিপোর্ট করে যে এটি সঠিক ইন্টারফেস সাপোর্ট করে (ERC-165 ব্যবহার করে (opens in a new tab))।
  3. যদি L2 কন্ট্রাক্টটি সঠিক হয়, তবে সঠিক এডড্রেস-এ উপযুক্ত সংখ্যক টোকেন মিন্ট করতে এটিকে কল করুন। যদি না হয়, তবে ব্যবহারকারীকে 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

Optimism-এর বেশিরভাগ কোড 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(

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 (মেইননেট)-এ এবং chainId 10 সহ এডড্রেসটি L2 (Optimism)-তে রয়েছে। অন্য দুটি chainId ভ্যালু হলো Kovan টেস্ট নেটওয়ার্ক (42) এবং Optimistic Kovan টেস্ট নেটওয়ার্ক (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-তে এটি একটি প্রি-ডিপ্লয়ড কন্ট্রাক্ট, তাই এটি সর্বদা 0x4200000000000000000000000000000000000010 এডড্রেস-এ থাকে। এটি এখানে L2 ব্রিজের সাথে সামঞ্জস্যের জন্য রয়েছে, কারণ L1 ব্রিজের এডড্রেস জানা সহজ নয়।

1 /* *
2 * @dev L2-তে কলারের ব্যালেন্সে নির্দিষ্ট পরিমাণ ERC20 ডিপোজিট করুন।
3 * @param _l1Token আমরা যে L1 ERC20 ডিপোজিট করছি তার ঠিকানা
4 * @param _l2Token L1-এর সংশ্লিষ্ট L2 ERC20-এর ঠিকানা
5 * @param _amount ডিপোজিট করার জন্য ERC20-এর পরিমাণ
6 * @param _l2Gas L2-তে ডিপোজিট সম্পন্ন করার জন্য প্রয়োজনীয় গ্যাস লিমিট।
7 * @param _data L2-তে ফরোয়ার্ড করার জন্য ঐচ্ছিক ডেটা। এই ডেটা শুধুমাত্র এক্সটার্নাল কন্ট্রাক্টগুলোর সুবিধার জন্য প্রদান করা হয়েছে। সর্বোচ্চ দৈর্ঘ্য প্রয়োগ করা ছাড়া, এই কন্ট্রাক্টগুলো এর বিষয়বস্তু সম্পর্কে কোনো গ্যারান্টি দেয় না। */
8 function depositERC20(
9 address _l1Token,
10 address _l2Token,
11 uint256 _amount,
12 uint32 _l2Gas,
13 bytes calldata _data
14 ) external;

_l2Gas প্যারামিটার হলো L2 গ্যাস-এর পরিমাণ যা লেনদেন খরচ করার অনুমতি পায়। একটি নির্দিষ্ট (উচ্চ) সীমা পর্যন্ত, এটি বিনামূল্যে (opens in a new tab), তাই মিন্ট করার সময় ERC-20 কন্ট্রাক্ট যদি সত্যিই অদ্ভুত কিছু না করে, তবে এটি কোনো সমস্যা হওয়া উচিত নয়। এই ফাংশনটি সাধারণ পরিস্থিতির যত্ন নেয়, যেখানে একজন ব্যবহারকারী একটি ভিন্ন ব্লকচেইন-এ একই এডড্রেস-এ অ্যাসেট ব্রিজ করে।

1 /* *
2 * @dev L2-তে প্রাপকের ব্যালেন্সে নির্দিষ্ট পরিমাণ ERC20 ডিপোজিট করুন।
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 function depositERC20To(
10 address _l1Token,
11 address _l2Token,
12 address _to,
13 uint256 _amount,
14 uint32 _l2Gas,
15 bytes calldata _data
16 ) external;

এই ফাংশনটি প্রায় depositERC20-এর মতোই, তবে এটি আপনাকে একটি ভিন্ন এডড্রেস-এ ERC-20 পাঠাতে দেয়।

1 /* ************************
2 * ক্রস-চেইন ফাংশনস *
3 ************************ */
4
5 /* *
6 * @dev L2 থেকে L1-এ উত্তোলন সম্পন্ন করুন এবং প্রাপকের L1 ERC20 টোকেন ব্যালেন্সে ফান্ড ক্রেডিট করুন।
7 * L2 থেকে শুরু হওয়া উত্তোলন চূড়ান্ত না হলে এই কলটি ব্যর্থ হবে।
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 function finalizeERC20Withdrawal(
16 address _l1Token,
17 address _l2Token,
18 address _from,
19 address _to,
20 uint256 _amount,
21 bytes calldata _data
22 ) external;
23}

Optimism-এ উত্তোলন (এবং 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 ডিপোজিট করুন।
13 .
14 .
15 . */
16 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;
17
18 /* *
19 * @dev L2-তে প্রাপকের ব্যালেন্সে নির্দিষ্ট পরিমাণ ETH ডিপোজিট করুন।
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-এ উত্তোলন সম্পন্ন করুন এবং প্রাপকের L1 ETH টোকেন ব্যালেন্সে ফান্ড ক্রেডিট করুন। যেহেতু শুধুমাত্র xDomainMessenger এই ফাংশনটি কল করতে পারে, তাই উত্তোলন চূড়ান্ত হওয়ার আগে এটি কখনোই কল করা হবে না।
35 .
36 .
37 . */
38 function finalizeETHWithdrawal(
39 address _from,
40 address _to,
41 uint256 _amount,
42 bytes calldata _data
43 ) external;
44}

CrossDomainEnabled

অন্য লেয়ার-এ মেসেজ পাঠানোর জন্য উভয় ব্রিজ (L1 এবং L2) দ্বারা এই কন্ট্রাক্টটি (opens in a new tab) ইনহেরিট করা হয়।

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 * ব্যবহৃত কম্পাইলার: ইনহেরিটিং কন্ট্রাক্ট দ্বারা সংজ্ঞায়িত */
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 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {

ক্রস ডোমেইন মেসেজিং ব্লকচেইন-এ চলমান যেকোনো কন্ট্রাক্ট দ্বারা অ্যাক্সেসযোগ্য (হয় ইথিরিয়াম মেইননেট বা Optimism)। কিন্তু আমাদের প্রতিটি দিকের ব্রিজের প্রয়োজন শুধুমাত্র নির্দিষ্ট মেসেজগুলোকে বিশ্বাস করা যদি সেগুলো অন্য দিকের ব্রিজ থেকে আসে।

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 * @return যে ক্রস-ডোমেইন মেসেঞ্জার কন্ট্রাক্টটি ব্যবহার করা উচিত তার ঠিকানা। */
12 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
13 return ICrossDomainMessenger(messenger);
14 }

এই ফাংশনটি ক্রস ডোমেইন মেসেঞ্জার রিটার্ন করে। আমরা messenger ভেরিয়েবলের পরিবর্তে একটি ফাংশন ব্যবহার করি যাতে এই কন্ট্রাক্ট থেকে ইনহেরিট করা কন্ট্রাক্টগুলো কোন ক্রস ডোমেইন মেসেঞ্জার ব্যবহার করতে হবে তা নির্দিষ্ট করতে একটি এ্যালগরিদম ব্যবহার করতে পারে।

1
2 /* *
3 * অন্য ডোমেইনের একটি অ্যাকাউন্টে মেসেজ পাঠায়
4 * @param _crossDomainTarget গন্তব্য ডোমেইনে উদ্দিষ্ট প্রাপক
5 * @param _message টার্গেটে পাঠানোর জন্য ডেটা (সাধারণত `onlyFromCrossDomainAccount()` সহ একটি ফাংশনে কলডেটা)
6 * @param _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) হলো একটি স্ট্যাটিক অ্যানালাইজার যা Optimism প্রতিটি কন্ট্রাক্টে দুর্বলতা এবং অন্যান্য সম্ভাব্য সমস্যা খুঁজতে রান করে। এই ক্ষেত্রে, নিচের লাইনটি দুটি দুর্বলতা ট্রিগার করে:

  1. রিএন্ট্রান্সি ইভেন্ট (opens in a new tab)
  2. বিনাইন রিএন্ট্রান্সি (opens in a new tab)
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
2 }
3}

এই ক্ষেত্রে আমরা রিএন্ট্রান্সি নিয়ে চিন্তিত নই, আমরা জানি getCrossDomainMessenger() একটি বিশ্বস্ত এডড্রেস রিটার্ন করে, যদিও Slither-এর তা জানার কোনো উপায় নেই।

The L1 bridge contract

এই কন্ট্রাক্টের সোর্স কোড এখানে রয়েছে (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-এর Address ইউটিলিটিজ (opens in a new tab)। এটি কন্ট্রাক্ট এডড্রেস এবং এক্সটার্নালি ওনড একাউন্ট (EOA)-এর অন্তর্গত এডড্রেসগুলোর মধ্যে পার্থক্য করতে ব্যবহৃত হয়।

মনে রাখবেন যে এটি একটি নিখুঁত সমাধান নয়, কারণ সরাসরি কল এবং কন্ট্রাক্টের কনস্ট্রাক্টর থেকে করা কলগুলোর মধ্যে পার্থক্য করার কোনো উপায় নেই, তবে অন্তত এটি আমাদের কিছু সাধারণ ব্যবহারকারীর ত্রুটি শনাক্ত করতে এবং প্রতিরোধ করতে দেয়।

1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

ERC-20 স্ট্যান্ডার্ড (opens in a new tab) একটি কন্ট্রাক্টের ব্যর্থতা রিপোর্ট করার দুটি উপায় সাপোর্ট করে:

  1. রিভার্ট
  2. false রিটার্ন করা

উভয় ক্ষেত্র পরিচালনা করা আমাদের কোডকে আরও জটিল করে তুলবে, তাই এর পরিবর্তে আমরা OpenZeppelin-এর SafeERC20 (opens in a new tab) ব্যবহার করি, যা নিশ্চিত করে যে সমস্ত ব্যর্থতার ফলে একটি রিভার্ট হয় (opens in a new tab)

1/* *
2 * @title L1StandardBridge
3 * @dev L1 ETH এবং ERC20 ব্রিজ হলো এমন একটি কন্ট্রাক্ট যা ডিপোজিট করা L1 ফান্ড এবং L2-তে ব্যবহৃত স্ট্যান্ডার্ড টোকেনগুলো স্টোর করে। এটি একটি সংশ্লিষ্ট L2 ব্রিজ সিঙ্ক্রোনাইজ করে, এটিকে ডিপোজিট সম্পর্কে অবহিত করে এবং নতুন চূড়ান্ত হওয়া উত্তোলনের জন্য এটি লিসেন করে।
4 * */
5contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
6 using SafeERC20 for IERC20;

এই লাইনটি হলো যেভাবে আমরা প্রতিবার IERC20 ইন্টারফেস ব্যবহার করার সময় SafeERC20 র‍্যাপার ব্যবহার করার নির্দেশ দিই।

1
2 /* *******************************
3 * এক্সটার্নাল কন্ট্রাক্ট রেফারেন্সেস *
4 ******************************* */
5
6 address public l2TokenBridge;

L2StandardBridge-এর এডড্রেস।

1
2 // ডিপোজিট করা L1 টোকেনের ব্যালেন্সে L1 টোকেন থেকে L2 টোকেন ম্যাপ করে
3 mapping(address => mapping(address => uint256)) public deposits;

এই ধরনের একটি ডাবল ম্যাপিং (opens in a new tab) হলো যেভাবে আপনি একটি টু-ডাইমেনশনাল স্পার্স অ্যারে (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 ফাংশনগুলোর গ্যাস খরচ কম হতে পারে, কারণ সেগুলোকে কলডেটায় প্যারামিটার সরবরাহ করা যেতে পারে। public ঘোষিত ফাংশনগুলো কন্ট্রাক্টের ভেতর থেকে অ্যাক্সেসযোগ্য হতে হবে। কন্ট্রাক্টগুলো তাদের নিজস্ব কলডেটা পরিবর্তন করতে পারে না, তাই প্যারামিটারগুলো মেমোরিতে থাকতে হবে। যখন এই ধরনের একটি ফাংশন বাহ্যিকভাবে কল করা হয়, তখন কলডেটা মেমোরিতে কপি করা প্রয়োজন, যার জন্য গ্যাস খরচ হয়। এই ক্ষেত্রে ফাংশনটি শুধুমাত্র একবার কল করা হয়, তাই অদক্ষতা আমাদের কাছে কোনো ব্যাপার না।

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

initialize ফাংশনটি শুধুমাত্র একবার কল করা উচিত। যদি L1 ক্রস ডোমেইন মেসেঞ্জার বা L2 টোকেন ব্রিজের এডড্রেস পরিবর্তন হয়, তবে আমরা একটি নতুন প্রক্সি এবং একটি নতুন ব্রিজ তৈরি করি যা এটিকে কল করে। সম্পূর্ণ সিস্টেম আপগ্রেড হওয়া ছাড়া এটি ঘটার সম্ভাবনা কম, যা একটি খুব বিরল ঘটনা।

মনে রাখবেন যে এই ফাংশনটিতে এমন কোনো মেকানিজম নেই যা সীমাবদ্ধ করে যে কে এটিকে কল করতে পারে। এর মানে হলো তাত্ত্বিকভাবে একজন আক্রমণকারী অপেক্ষা করতে পারে যতক্ষণ না আমরা প্রক্সি এবং ব্রিজের প্রথম ভার্সন ডিপ্লয় করি এবং তারপর বৈধ ব্যবহারকারীর আগে initialize ফাংশনে পৌঁছানোর জন্য ফ্রন্ট-রান (opens in a new tab) করতে পারে। তবে এটি প্রতিরোধ করার দুটি পদ্ধতি রয়েছে:

  1. যদি কন্ট্রাক্টগুলো সরাসরি কোনো EOA দ্বারা ডিপ্লয় না হয়ে এমন একটি লেনদেন-এ হয় যেখানে অন্য একটি কন্ট্রাক্ট তাদের তৈরি করে (opens in a new tab) তবে সম্পূর্ণ প্রক্রিয়াটি অ্যাটমিক হতে পারে এবং অন্য কোনো লেনদেন এক্সিকিউট হওয়ার আগেই শেষ হতে পারে।
  2. যদি initialize-এর বৈধ কল ব্যর্থ হয় তবে নতুন তৈরি প্রক্সি এবং ব্রিজ উপেক্ষা করা এবং নতুন তৈরি করা সর্বদা সম্ভব।
1 messenger = _l1messenger;
2 l2TokenBridge = _l2TokenBridge;
3 }

এই দুটি প্যারামিটার ব্রিজের জানা প্রয়োজন।

1
2 /* *************
3 * ডিপোজিটিং *
4 ************* */
5
6 /* * @dev মডিফায়ার যার জন্য প্রেরককে EOA হতে হবে। এই চেকটি initcode-এর মাধ্যমে একটি ক্ষতিকারক কন্ট্রাক্ট দ্বারা বাইপাস করা যেতে পারে, তবে এটি ব্যবহারকারীর ত্রুটিগুলোর যত্ন নেয় যা আমরা এড়াতে চাই। */
7 modifier onlyEOA() {
8 // কন্ট্রাক্টগুলো থেকে ডিপোজিট বন্ধ করতে ব্যবহৃত হয় (দুর্ঘটনাবশত টোকেন হারানো এড়াতে)
9 require(!Address.isContract(msg.sender), "Account not EOA");
10 _;
11 }

এই কারণেই আমাদের OpenZeppelin-এর Address ইউটিলিটিজ প্রয়োজন ছিল।

1 /* *
2 * @dev L2-তে কলারের ব্যালেন্সে নির্দিষ্ট পরিমাণ ETH ডিপোজিট করতে
3 * কোনো ডেটা ছাড়াই এই ফাংশনটি কল করা যেতে পারে।
4 * যেহেতু রিসিভ ফাংশন ডেটা নেয় না, তাই একটি রক্ষণশীল
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-এর চারপাশের র‍্যাপার, যে ফাংশনটি প্রকৃত ETH ডিপোজিট পরিচালনা করে।

1 /* *
2 * @dev ETH স্টোর করে এবং L2 ETH গেটওয়েকে ডিপোজিট সম্পর্কে অবহিত করে ডিপোজিটের লজিক সম্পাদন করে।
3 * @param _from L1-এ যে অ্যাকাউন্ট থেকে ডিপোজিট নেওয়া হবে।
4 * @param _to L2-তে যে অ্যাকাউন্টে ডিপোজিট দেওয়া হবে।
5 * @param _l2Gas L2-তে ডিপোজিট সম্পন্ন করার জন্য প্রয়োজনীয় গ্যাস লিমিট।
6 * @param _data L2-তে ফরোয়ার্ড করার জন্য ঐচ্ছিক ডেটা। এই ডেটা শুধুমাত্র এক্সটার্নাল কন্ট্রাক্টগুলোর সুবিধার জন্য প্রদান করা হয়েছে। সর্বোচ্চ দৈর্ঘ্য প্রয়োগ করা ছাড়া, এই কন্ট্রাক্টগুলো এর বিষয়বস্তু সম্পর্কে কোনো গ্যারান্টি দেয় না। */
7 function _initiateETHDeposit(
8 address _from,
9 address _to,
10 uint32 _l2Gas,
11 bytes memory _data
12 ) internal {
13 // finalizeDeposit কলের জন্য কলডেটা তৈরি করুন
14 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 কন্ট্রাক্ট যা Optimism-এ ETH পরিচালনা করে, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (এই কন্ট্রাক্টটি শুধুমাত্র অভ্যন্তরীণ Optimism ব্যবহারের জন্য)
_from_fromL1-এর এডড্রেস যা ETH পাঠায়
_to_toL2-এর এডড্রেস যা ETH গ্রহণ করে
amountmsg.valueপাঠানো wei-এর পরিমাণ (যা ইতিমধ্যে ব্রিজে পাঠানো হয়েছে)
_data_dataডিপোজিটের সাথে যুক্ত করার জন্য অতিরিক্ত ডেটা
1 // 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 }

এই ট্রান্সফার সম্পর্কে শোনে এমন যেকোনো ডিসেন্ট্রালাইজড এপ্লিকেশন-কে জানাতে একটি ইভেন্ট এমিট করুন।

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 ডিপোজিটেড টোকেন কন্ট্রাক্টকে ডিপোজিট সম্পর্কে অবহিত করে এবং 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 function _initiateERC20Deposit(
12 address _l1Token,
13 address _l2Token,
14 address _from,
15 address _to,
16 uint256 _amount,
17 uint32 _l2Gas,
18 bytes calldata _data
19 ) internal {

এই ফাংশনটি উপরের _initiateETHDeposit-এর মতোই, তবে কিছু গুরুত্বপূর্ণ পার্থক্য রয়েছে। প্রথম পার্থক্য হলো এই ফাংশনটি টোকেন এডড্রেস এবং ট্রান্সফার করার পরিমাণ প্যারামিটার হিসেবে গ্রহণ করে। ETH-এর ক্ষেত্রে ব্রিজে কলে ইতিমধ্যে ব্রিজ একাউন্ট-এ অ্যাসেট ট্রান্সফার অন্তর্ভুক্ত থাকে (msg.value)।

1 // যখন L1-এ কোনো ডিপোজিট শুরু হয়, তখন L1 ব্রিজ ভবিষ্যতের জন্য ফান্ডগুলো নিজের কাছে ট্রান্সফার করে
2 // উত্তোলনের জন্য। 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)-এর জন্য কলডেটা তৈরি করুন
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 // 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-এ কোনো উত্তোলন চূড়ান্ত হয়, তখন 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 *
13 **************************** */
14
15 /* *
16 * @dev অ্যাকাউন্টে ETH ব্যালেন্স যোগ করে। এটি একটি পুরানো গেটওয়ে থেকে নতুন গেটওয়েতে ETH
17 * মাইগ্রেট করার অনুমতি দেওয়ার জন্য তৈরি করা হয়েছে।
18 * দ্রষ্টব্য: এটি শুধুমাত্র একটি আপগ্রেডের জন্য রাখা হয়েছে যাতে আমরা পুরানো কন্ট্রাক্ট থেকে
19 * মাইগ্রেট করা ETH গ্রহণ করতে পারি */
20 function donateETH() external payable {}
21}

ব্রিজের একটি পূর্ববর্তী ইমপ্লিমেন্টেশন ছিল। যখন আমরা সেই ইমপ্লিমেন্টেশন থেকে এটিতে চলে আসি, তখন আমাদের সমস্ত অ্যাসেট সরাতে হয়েছিল। 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: 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)। Optimism চাকা পুনরায় আবিষ্কার করায় বিশ্বাস করে না, বিশেষ করে যখন চাকাটি ভালোভাবে অডিট করা হয় এবং অ্যাসেট ধরে রাখার জন্য যথেষ্ট বিশ্বস্ত হওয়া প্রয়োজন।

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 প্রতীক। */
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)-এর এক্সক্লুসিভ অর (opens in a new tab) হিসেবে চিহ্নিত করা হয়।

L2 ব্রিজ ERC-165-কে একটি স্যানিটি চেক হিসেবে ব্যবহার করে যাতে নিশ্চিত করা যায় যে এটি যে 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 ব্রিজ কোড

এটি সেই কোড যা Optimism-এ ব্রিজ রান করে। এই কন্ট্রাক্টের সোর্স এখানে রয়েছে (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-তে আমরা উভয়ের জন্য একই ফাংশন ব্যবহার করতে পারি কারণ অভ্যন্তরীণভাবে Optimism-এ 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 এবং L2-এর মধ্যে ETH এবং ERC20 ট্রানজিশন সক্ষম করতে L1 স্ট্যান্ডার্ড ব্রিজের সাথে একসাথে কাজ করে।
12 * যখন এটি L1 স্ট্যান্ডার্ড ব্রিজে ডিপোজিট সম্পর্কে জানতে পারে তখন এই কন্ট্রাক্টটি নতুন টোকেনগুলোর জন্য মিন্টার হিসেবে কাজ করে।
13 * এই কন্ট্রাক্টটি উত্তোলনের উদ্দেশ্যে টোকেনগুলোর বার্নার হিসেবেও কাজ করে, L1 ফান্ড রিলিজ করার জন্য L1 ব্রিজকে অবহিত করে। */
14contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
15 /* *******************************
16 * এক্সটার্নাল কন্ট্রাক্ট রেফারেন্সেস *
17 ******************************* */
18
19 address public l1TokenBridge;

L1 ব্রিজের এডড্রেস-এর ট্র্যাক রাখুন। মনে রাখবেন যে L1 সমতুল্যের বিপরীতে, এখানে আমাদের এই ভেরিয়েবলটি প্রয়োজন। L1 ব্রিজের এডড্রেস আগে থেকে জানা যায় না।

1
2 /* **************
3 * কনস্ট্রাক্টর *
4 ************** */
5
6 /* *
7 * @param _l2CrossDomainMessenger এই কন্ট্রাক্ট দ্বারা ব্যবহৃত ক্রস-ডোমেইন মেসেঞ্জার।
8 * @param _l1TokenBridge মেইন চেইনে ডিপ্লয় করা L1 ব্রিজের ঠিকানা। */
9 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)
10 CrossDomainEnabled(_l2CrossDomainMessenger)
11 {
12 l1TokenBridge = _l1TokenBridge;
13 }
14
15 /* **************
16 * উইথড্রয়িং *
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 টোকেন গেটওয়েকে উত্তোলন সম্পর্কে অবহিত করে উত্তোলনের লজিক সম্পাদন করে।
4 * @param _l2Token যে L2 টোকেন থেকে উত্তোলন শুরু হয়েছে তার ঠিকানা।
5 * @param _from L2-তে যে অ্যাকাউন্ট থেকে উত্তোলন নেওয়া হবে।
6 * @param _to L1-এ যে অ্যাকাউন্টে উত্তোলন দেওয়া হবে।
7 * @param _amount উত্তোলন করার জন্য টোকেনের পরিমাণ।
8 * @param _l1Gas অব্যবহৃত, তবে সম্ভাব্য ফরোয়ার্ড কম্প্যাটিবিলিটি বিবেচনার জন্য অন্তর্ভুক্ত করা হয়েছে।
9 * @param _data L1-এ ফরোয়ার্ড করার জন্য ঐচ্ছিক ডেটা। এই ডেটা শুধুমাত্র এক্সটার্নাল কন্ট্রাক্টগুলোর সুবিধার জন্য প্রদান করা হয়েছে। সর্বোচ্চ দৈর্ঘ্য প্রয়োগ করা ছাড়া, এই কন্ট্রাক্টগুলো এর বিষয়বস্তু সম্পর্কে কোনো গ্যারান্টি দেয় না। */
10 function _initiateWithdrawal(
11 address _l2Token,
12 address _from,
13 address _to,
14 uint256 _amount,
15 uint32 _l1Gas,
16 bytes calldata _data
17 ) internal {
18 // যখন কোনো উত্তোলন শুরু হয়, তখন আমরা পরবর্তী L2 প্রতিরোধ করতে উত্তোলনকারীর ফান্ড বার্ন করি
19 // ব্যবহার
20 // slither-disable-next-line reentrancy-events
21 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);

লক্ষ্য করুন যে আমরা _from প্যারামিটারের ওপর নির্ভর করছি না বরং msg.sender-এর ওপর নির্ভর করছি যা নকল করা অনেক কঠিন (আমার জানামতে অসম্ভব)।

1
2 // l1TokenBridge.finalizeERC20Withdrawal(_to, _amount)-এর জন্য কলডেটা তৈরি করুন
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 // টার্গেট টোকেনটি কমপ্লায়েন্ট কিনা তা চেক করুন এবং
2 // যাচাই করুন যে L1-এ ডিপোজিট করা টোকেনটি এখানে L2 ডিপোজিট করা টোকেন রিপ্রেজেন্টেশনের সাথে মেলে
3 if (
4 // slither-disable-next-line reentrancy-events
5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
6 _l1Token == IL2StandardERC20(_l2Token).l1Token()

স্যানিটি চেক:

  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 টোকেনে ডিপোজিট করা হচ্ছে সেটি সঠিক ঠিকানা সম্পর্কে দ্বিমত পোষণ করে
3 // এর L1 টোকেনের, অথবা সঠিক ইন্টারফেস সমর্থন করে না।
4 // এটি শুধুমাত্র তখনই ঘটা উচিত যদি কোনো ক্ষতিকারক L2 টোকেন থাকে, অথবা যদি কোনো ব্যবহারকারী কোনোভাবে
5 // ডিপোজিট করার জন্য ভুল L2 টোকেন ঠিকানা নির্দিষ্ট করে।
6 // যেকোনো ক্ষেত্রেই, আমরা এখানে প্রক্রিয়াটি থামিয়ে দিই এবং একটি উত্তোলন তৈরি করি
7 // মেসেজ যাতে ব্যবহারকারীরা কিছু ক্ষেত্রে তাদের ফান্ড বের করে নিতে পারে।
8 // ক্ষতিকারক টোকেন কন্ট্রাক্টগুলো পুরোপুরি প্রতিরোধ করার কোনো উপায় নেই, তবে এটি সীমাবদ্ধ করে
9 // ব্যবহারকারীর ত্রুটি এবং কিছু ধরণের ক্ষতিকারক কন্ট্রাক্ট আচরণ প্রশমিত করে।

যদি কোনো ব্যবহারকারী ভুল L2 টোকেন এডড্রেস ব্যবহার করে কোনো শনাক্তযোগ্য ত্রুটি করে, তবে আমরা ডিপোজিট বাতিল করতে এবং L1-এ টোকেনগুলো ফেরত দিতে চাই। L2 থেকে আমরা এটি করার একমাত্র উপায় হলো একটি মেসেজ পাঠানো যা ফল্ট চ্যালেঞ্জ পিরিয়ড পর্যন্ত অপেক্ষা করতে হবে, তবে এটি ব্যবহারকারীর জন্য টোকেনগুলো স্থায়ীভাবে হারানোর চেয়ে অনেক ভালো।

1 bytes memory message = abi.encodeWithSelector(
2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
3 _l1Token,
4 _l2Token,
5 _to, // প্রেরকের কাছে ডিপোজিট বাউন্স ব্যাক করতে এখানে _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}

উপসংহার

স্ট্যান্ডার্ড ব্রিজ হলো অ্যাসেট ট্রান্সফারের জন্য সবচেয়ে নমনীয় মেকানিজম। তবে, এটি এত জেনেরিক হওয়ার কারণে এটি সর্বদা ব্যবহার করার সবচেয়ে সহজ মেকানিজম নয়। বিশেষ করে উত্তোলনের জন্য, বেশিরভাগ ব্যবহারকারী থার্ড পার্টি ব্রিজ (opens in a new tab) ব্যবহার করতে পছন্দ করেন যা চ্যালেঞ্জ পিরিয়ডের জন্য অপেক্ষা করে না এবং উত্তোলন চূড়ান্ত করার জন্য কোনো Merkle প্রুফ-এর প্রয়োজন হয় না।

এই ব্রিজগুলো সাধারণত L1-এ অ্যাসেট রাখার মাধ্যমে কাজ করে, যা তারা একটি ছোট ফি-এর বিনিময়ে অবিলম্বে প্রদান করে (প্রায়শই একটি স্ট্যান্ডার্ড ব্রিজ উত্তোলনের জন্য গ্যাস খরচের চেয়ে কম)। যখন ব্রিজ (বা এটি পরিচালনাকারী লোকেরা) L1 অ্যাসেট কম হওয়ার আশঙ্কা করে তখন এটি L2 থেকে পর্যাপ্ত অ্যাসেট ট্রান্সফার করে। যেহেতু এগুলো অনেক বড় উত্তোলন, তাই উত্তোলন খরচ একটি বড় পরিমাণের ওপর অ্যামর্টাইজ করা হয় এবং এটি অনেক ছোট শতাংশ হয়।

আশা করি এই আর্টিকেলটি আপনাকে লেয়ার ২ কীভাবে কাজ করে এবং কীভাবে পরিষ্কার ও সুরক্ষিত Solidity কোড লিখতে হয় সে সম্পর্কে আরও বুঝতে সাহায্য করেছে।

আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)

পেজ সর্বশেষ আপডেট: 3 মার্চ, 2026

এই টিউটোরিয়ালটি কি সহায়ক ছিল?