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

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

Solidity
সেতু
লেয়ার ২
মধ্যবর্তী
ওরি পোমেরান্টজ
30 মার্চ, 2022
32 মিনিট পড়ার সময়

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

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

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

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

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

  • জমা (l1 থেকে l2-এ)
  • উত্তোলন (l2 থেকে l1-এ)

জমা ফ্লো

লেয়ার ১

  1. যদি একটি ERC-20 জমা করা হয়, তবে জমাকারী সেতুটিকে জমা করা পরিমাণ খরচ করার জন্য একটি অ্যালাউন্স দেয়
  2. জমাকারী l1 সেতুকে কল করে (depositERC20, depositERC20To, depositETH, অথবা depositETHTo)
  3. l1 সেতু সেতু করা সম্পদের দখল নেয়
    • ETH: কলটির অংশ হিসেবে জমাকারী দ্বারা সম্পদটি হস্তান্তর করা হয়
    • ERC-20: জমাকারীর দেওয়া অ্যালাউন্স ব্যবহার করে সেতুটি সম্পদটিকে নিজের কাছে হস্তান্তর করে
  4. l1 সেতু l2 সেতুতে finalizeDeposit কল করার জন্য ক্রস-ডোমেইন বার্তা মেকানিজম ব্যবহার করে

লেয়ার ২

  1. l2 সেতু যাচাই করে যে finalizeDeposit-এ কলটি বৈধ:
    • ক্রস ডোমেইন বার্তা কন্ট্রাক্ট থেকে এসেছে
    • মূলত l1-এর সেতু থেকে এসেছে
  2. l2 সেতু পরীক্ষা করে যে l2-এ ERC-20 টোকেন কন্ট্রাক্টটি সঠিক কিনা:
    • l2 কন্ট্রাক্ট রিপোর্ট করে যে এর l1 প্রতিপক্ষটি l1-এ যেখান থেকে টোকেনগুলো এসেছে তার মতোই
    • l2 কন্ট্রাক্ট রিপোর্ট করে যে এটি সঠিক ইন্টারফেস সমর্থন করে (ERC-165 ব্যবহার করে (opens in a new tab))।
  3. যদি l2 কন্ট্রাক্টটি সঠিক হয়, তবে উপযুক্ত ঠিকানায় উপযুক্ত সংখ্যক টোকেন মিন্ট করার জন্য এটিকে কল করুন। যদি তা না হয়, তবে ব্যবহারকারীকে l1-এ টোকেনগুলো দাবি করার অনুমতি দেওয়ার জন্য একটি উত্তোলন প্রক্রিয়া শুরু করুন।

উত্তোলন ফ্লো

লেয়ার ২

  1. উত্তোলনকারী l2 সেতুকে কল করে (withdraw অথবা withdrawTo)
  2. l2 সেতু msg.sender-এর অন্তর্গত উপযুক্ত সংখ্যক টোকেন পোড়ানো সম্পন্ন করে
  3. l2 সেতু l1 সেতুতে finalizeETHWithdrawal অথবা finalizeERC20Withdrawal কল করার জন্য ক্রস-ডোমেইন বার্তা মেকানিজম ব্যবহার করে

লেয়ার ১

  1. l1 সেতু যাচাই করে যে finalizeETHWithdrawal অথবা finalizeERC20Withdrawal-এ কলটি বৈধ:
    • ক্রস ডোমেইন বার্তা মেকানিজম থেকে এসেছে
    • মূলত l2-এর সেতু থেকে এসেছে
  2. l1 সেতু উপযুক্ত সম্পদ (ETH বা ERC-20) উপযুক্ত ঠিকানায় হস্তান্তর করে

লেয়ার ১ কোড

এটি সেই কোড যা l1, ইথেরিয়াম মেইননেট-এ চলে।

IL1ERC20Bridge

এই ইন্টারফেসটি এখানে সংজ্ঞায়িত করা হয়েছে (opens in a new tab)। এতে ERC-20 টোকেন সেতু করার জন্য প্রয়োজনীয় ফাংশন এবং সংজ্ঞা অন্তর্ভুক্ত রয়েছে।

// SPDX-License-Identifier: MIT

অপটিমিজমের বেশিরভাগ কোড MIT লাইসেন্সের অধীনে প্রকাশিত হয় (opens in a new tab)

pragma solidity >0.5.0 <0.9.0;

লেখার সময় Solidity-এর সর্বশেষ সংস্করণ হলো 0.8.12। 0.9.0 সংস্করণ প্রকাশিত না হওয়া পর্যন্ত, আমরা জানি না যে এই কোডটি এর সাথে সামঞ্জস্যপূর্ণ কিনা।

অপটিমিজম সেতু পরিভাষায় জমা মানে l1 থেকে l2-এ হস্তান্তর, এবং উত্তোলন মানে l2 থেকে l1-এ হস্তান্তর।

        address indexed _l1Token,
        address indexed _l2Token,

বেশিরভাগ ক্ষেত্রে l1-এ একটি ERC-20-এর ঠিকানা l2-এ সমতুল্য ERC-20-এর ঠিকানার সমান নয়। আপনি এখানে টোকেন ঠিকানাগুলোর তালিকা দেখতে পারেন (opens in a new tab)chainId 1 সহ ঠিকানাটি l1 (মেইননেট)-এ রয়েছে এবং chainId 10 সহ ঠিকানাটি l2 (অপটিমিজম)-এ রয়েছে। অন্য দুটি chainId মান হলো Kovan টেস্ট নেটওয়ার্ক (42) এবং Optimistic Kovan টেস্ট নেটওয়ার্ক (69)-এর জন্য।

        address indexed _from,
        address _to,
        uint256 _amount,
        bytes _data
    );

হস্তান্তরে নোট যোগ করা সম্ভব, সেক্ষেত্রে সেগুলো সেই ইভেন্টগুলোতে যোগ করা হয় যা তাদের রিপোর্ট করে।

    event ERC20WithdrawalFinalized(
        address indexed _l1Token,
        address indexed _l2Token,
        address indexed _from,
        address _to,
        uint256 _amount,
        bytes _data
    );

একই সেতু কন্ট্রাক্ট উভয় দিকে হস্তান্তর পরিচালনা করে। l1 সেতুর ক্ষেত্রে, এর অর্থ হলো জমার সূচনা এবং উত্তোলনের চূড়ান্তকরণ।

এই ফাংশনটির আসলে প্রয়োজন নেই, কারণ l2-এ এটি একটি প্রিডিপ্লয় করা কন্ট্রাক্ট, তাই এটি সর্বদা 0x4200000000000000000000000000000000000010 ঠিকানায় থাকে। এটি এখানে l2 সেতুর সাথে সামঞ্জস্যের জন্য রয়েছে, কারণ l1 সেতুর ঠিকানা জানা সহজ নয়।

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

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

অপটিমিজমে উত্তোলন (এবং 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 সেতু করার প্রয়োজন হয় না।

এই ইভেন্টটি প্রায় ERC-20 সংস্করণের (ERC20DepositInitiated) মতোই, শুধুমাত্র l1 এবং l2 টোকেন ঠিকানাগুলো ছাড়া। অন্যান্য ইভেন্ট এবং ফাংশনগুলোর ক্ষেত্রেও একই কথা প্রযোজ্য।

CrossDomainEnabled

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

// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.9.0;

/* ইন্টারফেস ইমপোর্ট */
import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";

এই ইন্টারফেসটি (opens in a new tab) ক্রস ডোমেইন মেসেঞ্জার ব্যবহার করে অন্য লেয়ারে কীভাবে বার্তা পাঠাতে হয় তা কন্ট্রাক্টকে বলে। এই ক্রস ডোমেইন মেসেঞ্জারটি সম্পূর্ণ অন্য একটি সিস্টেম, এবং এর নিজস্ব একটি নিবন্ধ প্রাপ্য, যা আমি ভবিষ্যতে লেখার আশা করি।

কন্ট্রাক্টটির যে একটি প্যারামিটার জানা দরকার, তা হলো এই লেয়ারে ক্রস ডোমেইন মেসেঞ্জারের ঠিকানা। এই প্যারামিটারটি কনস্ট্রাক্টর-এ একবার সেট করা হয় এবং কখনও পরিবর্তন হয় না।

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

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

শুধুমাত্র উপযুক্ত ক্রস ডোমেইন মেসেঞ্জার (messenger, যেমন আপনি নিচে দেখতে পাচ্ছেন) থেকে আসা বার্তাগুলোকে বিশ্বাস করা যেতে পারে।


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

ক্রস ডোমেইন মেসেঞ্জার অন্য লেয়ারের সাথে বার্তা পাঠানো ঠিকানাটি যেভাবে প্রদান করে তা হলো .xDomainMessageSender() ফাংশন (opens in a new tab)। যতক্ষণ এটি বার্তা দ্বারা সূচিত ট্রানজ্যাকশনে কল করা হয়, এটি এই তথ্য প্রদান করতে পারে।

আমাদের নিশ্চিত করতে হবে যে আমরা যে বার্তাটি পেয়েছি তা অন্য সেতু থেকে এসেছে।

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

অবশেষে, সেই ফাংশন যা অন্য লেয়ারে একটি বার্তা পাঠায়।

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

স্লিদার (opens in a new tab) হলো একটি স্ট্যাটিক অ্যানালাইজার যা অপটিমিজম প্রতিটি কন্ট্রাক্টে দুর্বলতা এবং অন্যান্য সম্ভাব্য সমস্যা খোঁজার জন্য চালায়। এক্ষেত্রে, নিচের লাইনটি দুটি দুর্বলতা ট্রিগার করে:

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

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

l1 সেতু কন্ট্রাক্ট

এই কন্ট্রাক্টের সোর্স কোড এখানে রয়েছে (opens in a new tab)

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

ইন্টারফেসগুলো অন্যান্য কন্ট্রাক্টের অংশ হতে পারে, তাই তাদের বিভিন্ন ধরনের Solidity সংস্করণ সমর্থন করতে হবে। তবে সেতুটি নিজেই আমাদের কন্ট্রাক্ট, এবং এটি কোন Solidity সংস্করণ ব্যবহার করে সে সম্পর্কে আমরা কঠোর হতে পারি।

/* ইন্টারফেস ইমপোর্ট */
import { IL1StandardBridge } from "./IL1StandardBridge.sol";
import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";

IL1ERC20Bridge এবং IL1StandardBridge উপরে ব্যাখ্যা করা হয়েছে।

import { IL2ERC20Bridge } from "../../L2/messaging/IL2ERC20Bridge.sol";

এই ইন্টারফেসটি (opens in a new tab) আমাদের l2-এ স্ট্যান্ডার্ড সেতু নিয়ন্ত্রণ করার জন্য বার্তা তৈরি করতে দেয়।

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

এই ইন্টারফেসটি (opens in a new tab) আমাদের ERC-20 কন্ট্রাক্ট নিয়ন্ত্রণ করতে দেয়। আপনি এখানে এটি সম্পর্কে আরও পড়তে পারেন

/* লাইব্রেরি ইমপোর্ট */
import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";

উপরে ব্যাখ্যা করা হয়েছে, এই কন্ট্রাক্টটি ইন্টারলেয়ার মেসেজিংয়ের জন্য ব্যবহৃত হয়।

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

Lib_PredeployAddresses (opens in a new tab)-এ l2 কন্ট্রাক্টগুলোর ঠিকানা রয়েছে যার সর্বদা একই ঠিকানা থাকে। এর মধ্যে l2-এর স্ট্যান্ডার্ড সেতু অন্তর্ভুক্ত রয়েছে।

import { Address } from "@openzeppelin/contracts/utils/Address.sol";

ওপেনজেপেলিন-এর ঠিকানা ইউটিলিটি (opens in a new tab)। এটি কন্ট্রাক্ট ঠিকানা এবং এক্সটার্নালি ওনড অ্যাকাউন্ট (EOA)-এর অন্তর্গত ঠিকানাগুলোর মধ্যে পার্থক্য করতে ব্যবহৃত হয়।

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

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

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

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

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

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


    /********************************
     * এক্সটার্নাল কন্ট্রাক্ট রেফারেন্স *
     ********************************/

    address public l2TokenBridge;

L2StandardBridge-এর ঠিকানা।


    // জমা করা লেয়ার ১ (l1) টোকেনের ব্যালেন্সে লেয়ার ১ (l1) টোকেন থেকে লেয়ার ২ (l2) টোকেন ম্যাপ করে
    mapping(address => mapping(address => uint256)) public deposits;

এরকম একটি ডাবল ম্যাপিং (opens in a new tab) হলো একটি দ্বিমাত্রিক স্পার্স অ্যারে (opens in a new tab) সংজ্ঞায়িত করার উপায়। এই ডেটা স্ট্রাকচারের মানগুলো deposit[L1 token addr][L2 token addr] হিসেবে চিহ্নিত করা হয়। ডিফল্ট মান হলো শূন্য। শুধুমাত্র যে সেলগুলো একটি ভিন্ন মানে সেট করা আছে সেগুলো স্টোরেজে লেখা হয়।


    /***************
     * কনস্ট্রাক্টর *
     ***************/

    // এই কন্ট্রাক্ট একটি প্রক্সির পিছনে থাকে, তাই কনস্ট্রাক্টর প্যারামিটারগুলো অব্যবহৃত থাকবে।
    constructor() CrossDomainEnabled(address(0)) {}

স্টোরেজের সমস্ত ভেরিয়েবল কপি না করেই এই কন্ট্রাক্টটি আপগ্রেড করতে সক্ষম হতে চাই। এটি করার জন্য আমরা একটি Proxy (opens in a new tab) ব্যবহার করি, একটি কন্ট্রাক্ট যা delegatecall (opens in a new tab) ব্যবহার করে কলগুলোকে একটি পৃথক কন্ট্রাক্টে হস্তান্তর করে যার ঠিকানা প্রক্সি চুক্তি দ্বারা সংরক্ষিত থাকে (যখন আপনি আপগ্রেড করেন তখন আপনি প্রক্সিকে সেই ঠিকানা পরিবর্তন করতে বলেন)। যখন আপনি delegatecall ব্যবহার করেন তখন স্টোরেজটি কলিং কন্ট্রাক্টের স্টোরেজ হিসেবেই থাকে, তাই সমস্ত কন্ট্রাক্ট স্টেট ভেরিয়েবলের মানগুলো প্রভাবিত হয় না।

এই প্যাটার্নের একটি প্রভাব হলো যে কন্ট্রাক্টটি delegatecall-এর কলড তার স্টোরেজ ব্যবহৃত হয় না এবং তাই এতে পাস করা কনস্ট্রাক্টর মানগুলো কোনো ব্যাপার না। এ কারণেই আমরা CrossDomainEnabled কনস্ট্রাক্টর-এ একটি অর্থহীন মান প্রদান করতে পারি। এটিও একটি কারণ যে নিচের ইনিশিয়ালাইজেশনটি কনস্ট্রাক্টর থেকে আলাদা।

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

    function initialize(address _l1messenger, address _l2TokenBridge) public {
        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-এ বৈধ কলটি ব্যর্থ হয় তবে নতুন তৈরি করা প্রক্সি এবং সেতুকে উপেক্ষা করা এবং নতুনগুলো তৈরি করা সর্বদা সম্ভব।
        messenger = _l1messenger;
        l2TokenBridge = _l2TokenBridge;
    }

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

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

এই ফাংশনটি পরীক্ষার উদ্দেশ্যে বিদ্যমান। লক্ষ্য করুন যে এটি ইন্টারফেস সংজ্ঞায় উপস্থিত নেই - এটি সাধারণ ব্যবহারের জন্য নয়।

এই দুটি ফাংশন হলো _initiateETHDeposit-এর চারপাশের র‍্যাপার, যে ফাংশনটি প্রকৃত ETH জমা পরিচালনা করে।

ক্রস ডোমেইন বার্তাগুলো যেভাবে কাজ করে তা হলো গন্তব্য কন্ট্রাক্টটিকে বার্তাটিকে এর কল ডেটা হিসেবে দিয়ে কল করা হয়। Solidity কন্ট্রাক্টগুলো সর্বদা তাদের কল ডেটা ব্যাখ্যা করে ABI স্পেসিফিকেশন (opens in a new tab) অনুসারে। Solidity ফাংশন abi.encodeWithSelector (opens in a new tab) সেই কল ডেটা তৈরি করে।

            IL2ERC20Bridge.finalizeDeposit.selector,
            address(0),
            Lib_PredeployAddresses.OVM_ETH,
            _from,
            _to,
            msg.value,
            _data
        );

এখানে বার্তাটি হলো এই প্যারামিটারগুলোর সাথে 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জমার সাথে সংযুক্ত করার জন্য অতিরিক্ত ডেটা
        // লেয়ার ২ (l2) তে কল ডেটা পাঠান
        // slither-disable-next-line reentrancy-events
        sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

ক্রস ডোমেইন মেসেঞ্জারের মাধ্যমে বার্তাটি পাঠান।

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

এই হস্তান্তরের কথা শোনে এমন যেকোনো বিকেন্দ্রীকৃত অ্যাপ্লিকেশন (dapp)-কে জানানোর জন্য একটি ইভেন্ট এমিট করুন।

এই দুটি ফাংশন হলো _initiateERC20Deposit-এর চারপাশের র‍্যাপার, যে ফাংশনটি প্রকৃত ERC-20 জমা পরিচালনা করে।

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

        // যখন লেয়ার ১ (l1) এ একটি জমা শুরু হয়, তখন লেয়ার ১ (l1) সেতু ভবিষ্যতের জন্য ফান্ডগুলো নিজের কাছে হস্তান্তর করে
        // উত্তোলন। safeTransferFrom কন্ট্রাক্ট এর কোড আছে কিনা তাও চেক করে, তাই এটি ব্যর্থ হবে যদি
        // _from একটি EOA বা ঠিকানা(0) হয়।
        // slither-disable-next-line reentrancy-events, reentrancy-benign
        IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);

ERC-20 টোকেন হস্তান্তর ETH থেকে একটি ভিন্ন প্রক্রিয়া অনুসরণ করে:

  1. ব্যবহারকারী (_from) উপযুক্ত টোকেন হস্তান্তর করার জন্য সেতুটিকে একটি অ্যালাউন্স দেয়।
  2. ব্যবহারকারী টোকেন কন্ট্রাক্টের ঠিকানা, পরিমাণ ইত্যাদি দিয়ে সেতুটিকে কল করে।
  3. সেতুটি জমা প্রক্রিয়ার অংশ হিসেবে টোকেনগুলো (নিজের কাছে) হস্তান্তর করে।

প্রথম ধাপটি শেষ দুটির থেকে একটি পৃথক ট্রানজ্যাকশনে ঘটতে পারে। যাইহোক, ফ্রন্ট-রানিং কোনো সমস্যা নয় কারণ যে দুটি ফাংশন _initiateERC20Deposit (depositERC20 এবং depositERC20To) কল করে তারা শুধুমাত্র msg.sender-কে _from প্যারামিটার হিসেবে দিয়ে এই ফাংশনটিকে কল করে।

জমা করা টোকেনের পরিমাণ deposits ডেটা স্ট্রাকচারে যোগ করুন। l2-এ একাধিক ঠিকানা থাকতে পারে যা একই l1 ERC-20 টোকেনের সাথে মিলে যায়, তাই জমার ট্র্যাক রাখার জন্য l1 ERC-20 টোকেনের সেতুর ব্যালেন্স ব্যবহার করা যথেষ্ট নয়।

l2 সেতু l2 ক্রস ডোমেইন মেসেঞ্জারে একটি বার্তা পাঠায় যার ফলে l1 ক্রস ডোমেইন মেসেঞ্জার এই ফাংশনটিকে কল করে (অবশ্যই একবার যে ট্রানজ্যাকশনটি বার্তাটিকে চূড়ান্ত করে (opens in a new tab) তা l1-এ জমা দেওয়ার পরে)।

    ) external onlyFromCrossDomainAccount(l2TokenBridge) {

নিশ্চিত করুন যে এটি একটি বৈধ বার্তা, যা ক্রস ডোমেইন মেসেঞ্জার থেকে আসছে এবং l2 টোকেন সেতু থেকে উদ্ভূত হয়েছে। এই ফাংশনটি সেতু থেকে ETH উত্তোলন করতে ব্যবহৃত হয়, তাই আমাদের নিশ্চিত করতে হবে যে এটি শুধুমাত্র অনুমোদিত কলার দ্বারা কল করা হয়েছে।

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

ETH হস্তান্তর করার উপায় হলো msg.value-এ Wei-এর পরিমাণ সহ প্রাপককে কল করা।

        require(success, "TransferHelper::safeTransferETH: ETH transfer failed");

        // slither-disable-next-line reentrancy-events
        emit ETHWithdrawalFinalized(_from, _to, _amount, _data);

উত্তোলন সম্পর্কে একটি ইভেন্ট এমিট করুন।

এই ফাংশনটি উপরের finalizeETHWithdrawal-এর মতোই, তবে ERC-20 টোকেনের জন্য প্রয়োজনীয় পরিবর্তনগুলো রয়েছে।

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

deposits ডেটা স্ট্রাকচার আপডেট করুন।

সেতুর একটি পূর্ববর্তী বাস্তবায়ন ছিল। যখন আমরা সেই বাস্তবায়ন থেকে এটিতে চলে আসি, তখন আমাদের সমস্ত সম্পদ সরাতে হয়েছিল। ERC-20 টোকেনগুলো সহজেই সরানো যেতে পারে। যাইহোক, একটি কন্ট্রাক্টে ETH হস্তান্তর করার জন্য আপনার সেই কন্ট্রাক্টের অনুমোদন প্রয়োজন, যা donateETH আমাদের প্রদান করে।

l2-এ ERC-20 টোকেন

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

IL2StandardERC20

l2-এ প্রতিটি ERC-20 টোকেন যা স্ট্যান্ডার্ড সেতু ব্যবহার করে তাকে এই ইন্টারফেসটি (opens in a new tab) প্রদান করতে হবে, যার মধ্যে স্ট্যান্ডার্ড সেতুর প্রয়োজনীয় ফাংশন এবং ইভেন্টগুলো রয়েছে।

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

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

স্ট্যান্ডার্ড ERC-20 ইন্টারফেস (opens in a new tab)-এ mint এবং burn ফাংশনগুলো অন্তর্ভুক্ত নেই। এই পদ্ধতিগুলো ERC-20 স্ট্যান্ডার্ড (opens in a new tab) দ্বারা প্রয়োজনীয় নয়, যা টোকেন তৈরি এবং ধ্বংস করার মেকানিজমগুলোকে অনির্দিষ্ট রাখে।

import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

একটি কন্ট্রাক্ট কী কী ফাংশন প্রদান করে তা নির্দিষ্ট করতে ERC-165 ইন্টারফেস (opens in a new tab) ব্যবহৃত হয়। আপনি এখানে স্ট্যান্ডার্ডটি পড়তে পারেন (opens in a new tab)

interface IL2StandardERC20 is IERC20, IERC165 {
    function l1Token() external returns (address);

এই ফাংশনটি l1 টোকেনের ঠিকানা প্রদান করে যা এই কন্ট্রাক্টে সেতু করা হয়েছে। মনে রাখবেন যে বিপরীত দিকে আমাদের অনুরূপ কোনো ফাংশন নেই। আমাদের যেকোনো l1 টোকেন সেতু করতে সক্ষম হতে হবে, এটি বাস্তবায়নের সময় l2 সমর্থন করার পরিকল্পনা করা হয়েছিল কি না তা নির্বিশেষে।


    function mint(address _to, uint256 _amount) external;

    function burn(address _from, uint256 _amount) external;

    event Mint(address indexed _account, uint256 _amount);
    event Burn(address indexed _account, uint256 _amount);
}

টোকেন মিন্ট (তৈরি) এবং পোড়ানো (ধ্বংস) করার জন্য ফাংশন এবং ইভেন্ট। টোকেনের সংখ্যা সঠিক (l1-এ লক করা টোকেনের সংখ্যার সমান) তা নিশ্চিত করার জন্য সেতুটিই একমাত্র সত্তা হওয়া উচিত যা এই ফাংশনগুলো চালাতে পারে।

L2StandardERC20

এটি হলো IL2StandardERC20 ইন্টারফেসের আমাদের বাস্তবায়ন (opens in a new tab)। আপনার যদি কোনো ধরনের কাস্টম লজিকের প্রয়োজন না হয়, তবে আপনার এটি ব্যবহার করা উচিত।

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

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

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

import "./IL2StandardERC20.sol";

contract L2StandardERC20 is IL2StandardERC20, ERC20 {
    address public l1Token;
    address public l2Bridge;

এই দুটি অতিরিক্ত কনফিগারেশন প্যারামিটার আমাদের প্রয়োজন যা ERC-20-এর সাধারণত প্রয়োজন হয় না।

প্রথমে আমরা যে কন্ট্রাক্ট থেকে ইনহেরিট করি তার জন্য কনস্ট্রাক্টর কল করুন (ERC20(_name, _symbol)) এবং তারপর আমাদের নিজস্ব ভেরিয়েবলগুলো সেট করুন।

এভাবেই 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-এর মিথ্যা উত্তর প্রদান করা থেকে বিরত রাখার কিছু নেই, তাই এটি একটি স্যানিটি চেক মেকানিজম, কোনো নিরাপত্তা মেকানিজম নয়

শুধুমাত্র l2 সেতুকে সম্পদ মিন্ট এবং পোড়ানো করার অনুমতি দেওয়া হয়েছে।

_mint এবং _burn আসলে ওপেনজেপেলিন ERC-20 কন্ট্রাক্ট-এ সংজ্ঞায়িত করা হয়েছে। সেই কন্ট্রাক্টটি কেবল সেগুলোকে বাহ্যিকভাবে প্রকাশ করে না, কারণ টোকেন মিন্ট এবং পোড়ানো করার শর্তগুলো ERC-20 ব্যবহার করার উপায়গুলোর মতোই বৈচিত্র্যময়।

l2 সেতু কোড

এটি সেই কোড যা অপটিমিজমে সেতু চালায়। এই কন্ট্রাক্টের সোর্স এখানে রয়েছে (opens in a new tab)

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

/* ইন্টারফেস ইমপোর্ট */
import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";
import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";
import { 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 টোকেন হিসেবে পরিচালিত হয়।

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

এই দুটি ফাংশন উত্তোলন শুরু করে। মনে রাখবেন যে l1 টোকেন ঠিকানা নির্দিষ্ট করার কোনো প্রয়োজন নেই। l2 টোকেনগুলো আমাদের l1 সমতুল্যের ঠিকানা বলবে বলে আশা করা হচ্ছে।

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


        // l1TokenBridge.finalizeERC20Withdrawal(_to, _amount) এর জন্য কল ডেটা তৈরি করুন
        // slither-disable-next-line reentrancy-events
        address l1Token = IL2StandardERC20(_l2Token).l1Token();
        bytes memory message;

        if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {

l1-এ ETH এবং ERC-20-এর মধ্যে পার্থক্য করা প্রয়োজন।

এই ফাংশনটি L1StandardBridge দ্বারা কল করা হয়।

    ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {

নিশ্চিত করুন যে বার্তার উৎসটি বৈধ। এটি গুরুত্বপূর্ণ কারণ এই ফাংশনটি _mint কল করে এবং এমন টোকেন দিতে ব্যবহার করা যেতে পারে যা l1-এ সেতুর মালিকানাধীন টোকেন দ্বারা আচ্ছাদিত নয়।

        // টার্গেট টোকেনটি কমপ্লায়েন্ট কিনা তা চেক করুন এবং
        // যাচাই করুন যে লেয়ার ১ (l1) এ জমা করা টোকেনটি এখানে লেয়ার ২ (l2) জমা করা টোকেন উপস্থাপনার সাথে মেলে
        if (
            // slither-disable-next-line reentrancy-events
            ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&
            _l1Token == IL2StandardERC20(_l2Token).l1Token()

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

  1. সঠিক ইন্টারফেস সমর্থিত
  2. l2 ERC-20 কন্ট্রাক্টের l1 ঠিকানা টোকেনগুলোর l1 উৎসের সাথে মেলে
        ) {
            // যখন একটি জমা চূড়ান্ত হয়, তখন আমরা লেয়ার ২ (l2) তে অ্যাকাউন্টে সমপরিমাণ
            // টোকেন ক্রেডিট করি।
            // slither-disable-next-line reentrancy-events
            IL2StandardERC20(_l2Token).mint(_to, _amount);
            // slither-disable-next-line reentrancy-events
            emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);

যদি স্যানিটি চেক পাস হয়, তবে জমা চূড়ান্ত করুন:

  1. টোকেনগুলো মিন্ট করুন
  2. উপযুক্ত ইভেন্ট এমিট করুন

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

উপসংহার

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

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

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

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