प्रमुख मजकुराकडे जा

Optimism स्टँडर्ड ब्रिज कॉन्ट्रॅक्ट वॉकथ्रू

सॉलिडिटी
ब्रिज
स्तर 2
मध्यम
Ori Pomerantz
३० मार्च, २०२२
31 मिनिट वाचन

Optimismopens in a new tab हा एक Optimistic Rollup आहे. ऑप्टिमिस्टिक रोलअप्स Ethereum मेननेटपेक्षा (ज्याला लेयर 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, Ethereum मेननेटवर चालतो.

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
3 */
4interface IL1ERC20Bridge {
5 /**********
6 * Events *
7 **********/
8
9 event ERC20DepositInitiated(
सर्व दाखवा

Optimism ब्रिजच्या परिभाषेत deposit म्हणजे L1 पासून L2 मध्ये हस्तांतरण, आणि withdrawal म्हणजे 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) आणि ऑप्टिमिस्टिक 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 * Public Functions *
4 ********************/
5
6 /**
7 * @dev get the address of the corresponding L2 bridge contract.
8 * @return Address of the corresponding L2 bridge contract.
9 */
10 function l2TokenBridge() external returns (address);
सर्व दाखवा

या फंक्शनची खरोखर गरज नाही, कारण L2 वर तो एक प्रीडिप्लॉयड कॉन्ट्रॅक्ट आहे, त्यामुळे तो नेहमी 0x4200000000000000000000000000000000000010 या ऍड्रेसवर असतो. हे L2 ब्रिजसोबत सममितीसाठी येथे आहे, कारण L1 ब्रिजचा ऍड्रेस जाणून घेणे सोपे नाही.

1 /**
2 * @dev deposit an amount of the ERC20 to the caller's balance on L2.
3 * @param _l1Token Address of the L1 ERC20 we are depositing
4 * @param _l2Token Address of the L1 respective L2 ERC20
5 * @param _amount Amount of the ERC20 to deposit
6 * @param _l2Gas Gas limit required to complete the deposit on L2.
7 * @param _data Optional data to forward to L2. This data is provided
8 * solely as a convenience for external contracts. Aside from enforcing a maximum
9 * length, these contracts provide no guarantees about its content.
10 */
11 function depositERC20(
12 address _l1Token,
13 address _l2Token,
14 uint256 _amount,
15 uint32 _l2Gas,
16 bytes calldata _data
17 ) external;
सर्व दाखवा

_l2Gas पॅरामीटर हे L2 गॅसची रक्कम आहे जी ट्रान्झॅक्शन खर्च करू शकते. एका विशिष्ट (उच्च) मर्यादेपर्यंत, हे विनामूल्य आहेopens in a new tab, त्यामुळे जोपर्यंत ERC-20 कॉन्ट्रॅक्ट मिंटिंग करताना काहीतरी विचित्र करत नाही, तोपर्यंत ही समस्या नसावी. हे फंक्शन सामान्य परिस्थितीची काळजी घेते, जिथे वापरकर्ता वेगळ्या ब्लॉकचेनवर समान ऍड्रेसवर मालमत्ता ब्रिज करतो.

1 /**
2 * @dev deposit an amount of ERC20 to a recipient's balance on L2.
3 * @param _l1Token Address of the L1 ERC20 we are depositing
4 * @param _l2Token Address of the L1 respective L2 ERC20
5 * @param _to L2 address to credit the withdrawal to.
6 * @param _amount Amount of the ERC20 to deposit.
7 * @param _l2Gas Gas limit required to complete the deposit on L2.
8 * @param _data Optional data to forward to L2. This data is provided
9 * solely as a convenience for external contracts. Aside from enforcing a maximum
10 * length, these contracts provide no guarantees about its content.
11 */
12 function depositERC20To(
13 address _l1Token,
14 address _l2Token,
15 address _to,
16 uint256 _amount,
17 uint32 _l2Gas,
18 bytes calldata _data
19 ) external;
सर्व दाखवा

हे फंक्शन depositERC20 शी जवळजवळ समान आहे, परंतु ते तुम्हाला ERC-20 वेगळ्या ऍड्रेसवर पाठवू देते.

1 /*************************
2 * Cross-chain Functions *
3 *************************/
4
5 /**
6 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
7 * L1 ERC20 token.
8 * This call will fail if the initialized withdrawal from L2 has not been finalized.
9 *
10 * @param _l1Token Address of L1 token to finalizeWithdrawal for.
11 * @param _l2Token Address of L2 token where withdrawal was initiated.
12 * @param _from L2 address initiating the transfer.
13 * @param _to L1 address to credit the withdrawal to.
14 * @param _amount Amount of the ERC20 to deposit.
15 * @param _data Data provided by the sender on L2. This data is provided
16 * solely as a convenience for external contracts. Aside from enforcing a maximum
17 * length, these contracts provide no guarantees about its content.
18 */
19 function finalizeERC20Withdrawal(
20 address _l1Token,
21 address _l2Token,
22 address _from,
23 address _to,
24 uint256 _amount,
25 bytes calldata _data
26 ) external;
27}
सर्व दाखवा

Optimism मध्ये विड्रॉवल (आणि L2 पासून L1 पर्यंतचे इतर मेसेजेस) ही दोन टप्प्यांची प्रक्रिया आहे:

  1. L2 वर एक आरंभिक ट्रान्झॅक्शन.
  2. L1 वर एक अंतिम किंवा क्लेमिंग ट्रान्झॅक्शन. L2 ट्रान्झॅक्शनसाठी फॉल्ट चॅलेंज कालावधीopens in a new tab संपल्यानंतर हा ट्रान्झॅक्शन होणे आवश्यक आहे.

IL1StandardBridge

हा इंटरफेस येथे परिभाषित केला आहेopens in a new tab. या फाइलमध्ये ETH साठी इव्हेंट आणि फंक्शन परिभाषा आहेत. या परिभाषा वरील IL1ERC20Bridge मध्ये ERC-20 साठी परिभाषित केलेल्या परिभाषांशी खूप सारख्या आहेत.

ब्रिज इंटरफेस दोन फाइल्समध्ये विभागलेला आहे कारण काही 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
8 */
9interface IL1StandardBridge is IL1ERC20Bridge {
10 /**********
11 * Events *
12 **********/
13 event ETHDepositInitiated(
14 address indexed _from,
15 address indexed _to,
16 uint256 _amount,
17 bytes _data
18 );
सर्व दाखवा

हा इव्हेंट ERC-20 आवृत्ती (ERC20DepositInitiated) शी जवळजवळ समान आहे, फक्त L1 आणि L2 टोकन ऍड्रेस वगळता. इतर इव्हेंट्स आणि फंक्शन्ससाठीही हेच सत्य आहे.

1 event ETHWithdrawalFinalized(
2 .
3 .
4 .
5 );
6
7 /********************
8 * Public Functions *
9 ********************/
10
11 /**
12 * @dev Deposit an amount of the ETH to the caller's balance on L2.
13 .
14 .
15 .
16 */
17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;
18
19 /**
20 * @dev Deposit an amount of ETH to a recipient's balance on L2.
21 .
22 .
23 .
24 */
25 function depositETHTo(
26 address _to,
27 uint32 _l2Gas,
28 bytes calldata _data
29 ) external payable;
30
31 /*************************
32 * Cross-chain Functions *
33 *************************/
34
35 /**
36 * @dev Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the
37 * L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called
38 * before the withdrawal is finalized.
39 .
40 .
41 .
42 */
43 function finalizeETHWithdrawal(
44 address _from,
45 address _to,
46 uint256 _amount,
47 bytes calldata _data
48 ) external;
49}
सर्व दाखवा

CrossDomainEnabled

हा कॉन्ट्रॅक्टopens in a new tab दोन्ही ब्रिजेस (L1 आणि L2) द्वारे दुसऱ्या लेयरवर मेसेज पाठवण्यासाठी वारसा म्हणून मिळतो.

1// SPDX-License-Identifier: MIT
2pragma solidity >0.5.0 <0.9.0;
3
4/* Interface Imports */
5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";

हा इंटरफेसopens in a new tab क्रॉस डोमेन मेसेंजरचा वापर करून दुसऱ्या लेयरवर मेसेज कसे पाठवायचे हे कॉन्ट्रॅक्टला सांगतो. हा क्रॉस डोमेन मेसेंजर एक संपूर्ण वेगळी प्रणाली आहे, आणि त्याला स्वतःचा लेख आवश्यक आहे, जो मी भविष्यात लिहिण्याची आशा करतो.

1/**
2 * @title CrossDomainEnabled
3 * @dev Helper contract for contracts performing cross-domain communications
4 *
5 * Compiler used: defined by inheriting contract
6 */
7contract CrossDomainEnabled {
8 /*************
9 * Variables *
10 *************/
11
12 // Messenger contract used to send and receive messages from the other domain.
13 address public messenger;
14
15 /***************
16 * Constructor *
17 ***************/
18
19 /**
20 * @param _messenger Address of the CrossDomainMessenger on the current layer.
21 */
22 constructor(address _messenger) {
23 messenger = _messenger;
24 }
सर्व दाखवा

कॉन्ट्रॅक्टला माहित असणे आवश्यक असलेले एक पॅरामीटर, या लेयरवरील क्रॉस डोमेन मेसेंजरचा ऍड्रेस. हे पॅरामीटर कन्स्ट्रक्टरमध्ये एकदाच सेट केले जाते आणि कधीही बदलत नाही.

1
2 /**********************
3 * Function Modifiers *
4 **********************/
5
6 /**
7 * Enforces that the modified function is only callable by a specific cross-domain account.
8 * @param _sourceDomainAccount The only account on the originating domain which is
9 * authenticated to call this function.
10 */
11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {
सर्व दाखवा

क्रॉस डोमेन मेसेजिंग ज्या ब्लॉकचेनवर चालू आहे (Ethereum मेननेट किंवा 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 * Internal Functions *
7 **********************/
8
9 /**
10 * Gets the messenger, usually from storage. This function is exposed in case a child contract
11 * needs to override.
12 * @return The address of the cross-domain messenger contract which should be used.
13 */
14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
15 return ICrossDomainMessenger(messenger);
16 }
सर्व दाखवा

हे फंक्शन क्रॉस डोमेन मेसेंजर परत करते. आपण messenger व्हेरिएबलऐवजी फंक्शन वापरतो कारण यातून वारसा घेणाऱ्या कॉन्ट्रॅक्ट्सना कोणता क्रॉस डोमेन मेसेंजर वापरायचा हे निर्दिष्ट करण्यासाठी एक अल्गोरिदम वापरण्याची परवानगी मिळते.

1
2 /**
3 * Sends a message to an account on another domain
4 * @param _crossDomainTarget The intended recipient on the destination domain
5 * @param _message The data to send to the target (usually calldata to a function with
6 * `onlyFromCrossDomainAccount()`)
7 * @param _gasLimit The gasLimit for the receipt of the message on the target domain.
8 */
9 function sendCrossDomainMessage(
10 address _crossDomainTarget,
11 uint32 _gasLimit,
12 bytes memory _message
सर्व दाखवा

शेवटी, दुसऱ्या लेयरवर मेसेज पाठवणारे फंक्शन.

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

स्लिथर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() एक विश्वासार्ह ऍड्रेस परत करतो, जरी स्लिथरला हे माहित नसले तरी.

L1 ब्रिज कॉन्ट्रॅक्ट

या कॉन्ट्रॅक्टचा सोर्स कोड येथे आहेopens in a new tab.

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

हे इंटरफेस इतर कॉन्ट्रॅक्ट्सचा भाग असू शकतात, त्यामुळे त्यांना Solidity च्या विस्तृत आवृत्त्यांना समर्थन द्यावे लागते. परंतु ब्रिज स्वतः आमचा कॉन्ट्रॅक्ट आहे, आणि आम्ही ते कोणती Solidity आवृत्ती वापरते याबद्दल कठोर असू शकतो.

1/* Interface Imports */
2import { IL1StandardBridge } from "./IL1StandardBridge.sol";
3import { IL1ERC20Bridge } from "./IL1ERC20Bridge.sol";

IL1ERC20Bridge आणि IL1StandardBridge वर स्पष्ट केले आहेत.

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

हा इंटरफेसopens in a new tab आम्हाला L2 वरील स्टँडर्ड ब्रिज नियंत्रित करण्यासाठी मेसेज तयार करण्याची परवानगी देतो.

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

हा इंटरफेसopens in a new tab आम्हाला ERC-20 कॉन्ट्रॅक्ट नियंत्रित करण्याची परवानगी देतो. तुम्ही याबद्दल येथे अधिक वाचू शकता.

1/* Library Imports */
2import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";

वर स्पष्ट केल्याप्रमाणे, हा कॉन्ट्रॅक्ट इंटरलेअर मेसेजिंगसाठी वापरला जातो.

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

Lib_PredeployAddressesopens in a new tab मध्ये L2 कॉन्ट्रॅक्ट्सचे ऍड्रेस आहेत ज्यांचे ऍड्रेस नेहमी समान असतात. यात L2 वरील स्टँडर्ड ब्रिजचा समावेश आहे.

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

OpenZeppelin's 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's SafeERC20opens in a new tab वापरतो, जे सर्व अपयशांचे परिणाम रिव्हर्टमध्ये होतील याची खात्री करतेopens in a new tab.

1/**
2 * @title L1StandardBridge
3 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard
4 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits
5 * and listening to it for newly finalized withdrawals.
6 *
7 */
8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
9 using SafeERC20 for IERC20;
सर्व दाखवा

ही ओळ आम्ही प्रत्येक वेळी IERC20 इंटरफेस वापरताना SafeERC20 रॅपर वापरण्याचे कसे निर्दिष्ट करतो हे दर्शवते.

1
2 /********************************
3 * External Contract References *
4 ********************************/
5
6 address public l2TokenBridge;

L2StandardBridge चा ऍड्रेस.

1
2 // Maps L1 token to L2 token to balance of the L1 token deposited
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 * Constructor *
4 ***************/
5
6 // This contract lives behind a proxy, so the constructor parameters will go unused.
7 constructor() CrossDomainEnabled(address(0)) {}

स्टोरेजमधील सर्व व्हेरिएबल्स कॉपी न करता हा कॉन्ट्रॅक्ट अपग्रेड करण्याची इच्छा आहे. हे करण्यासाठी आम्ही एक Proxyopens in a new tab वापरतो, जो एका वेगळ्या कॉन्ट्रॅक्टवर कॉल हस्तांतरित करण्यासाठी delegatecallopens in a new tab वापरतो, ज्याचा ऍड्रेस प्रॉक्सी कॉन्ट्रॅक्टद्वारे संग्रहित केला जातो (तुम्ही अपग्रेड करता तेव्हा तुम्ही प्रॉक्सीला तो ऍड्रेस बदलण्यास सांगता). तुम्ही delegatecall वापरता तेव्हा स्टोरेज कॉलिंग कॉन्ट्रॅक्टचाच राहतो, त्यामुळे सर्व कॉन्ट्रॅक्ट स्थिती व्हेरिएबल्सच्या मूल्यांवर कोणताही परिणाम होत नाही.

या पॅटर्नचा एक परिणाम असा आहे की delegatecall चा कॉल केलेला कॉन्ट्रॅक्टचा स्टोरेज वापरला जात नाही आणि त्यामुळे त्याला पास केलेले कन्स्ट्रक्टर मूल्ये महत्त्वाची नसतात. हेच कारण आहे की आम्ही CrossDomainEnabled कन्स्ट्रक्टरला एक निरर्थक मूल्य देऊ शकतो. हेच कारण आहे की खालील इनिशियलायझेशन कन्स्ट्रक्टरपासून वेगळे आहे.

1 /******************
2 * Initialization *
3 ******************/
4
5 /**
6 * @param _l1messenger L1 Messenger address being used for cross-chain communications.
7 * @param _l2TokenBridge L2 standard bridge address.
8 */
9 // slither-disable-next-line external-function
सर्व दाखवा

ही स्लिथर टेस्ट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 * Depositing *
4 **************/
5
6 /** @dev Modifier requiring sender to be EOA. This check could be bypassed by a malicious
7 * contract via initcode, but it takes care of the user error we want to avoid.
8 */
9 modifier onlyEOA() {
10 // Used to stop deposits from contracts (avoid accidentally lost tokens)
11 require(!Address.isContract(msg.sender), "Account not EOA");
12 _;
13 }
सर्व दाखवा

हेच कारण आहे की आम्हाला OpenZeppelin च्या Address युटिलिटीजची आवश्यकता होती.

1 /**
2 * @dev This function can be called with no data
3 * to deposit an amount of ETH to the caller's balance on L2.
4 * Since the receive function doesn't take data, a conservative
5 * default amount is forwarded to L2.
6 */
7 receive() external payable onlyEOA {
8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));
9 }
सर्व दाखवा

हे फंक्शन चाचणी उद्देशांसाठी अस्तित्वात आहे. लक्षात घ्या की ते इंटरफेस परिभाषांमध्ये दिसत नाही - ते सामान्य वापरासाठी नाही.

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

ही दोन फंक्शन्स _initiateETHDeposit या फंक्शनच्या भोवती रॅपर्स आहेत, जे प्रत्यक्ष ETH डिपॉझिट हाताळते.

1 /**
2 * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of
3 * the deposit.
4 * @param _from Account to pull the deposit from on L1.
5 * @param _to Account to give the deposit to on L2.
6 * @param _l2Gas Gas limit required to complete the deposit on L2.
7 * @param _data Optional data to forward to L2. This data is provided
8 * solely as a convenience for external contracts. Aside from enforcing a maximum
9 * length, these contracts provide no guarantees about its content.
10 */
11 function _initiateETHDeposit(
12 address _from,
13 address _to,
14 uint32 _l2Gas,
15 bytes memory _data
16 ) internal {
17 // Construct calldata for finalizeDeposit call
18 bytes memory message = abi.encodeWithSelector(
सर्व दाखवा

क्रॉस डोमेन मेसेजेस ज्या प्रकारे काम करतात ते म्हणजे डेस्टिनेशन कॉन्ट्रॅक्टला त्याच्या कॉलडेटा म्हणून मेसेजसह कॉल केले जाते. Solidity कॉन्ट्रॅक्ट्स नेहमी त्यांच्या कॉलडेटाचा अर्थ ABI स्पेसिफिकेशन्सopens in a new tab नुसार लावतात. Solidity फंक्शन abi.encodeWithSelectoropens in a new tab तो कॉलडेटा तयार करते.

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

येथे मेसेज म्हणजे या पॅरामीटर्ससह the finalizeDeposit functionopens in a new tab कॉल करणे:

पॅरामीटरमूल्यअर्थ
_l1Tokenaddress(0)ETH (जे ERC-20 टोकन नाही) साठी L1 वर उभे राहण्यासाठी विशेष मूल्य
_l2TokenLib_PredeployAddresses.OVM_ETHOptimism वर ETH व्यवस्थापित करणारा L2 कॉन्ट्रॅक्ट, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (हा कॉन्ट्रॅक्ट फक्त अंतर्गत Optimism वापरासाठी आहे)
_from_fromL1 वर ETH पाठवणारा ऍड्रेस
_to_toL2 वर ETH प्राप्त करणारा ऍड्रेस
रक्कमmsg.valueपाठवलेली wei ची रक्कम (जी आधीच ब्रिजला पाठवली गेली आहे)
_data_dataडिपॉझिटला जोडण्यासाठी अतिरिक्त डेटा
1 // Send calldata into 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 */
4 function depositERC20(
5 .
6 .
7 .
8 ) external virtual onlyEOA {
9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);
10 }
11
12 /**
13 * @inheritdoc IL1ERC20Bridge
14 */
15 function depositERC20To(
16 .
17 .
18 .
19 ) external virtual {
20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);
21 }
सर्व दाखवा

ही दोन फंक्शन्स _initiateERC20Deposit या फंक्शनच्या भोवती रॅपर्स आहेत, जे प्रत्यक्ष ERC-20 डिपॉझिट हाताळते.

1 /**
2 * @dev Performs the logic for deposits by informing the L2 Deposited Token
3 * contract of the deposit and calling a handler to lock the L1 funds. (e.g., transferFrom)
4 *
5 * @param _l1Token Address of the L1 ERC20 we are depositing
6 * @param _l2Token Address of the L1 respective L2 ERC20
7 * @param _from Account to pull the deposit from on L1
8 * @param _to Account to give the deposit to on L2
9 * @param _amount Amount of the ERC20 to deposit.
10 * @param _l2Gas Gas limit required to complete the deposit on L2.
11 * @param _data Optional data to forward to L2. This data is provided
12 * solely as a convenience for external contracts. Aside from enforcing a maximum
13 * length, these contracts provide no guarantees about its content.
14 */
15 function _initiateERC20Deposit(
16 address _l1Token,
17 address _l2Token,
18 address _from,
19 address _to,
20 uint256 _amount,
21 uint32 _l2Gas,
22 bytes calldata _data
23 ) internal {
सर्व दाखवा

हे फंक्शन वरील _initiateETHDeposit शी सारखे आहे, काही महत्त्वाच्या फरकांसह. पहिला फरक म्हणजे हे फंक्शन टोकन ऍड्रेस आणि हस्तांतरित करण्याची रक्कम पॅरामीटर्स म्हणून प्राप्त करते. ETH च्या बाबतीत ब्रिजवर केलेल्या कॉलमध्ये ब्रिज खात्यावर मालमत्तेचे हस्तांतरण (msg.value) आधीच समाविष्ट असते.

1 // When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future
2 // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if
3 // _from is an EOA or 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) या फंक्शनला फक्त _from पॅरामीटर म्हणून msg.sender सह कॉल करतात.

1 // Construct calldata for _l2Token.finalizeDeposit(_to, _amount)
2 bytes memory message = abi.encodeWithSelector(
3 IL2ERC20Bridge.finalizeDeposit.selector,
4 _l1Token,
5 _l2Token,
6 _from,
7 _to,
8 _amount,
9 _data
10 );
11
12 // Send calldata into 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 * Cross-chain Functions *
8 *************************/
9
10 /**
11 * @inheritdoc IL1StandardBridge
12 */
13 function finalizeETHWithdrawal(
14 address _from,
15 address _to,
16 uint256 _amount,
17 bytes calldata _data
सर्व दाखवा

L2 ब्रिज L2 क्रॉस डोमेन मेसेंजरला एक मेसेज पाठवतो ज्यामुळे L1 क्रॉस डोमेन मेसेंजर या फंक्शनला कॉल करतो (एकदा L1 वर मेसेज अंतिम करणारा ट्रान्झॅक्शनopens in a new tab सबमिट झाला की).

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 */
6 function finalizeERC20Withdrawal(
7 address _l1Token,
8 address _l2Token,
9 address _from,
10 address _to,
11 uint256 _amount,
12 bytes calldata _data
13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {
सर्व दाखवा

हे फंक्शन वरील finalizeETHWithdrawal शी सारखे आहे, ERC-20 टोकनसाठी आवश्यक बदलांसह.

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

deposits डेटा स्ट्रक्चर अपडेट करा.

1
2 // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer
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 * Temporary - Migrating ETH *
13 *****************************/
14
15 /**
16 * @dev Adds ETH balance to the account. This is meant to allow for ETH
17 * to be migrated from an old gateway to a new gateway.
18 * NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the
19 * old contract
20 */
21 function donateETH() external payable {}
22}
सर्व दाखवा

ब्रिजची पूर्वीची अंमलबजावणी होती. जेव्हा आम्ही त्या अंमलबजावणीवरून यावर आलो, तेव्हा आम्हाला सर्व मालमत्ता हलवावी लागली. ERC-20 टोकन फक्त हलवले जाऊ शकतात. तथापि, एका कॉन्ट्रॅक्टवर ETH हस्तांतरित करण्यासाठी तुम्हाला त्या कॉन्ट्रॅक्टची मंजुरी आवश्यक आहे, जी donateETH आपल्याला प्रदान करते.

L2 वरील ERC-20 टोकन्स

एखाद्या ERC-20 टोकनला स्टँडर्ड ब्रिजमध्ये बसण्यासाठी, त्याला स्टँडर्ड ब्रिजला, आणि फक्त स्टँडर्ड ब्रिजला, टोकन मिंट करण्याची परवानगी देणे आवश्यक आहे. हे आवश्यक आहे कारण ब्रिजेसना हे सुनिश्चित करणे आवश्यक आहे की Optimism वर फिरणाऱ्या टोकन्सची संख्या L1 ब्रिज कॉन्ट्रॅक्टमध्ये लॉक केलेल्या टोकन्सच्या संख्येइतकी आहे. जर L2 वर खूप जास्त टोकन असतील तर काही वापरकर्ते त्यांची मालमत्ता L1 वर परत ब्रिज करू शकणार नाहीत. एका विश्वासार्ह ब्रिजऐवजी, आम्ही मूलतः फ्रॅक्शनल रिझर्व्ह बँकिंगopens in a new tab पुन्हा तयार करू. जर L1 वर खूप जास्त टोकन असतील, तर त्यापैकी काही टोकन ब्रिज कॉन्ट्रॅक्टमध्ये कायमचे लॉक राहतील कारण L2 टोकन बर्न केल्याशिवाय त्यांना रिलीज करण्याचा कोणताही मार्ग नाही.

IL2StandardERC20

L2 वरील प्रत्येक ERC-20 टोकन जो स्टँडर्ड ब्रिज वापरतो, त्याला हा इंटरफेसopens in a new tab प्रदान करणे आवश्यक आहे, ज्यात स्टँडर्ड ब्रिजला आवश्यक असलेली फंक्शन्स आणि इव्हेंट्स आहेत.

1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.9;
3
4import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

The standard ERC-20 interfaceopens in a new tab मध्ये mint आणि burn फंक्शन्स समाविष्ट नाहीत. त्या पद्धती the ERC-20 standardopens in a new tab द्वारे आवश्यक नाहीत, जे टोकन तयार आणि नष्ट करण्याच्या यंत्रणा अनिर्दिष्ट ठेवते.

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

The ERC-165 interfaceopens 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";

The OpenZeppelin ERC-20 contractopens 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 Address of the L2 standard bridge.
4 * @param _l1Token Address of the corresponding L1 token.
5 * @param _name ERC20 name.
6 * @param _symbol ERC20 symbol.
7 */
8 constructor(
9 address _l2Bridge,
10 address _l1Token,
11 string memory _name,
12 string memory _symbol
13 ) ERC20(_name, _symbol) {
14 l1Token = _l1Token;
15 l2Bridge = _l2Bridge;
16 }
सर्व दाखवा

प्रथम ज्या कॉन्ट्रॅक्टमधून आपण वारसा घेतो त्याच्या कन्स्ट्रक्टरला कॉल करा (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-165opens in a new tab अशा प्रकारे काम करते. प्रत्येक इंटरफेस समर्थित फंक्शन्सची संख्या आहे आणि त्या फंक्शन्सच्या ABI फंक्शन सिलेक्टर्सopens in a new tab चा exclusive oropens in a new tab म्हणून ओळखला जातो.

L2 ब्रिज ERC-165 चा वापर सॅनिटि चेक म्हणून करतो की ज्या ERC-20 कॉन्ट्रॅक्टला तो मालमत्ता पाठवतो तो IL2StandardERC20 आहे याची खात्री करण्यासाठी.

टीप: दुष्ट कॉन्ट्रॅक्टला supportsInterface ला चुकीची उत्तरे देण्यापासून रोखण्यासाठी काहीही नाही, म्हणून ही एक सॅनिटि चेक यंत्रणा आहे, सुरक्षा यंत्रणा नाही.

1 // slither-disable-next-line external-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/* Interface Imports */
5import { IL1StandardBridge } from "../../L1/messaging/IL1StandardBridge.sol";
6import { IL1ERC20Bridge } from "../../L1/messaging/IL1ERC20Bridge.sol";
7import { IL2ERC20Bridge } from "./IL2ERC20Bridge.sol";

IL2ERC20Bridgeopens in a new tab इंटरफेस वरील L1 समकक्ष शी खूप सारखा आहे. दोन महत्त्वपूर्ण फरक आहेत:

  1. L1 वर तुम्ही डिपॉझिट सुरू करता आणि विड्रॉवल अंतिम करता. येथे तुम्ही विड्रॉवल सुरू करता आणि डिपॉझिट अंतिम करता.
  2. L1 वर ETH आणि ERC-20 टोकनमध्ये फरक करणे आवश्यक आहे. L2 वर आपण दोघांसाठी समान फंक्शन्स वापरू शकतो कारण अंतर्गतपणे Optimism वरील ETH बॅलन्स ERC-20 टोकन म्हणून 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000opens in a new tab ऍड्रेससह हाताळले जातात.
1/* Library Imports */
2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";
4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";
5
6/* Contract Imports */
7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";
8
9/**
10 * @title L2StandardBridge
11 * @dev The L2 Standard bridge is a contract which works together with the L1 Standard bridge to
12 * enable ETH and ERC20 transitions between L1 and L2.
13 * This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard
14 * bridge.
15 * This contract also acts as a burner of the tokens intended for withdrawal, informing the L1
16 * bridge to release L1 funds.
17 */
18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {
19 /********************************
20 * External Contract References *
21 ********************************/
22
23 address public l1TokenBridge;
सर्व दाखवा

L1 ब्रिजच्या ऍड्रेसचा मागोवा ठेवा. लक्षात घ्या की L1 समकक्षाच्या उलट, येथे आम्हाला आवश्यक हे व्हेरिएबल आहे. L1 ब्रिजचा ऍड्रेस आगाऊ माहित नसतो.

1
2 /***************
3 * Constructor *
4 ***************/
5
6 /**
7 * @param _l2CrossDomainMessenger Cross-domain messenger used by this contract.
8 * @param _l1TokenBridge Address of the L1 bridge deployed to the main chain.
9 */
10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)
11 CrossDomainEnabled(_l2CrossDomainMessenger)
12 {
13 l1TokenBridge = _l1TokenBridge;
14 }
15
16 /***************
17 * Withdrawing *
18 ***************/
19
20 /**
21 * @inheritdoc IL2ERC20Bridge
22 */
23 function withdraw(
24 address _l2Token,
25 uint256 _amount,
26 uint32 _l1Gas,
27 bytes calldata _data
28 ) external virtual {
29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);
30 }
31
32 /**
33 * @inheritdoc IL2ERC20Bridge
34 */
35 function withdrawTo(
36 address _l2Token,
37 address _to,
38 uint256 _amount,
39 uint32 _l1Gas,
40 bytes calldata _data
41 ) external virtual {
42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);
43 }
सर्व दाखवा

ही दोन फंक्शन्स विड्रॉवल सुरू करतात. लक्षात घ्या की L1 टोकन ऍड्रेस निर्दिष्ट करण्याची गरज नाही. L2 टोकन्सनी आम्हाला L1 समकक्षाचा ऍड्रेस सांगावा अशी अपेक्षा आहे.

1
2 /**
3 * @dev Performs the logic for withdrawals by burning the token and informing
4 * the L1 token Gateway of the withdrawal.
5 * @param _l2Token Address of L2 token where withdrawal is initiated.
6 * @param _from Account to pull the withdrawal from on L2.
7 * @param _to Account to give the withdrawal to on L1.
8 * @param _amount Amount of the token to withdraw.
9 * @param _l1Gas Unused, but included for potential forward compatibility considerations.
10 * @param _data Optional data to forward to L1. This data is provided
11 * solely as a convenience for external contracts. Aside from enforcing a maximum
12 * length, these contracts provide no guarantees about its content.
13 */
14 function _initiateWithdrawal(
15 address _l2Token,
16 address _from,
17 address _to,
18 uint256 _amount,
19 uint32 _l1Gas,
20 bytes calldata _data
21 ) internal {
22 // When a withdrawal is initiated, we burn the withdrawer's funds to prevent subsequent L2
23 // usage
24 // slither-disable-next-line reentrancy-events
25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);
सर्व दाखवा

लक्षात घ्या की आम्ही _from पॅरामीटरवर अवलंबून नाही तर msg.sender वर आहोत जे खोटे करणे खूप कठीण आहे (मला माहित आहे तोपर्यंत अशक्य).

1
2 // Construct calldata for 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 // Send message up to L1 bridge
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 * Cross-chain Function: Depositing *
30 ************************************/
31
32 /**
33 * @inheritdoc IL2ERC20Bridge
34 */
35 function finalizeDeposit(
36 address _l1Token,
37 address _l2Token,
38 address _from,
39 address _to,
40 uint256 _amount,
41 bytes calldata _data
सर्व दाखवा

हे फंक्शन L1StandardBridge द्वारे कॉल केले जाते.

1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {

मेसेजचा स्त्रोत वैध आहे याची खात्री करा. हे महत्त्वाचे आहे कारण हे फंक्शन _mint ला कॉल करते आणि ब्रिजच्या L1 वरील टोकनने कव्हर न केलेल्या टोकन देण्यासाठी वापरले जाऊ शकते.

1 // Check the target token is compliant and
2 // verify the deposited token on L1 matches the L2 deposited token representation here
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 // When a deposit is finalized, we credit the account on L2 with the same amount of
3 // tokens.
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 // Either the L2 token which is being deposited-into disagrees about the correct address
3 // of its L1 token, or does not support the correct interface.
4 // This should only happen if there is a malicious L2 token, or if a user somehow
5 // specified the wrong L2 token address to deposit into.
6 // In either case, we stop the process here and construct a withdrawal
7 // message so that users can get their funds out in some cases.
8 // There is no way to prevent malicious token contracts altogether, but this does limit
9 // user error and mitigate some forms of malicious contract behavior.
सर्व दाखवा

जर वापरकर्त्याने चुकीचा L2 टोकन ऍड्रेस वापरून ओळखता येणारी चूक केली असेल, तर आम्ही डिपॉझिट रद्द करून L1 वर टोकन परत करू इच्छितो. L2 वरून हे करण्याचा एकमेव मार्ग म्हणजे एक मेसेज पाठवणे ज्याला फॉल्ट चॅलेंज कालावधीची वाट पाहावी लागेल, परंतु वापरकर्त्यासाठी टोकन कायमचे गमावण्यापेक्षा ते बरेच चांगले आहे.

1 bytes memory message = abi.encodeWithSelector(
2 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,
3 _l1Token,
4 _l2Token,
5 _to, // switched the _to and _from here to bounce back the deposit to the sender
6 _from,
7 _amount,
8 _data
9 );
10
11 // Send message up to L1 bridge
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 वापरण्यास प्राधान्य देतात जे चॅलेंज कालावधीची वाट पाहत नाहीत आणि विड्रॉवल अंतिम करण्यासाठी मर्केल प्रूफची आवश्यकता नसते.

हे ब्रिज सामान्यतः L1 वर मालमत्ता ठेवून काम करतात, जे ते थोड्या शुल्कासाठी त्वरित प्रदान करतात (अनेकदा स्टँडर्ड ब्रिज विड्रॉवलसाठी गॅसच्या खर्चापेक्षा कमी). जेव्हा ब्रिज (किंवा ते चालवणारे लोक) L1 मालमत्तेची कमतरता भासेल अशी अपेक्षा करतात तेव्हा ते L2 वरून पुरेशी मालमत्ता हस्तांतरित करतात. हे खूप मोठे विड्रॉवल असल्याने, विड्रॉवल खर्च मोठ्या रकमेवर वितरित केला जातो आणि तो खूपच लहान टक्केवारी असतो.

आशा आहे की या लेखामुळे तुम्हाला लेयर 2 कसे काम करते, आणि स्पष्ट आणि सुरक्षित Solidity कोड कसा लिहायचा याबद्दल अधिक समजण्यास मदत झाली असेल.

माझ्या कामाबद्दल अधिक माहितीसाठी येथे पहाopens in a new tab.

पृष्ठ अखेरचे अद्यतन: २२ ऑक्टोबर, २०२५

हे मार्गदर्शन उपयुक्त होते का?