ऑप्टिमिज्म स्टैंडर्ड ब्रिज कॉन्ट्रैक्ट वॉकथ्रू
ऑप्टिमिज़्म (opens in a new tab) एक ऑप्टिमिस्टिक रोलअप है। ऑप्टिमिस्टिक रोलअप, एथेरियम मेननेट (जिसे लेयर 1 या L1 भी कहा जाता है) की तुलना में बहुत कम कीमत पर ट्रांज़ैक्शन को प्रोसेस कर सकते हैं क्योंकि ट्रांज़ैक्शन नेटवर्क पर हर नोड के बजाय केवल कुछ नोड्स द्वारा प्रोसेस किए जाते हैं। साथ ही, सभी डेटा L1 पर लिखे जाते हैं ताकि मेननेट की सभी अखंडता और उपलब्धता गारंटी के साथ सब कुछ साबित और पुनर्निर्मित किया जा सके।
ऑप्टिमिज्म (या किसी अन्य L2) पर L1 परिसंपत्तियों का उपयोग करने के लिए, परिसंपत्तियों को ब्रिज करने की आवश्यकता होती है। इसे प्राप्त करने का एक तरीका यह है कि यूज़र L1 पर परिसंपत्तियों (ETH और ERC-20 टोकन सबसे आम हैं) को लॉक करें, और L2 पर उपयोग करने के लिए समकक्ष परिसंपत्तियां प्राप्त करें। अंततः, जिसके पास भी वे होते हैं, वह उन्हें L1 पर वापस ब्रिज करना चाह सकता है। ऐसा करने पर, संपत्ति L2 पर बर्न हो जाती है और फिर L1 पर यूज़र को वापस जारी कर दी जाती है।
यह वह तरीका है जिससे ऑप्टिमिज्म स्टैंडर्ड ब्रिज (opens in a new tab) काम करता है। इस लेख में हम उस ब्रिज के सोर्स कोड पर जाते हैं यह देखने के लिए कि यह कैसे काम करता है और अच्छी तरह से लिखे गए सॉलिडिटी कोड के उदाहरण के रूप में इसका अध्ययन करते हैं।
कंट्रोल फ्लो
ब्रिज में दो मुख्य फ्लो हैं:
- जमा (L1 से L2 तक)
- निकासी (L2 से L1 तक)
जमा फ्लो
लेयर 1
- यदि ERC-20 जमा कर रहे हैं, तो जमाकर्ता ब्रिज को जमा की जा रही राशि खर्च करने के लिए भत्ता देता है
- जमाकर्ता L1 ब्रिज (
depositERC20,depositERC20To,depositETH, याdepositETHTo) को कॉल करता है - L1 ब्रिज, ब्रिज की गई संपत्ति का कब्ज़ा ले लेता है
- ETH: संपत्ति को कॉल के हिस्से के रूप में जमाकर्ता द्वारा स्थानांतरित किया जाता है
- ERC-20: संपत्ति को ब्रिज द्वारा जमाकर्ता द्वारा प्रदान किए गए भत्ते का उपयोग करके स्वयं को स्थानांतरित किया जाता है
- L1 ब्रिज, L2 ब्रिज पर
finalizeDepositको कॉल करने के लिए क्रॉस-डोमेन संदेश तंत्र का उपयोग करता है
लेयर 2
- L2 ब्रिज
finalizeDepositके कॉल को सत्यापित करता है कि वह वैध है:- क्रॉस डोमेन संदेश अनुबंध से आया है
- मूल रूप से L1 पर ब्रिज से था
- L2 ब्रिज यह जाँचता है कि L2 पर ERC-20 टोकन अनुबंध सही है या नहीं:
- L2 अनुबंध रिपोर्ट करता है कि उसका L1 समकक्ष वही है जहां से टोकन L1 पर आए थे
- L2 अनुबंध रिपोर्ट करता है कि यह सही इंटरफ़ेस का समर्थन करता है (ERC-165 का उपयोग करके (opens in a new tab))।
- यदि L2 अनुबंध सही है, तो उपयुक्त पते पर उपयुक्त संख्या में टोकन बनाने के लिए इसे कॉल करें। यदि नहीं, तो यूज़र को L1 पर टोकन का दावा करने की अनुमति देने के लिए निकासी प्रक्रिया शुरू करें।
निकासी फ्लो
लेयर 2
- निकासीकर्ता L2 ब्रिज (
withdrawयाwithdrawTo) को कॉल करता है - L2 ब्रिज
msg.senderसे संबंधित टोकन की उचित संख्या को बर्न करता है - L2 ब्रिज, L1 ब्रिज पर
finalizeETHWithdrawalयाfinalizeERC20Withdrawalको कॉल करने के लिए क्रॉस-डोमेन संदेश तंत्र का उपयोग करता है
लेयर 1
- L1 ब्रिज
finalizeETHWithdrawalयाfinalizeERC20Withdrawalके कॉल को सत्यापित करता है कि वह वैध है:- क्रॉस डोमेन संदेश तंत्र से आया है
- मूल रूप से L2 पर ब्रिज से था
- L1 ब्रिज उपयुक्त संपत्ति (ETH या ERC-20) को उपयुक्त पते पर स्थानांतरित करता है
लेयर 1 कोड
यह वह कोड है जो L1, एथेरियम मेननेट पर चलता है।
IL1ERC20Bridge
यह इंटरफ़ेस यहाँ परिभाषित किया गया है (opens in a new tab)। इसमें ERC-20 टोकन को ब्रिज करने के लिए आवश्यक फ़ंक्शन और परिभाषाएँ शामिल हैं।
1// SPDX-License-Identifier: MITऑप्टिमिज्म का अधिकांश कोड MIT लाइसेंस के तहत जारी किया गया है (opens in a new tab)।
1pragma solidity >0.5.0 <0.9.0;लिखने के समय सॉलिडिटी का नवीनतम संस्करण 0.8.12 है। जब तक संस्करण 0.9.0 जारी नहीं हो जाता, हम नहीं जानते कि यह कोड इसके साथ संगत है या नहीं।
1/**2 * @title IL1ERC20Bridge3 */4interface IL1ERC20Bridge {5 /**********6 * इवेंट्स *7 **********/89 event ERC20DepositInitiated(सभी दिखाएँऑप्टिमिज्म ब्रिज शब्दावली में 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 (ऑप्टिमिज्म) पर है।
अन्य दो chainId मान कोवन टेस्ट नेटवर्क (42) और ऑप्टिमिस्टिक कोवन टेस्ट नेटवर्क (69) के लिए हैं।
1 address indexed _from,2 address _to,3 uint256 _amount,4 bytes _data5 );स्थानांतरण में नोट्स जोड़ना संभव है, जिस स्थिति में वे उन्हें रिपोर्ट करने वाले इवेंट्स में जोड़े जाते हैं।
1 event ERC20WithdrawalFinalized(2 address indexed _l1Token,3 address indexed _l2Token,4 address indexed _from,5 address _to,6 uint256 _amount,7 bytes _data8 );एक ही ब्रिज अनुबंध दोनों दिशाओं में स्थानांतरण को संभालता है। L1 ब्रिज के मामले में, इसका मतलब जमा की शुरुआत और निकासी को अंतिम रूप देना है।
12 /********************3 * सार्वजनिक फ़ंक्शन *4 ********************/56 /**7 * @dev संबंधित L2 ब्रिज अनुबंध का पता प्राप्त करें।8 * @return संबंधित L2 ब्रिज अनुबंध का पता।9 */10 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 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा9 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।10 */11 function depositERC20(12 address _l1Token,13 address _l2Token,14 uint256 _amount,15 uint32 _l2Gas,16 bytes calldata _data17 ) external;सभी दिखाएँ_l2Gas पैरामीटर L2 गैस की वह राशि है जिसे ट्रांज़ैक्शन खर्च करने की अनुमति है।
एक निश्चित (उच्च) सीमा तक, यह मुफ़्त है (opens in a new tab), इसलिए जब तक ERC-20 अनुबंध मिन्टिंग के समय कुछ वास्तव में अजीब नहीं करता है, यह एक मुद्दा नहीं होना चाहिए।
यह फ़ंक्शन सामान्य परिदृश्य का ध्यान रखता है, जहां एक यूज़र एक अलग ब्लॉकचेन पर एक ही पते पर संपत्ति को ब्रिज करता है।
1 /**2 * @dev 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 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा10 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।11 */12 function depositERC20To(13 address _l1Token,14 address _l2Token,15 address _to,16 uint256 _amount,17 uint32 _l2Gas,18 bytes calldata _data19 ) external;सभी दिखाएँयह फ़ंक्शन depositERC20 के लगभग समान है, लेकिन यह आपको ERC-20 को एक अलग पते पर भेजने की सुविधा देता है।
1 /*************************2 * क्रॉस-चेन फ़ंक्शन *3 *************************/45 /**6 * @dev L2 से L1 में निकासी पूरी करें, और धन को प्राप्तकर्ता की7 * L1 ERC20 टोकन की शेष राशि में जमा करें।8 * यदि L2 से आरंभ की गई निकासी को अंतिम रूप नहीं दिया गया है तो यह कॉल विफल हो जाएगा।9 *10 * @param _l1Token L1 टोकन का पता जिसके लिए finalizeWithdrawal करना है।11 * @param _l2Token L2 टोकन का पता जहां निकासी शुरू की गई थी।12 * @param _from स्थानांतरण शुरू करने वाला L2 पता।13 * @param _to L1 पता जिसमें निकासी जमा करनी है।14 * @param _amount जमा करने के लिए ERC20 की राशि।15 * @param _data L2 पर प्रेषक द्वारा प्रदान किया गया डेटा। यह डेटा प्रदान किया गया है16 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा17 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।18 */19 function finalizeERC20Withdrawal(20 address _l1Token,21 address _l2Token,22 address _from,23 address _to,24 uint256 _amount,25 bytes calldata _data26 ) external;27}सभी दिखाएँऑप्टिमिज्म में निकासी (और L2 से L1 के अन्य संदेश) एक दो-चरणीय प्रक्रिया है:
- L2 पर एक प्रारंभिक ट्रांज़ैक्शन।
- 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: MIT2pragma solidity >0.5.0 <0.9.0;34import "./IL1ERC20Bridge.sol";56/**7 * @title IL1StandardBridge8 */9interface IL1StandardBridge is IL1ERC20Bridge {10 /**********11 * इवेंट्स *12 **********/13 event ETHDepositInitiated(14 address indexed _from,15 address indexed _to,16 uint256 _amount,17 bytes _data18 );सभी दिखाएँयह इवेंट ERC-20 संस्करण (ERC20DepositInitiated) के लगभग समान है, सिवाय L1 और L2 टोकन पतों के बिना।
यही बात अन्य इवेंट्स और फ़ंक्शन के लिए भी सच है।
1 event ETHWithdrawalFinalized(2 .3 .4 .5 );67 /********************8 * सार्वजनिक फ़ंक्शन *9 ********************/1011 /**12 * @dev ETH की एक राशि को L2 पर कॉलर की शेष राशि में जमा करें।13 .14 .15 .16 */17 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable;1819 /**20 * @dev L2 पर प्राप्तकर्ता की शेष राशि में ETH की राशि जमा करें।21 .22 .23 .24 */25 function depositETHTo(26 address _to,27 uint32 _l2Gas,28 bytes calldata _data29 ) external payable;3031 /*************************32 * क्रॉस-चेन फ़ंक्शन *33 *************************/3435 /**36 * @dev L2 से L1 में निकासी पूरी करें, और धन को प्राप्तकर्ता की37 * L1 ETH टोकन की शेष राशि में जमा करें। चूंकि केवल xDomainMessenger ही इस फ़ंक्शन को कॉल कर सकता है, इसलिए इसे कभी भी38 * निकासी को अंतिम रूप दिए जाने से पहले कॉल नहीं किया जाएगा।39 .40 .41 .42 */43 function finalizeETHWithdrawal(44 address _from,45 address _to,46 uint256 _amount,47 bytes calldata _data48 ) external;49}सभी दिखाएँCrossDomainEnabled
यह अनुबंध (opens in a new tab) दोनों ब्रिज (L1 और L2) द्वारा दूसरी लेयर को संदेश भेजने के लिए इनहेरिट किया गया है।
1// SPDX-License-Identifier: MIT2pragma solidity >0.5.0 <0.9.0;34/* इंटरफ़ेस इम्पोर्ट */5import { ICrossDomainMessenger } from "./ICrossDomainMessenger.sol";यह इंटरफ़ेस (opens in a new tab) अनुबंध को बताता है कि क्रॉस डोमेन मैसेंजर का उपयोग करके दूसरी लेयर को संदेश कैसे भेजें। यह क्रॉस डोमेन मैसेंजर एक पूरी तरह से अलग सिस्टम है, और इसके अपने लेख का हकदार है, जिसे मैं भविष्य में लिखने की उम्मीद करता हूं।
1/**2 * @title CrossDomainEnabled3 * @dev क्रॉस-डोमेन संचार करने वाले अनुबंधों के लिए हेल्पर अनुबंध4 *5 * कंपाइलर का उपयोग किया गया: इनहेरिट करने वाले अनुबंध द्वारा परिभाषित6 */7contract CrossDomainEnabled {8 /*************9 * चर *10 *************/1112 // मैसेंजर अनुबंध का उपयोग दूसरे डोमेन से संदेश भेजने और प्राप्त करने के लिए किया जाता है।13 address public messenger;1415 /***************16 * कंस्ट्रक्टर *17 ***************/1819 /**20 * @param _messenger वर्तमान लेयर पर CrossDomainMessenger का पता।21 */22 constructor(address _messenger) {23 messenger = _messenger;24 }सभी दिखाएँएक पैरामीटर जो अनुबंध को जानना आवश्यक है, वह है इस लेयर पर क्रॉस डोमेन मैसेंजर का पता। यह पैरामीटर एक बार, कंस्ट्रक्टर में सेट किया जाता है, और कभी नहीं बदलता है।
12 /**********************3 * फ़ंक्शन संशोधक *4 **********************/56 /**7 * यह सुनिश्चित करता है कि संशोधित फ़ंक्शन केवल एक विशिष्ट क्रॉस-डोमेन खाते द्वारा कॉल करने योग्य है।8 * @param _sourceDomainAccount मूल डोमेन पर एकमात्र खाता जो इस फ़ंक्शन को कॉल करने के लिए9 * प्रमाणित है।10 */11 modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {सभी दिखाएँक्रॉस डोमेन मैसेजिंग उस ब्लॉकचेन पर किसी भी अनुबंध द्वारा सुलभ है जहां यह चल रहा है (या तो एथेरियम मेननेट या ऑप्टिमिज्म)। लेकिन हमें प्रत्येक तरफ ब्रिज की आवश्यकता है ताकि कुछ संदेशों पर केवल भरोसा किया जा सके यदि वे दूसरी तरफ के ब्रिज से आते हैं।
1 require(2 msg.sender == address(getCrossDomainMessenger()),3 "OVM_XCHAIN: messenger contract unauthenticated"4 );केवल उपयुक्त क्रॉस डोमेन मैसेंजर (messenger, जैसा कि आप नीचे देखते हैं) से संदेशों पर भरोसा किया जा सकता है।
12 require(3 getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,4 "OVM_XCHAIN: wrong sender of cross-domain message"5 );जिस तरह से क्रॉस डोमेन मैसेंजर उस पते को प्रदान करता है जिसने दूसरी लेयर के साथ एक संदेश भेजा है, वह .xDomainMessageSender() फ़ंक्शन (opens in a new tab) है।
जब तक इसे उस ट्रांज़ैक्शन में कॉल किया जाता है जिसे संदेश द्वारा शुरू किया गया था, यह इस जानकारी को प्रदान कर सकता है।
हमें यह सुनिश्चित करने की आवश्यकता है कि हमें जो संदेश मिला है वह दूसरे ब्रिज से आया है।
12 _;3 }45 /**********************6 * आंतरिक फ़ंक्शन *7 **********************/89 /**10 * मैसेंजर प्राप्त करता है, आमतौर पर स्टोरेज से। यह फ़ंक्शन उस स्थिति में उजागर होता है जब किसी चाइल्ड अनुबंध को11 * ओवरराइड करने की आवश्यकता होती है।12 * @return क्रॉस-डोमेन मैसेंजर अनुबंध का पता जिसका उपयोग किया जाना चाहिए।13 */14 function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {15 return ICrossDomainMessenger(messenger);16 }सभी दिखाएँयह फ़ंक्शन क्रॉस डोमेन मैसेंजर लौटाता है।
हम messenger चर के बजाय एक फ़ंक्शन का उपयोग करते हैं ताकि इससे इनहेरिट करने वाले अनुबंध एक एल्गोरिथम का उपयोग कर सकें यह निर्दिष्ट करने के लिए कि किस क्रॉस डोमेन मैसेंजर का उपयोग करना है।
12 /**3 * दूसरे डोमेन पर एक खाते में एक संदेश भेजता है4 * @param _crossDomainTarget गंतव्य डोमेन पर इच्छित प्राप्तकर्ता5 * @param _message लक्ष्य को भेजने के लिए डेटा (आमतौर पर `onlyFromCrossDomainAccount()` के साथ एक फ़ंक्शन के लिए calldata)6 * @param _gasLimit लक्ष्य डोमेन पर संदेश की रसीद के लिए gasLimit।7 */8 function sendCrossDomainMessage(9 address _crossDomainTarget,10 uint32 _gasLimit,11 bytes memory _messageसभी दिखाएँअंत में, वह फ़ंक्शन जो दूसरी लेयर को एक संदेश भेजता है।
1 ) internal {2 // slither-disable-next-line reentrancy-events, reentrancy-benignस्लिदर (opens in a new tab) एक स्टैटिक एनालाइज़र है जिसे ऑप्टिमिज्म हर अनुबंध पर चलाता है ताकि कमजोरियों और अन्य संभावित समस्याओं की तलाश की जा सके। इस मामले में, निम्नलिखित लाइन दो कमजोरियों को ट्रिगर करती है:
1 getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);2 }3}इस मामले में हम रीएंट्रेंसी के बारे में चिंतित नहीं हैं, हम जानते हैं कि getCrossDomainMessenger() एक भरोसेमंद पता लौटाता है, भले ही स्लिदर के पास यह जानने का कोई तरीका न हो।
L1 ब्रिज अनुबंध
इस अनुबंध का सोर्स कोड यहाँ है (opens in a new tab)।
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;इंटरफ़ेस अन्य अनुबंधों का हिस्सा हो सकते हैं, इसलिए उन्हें सॉलिडिटी संस्करणों की एक विस्तृत श्रृंखला का समर्थन करना होता है। लेकिन ब्रिज खुद हमारा अनुबंध है, और हम इस बारे में सख्त हो सकते हैं कि यह किस सॉलिडिटी संस्करण का उपयोग करता है।
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";ओपनज़ेपेलिन की पता उपयोगिताएँ (opens in a new tab)। इसका उपयोग अनुबंध पतों और बाह्य रूप से स्वामित्व वाले खातों (EOA) से संबंधित पतों के बीच अंतर करने के लिए किया जाता है।
ध्यान दें कि यह एक आदर्श समाधान नहीं है, क्योंकि सीधे कॉल और एक अनुबंध के कंस्ट्रक्टर से किए गए कॉल के बीच अंतर करने का कोई तरीका नहीं है, लेकिन कम से कम यह हमें कुछ सामान्य यूज़र त्रुटियों को पहचानने और रोकने की सुविधा देता है।
1import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";ERC-20 मानक (opens in a new tab) एक अनुबंध के लिए विफलता की रिपोर्ट करने के दो तरीकों का समर्थन करता है:
- रिवर्ट करें
falseलौटाएं
दोनों मामलों को संभालने से हमारा कोड और अधिक जटिल हो जाएगा, इसलिए इसके बजाय हम ओपनज़ेपेलिन के SafeERC20 (opens in a new tab) का उपयोग करते हैं, जो यह सुनिश्चित करता है कि सभी विफलताएं एक रिवर्ट में परिणत हों (opens in a new tab)।
1/**2 * @title L1StandardBridge3 * @dev L1 ETH और ERC20 ब्रिज एक अनुबंध है जो जमा किए गए L1 फंड और L2 पर उपयोग में आने वाले स्टैंडर्ड टोकन को संग्रहीत करता है।4 * यह एक संबंधित L2 ब्रिज को सिंक्रनाइज़ करता है, इसे जमा के बारे में सूचित करता है5 * और नई अंतिम रूप दी गई निकासी के लिए इसे सुनता है।6 *7 */8contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {9 using SafeERC20 for IERC20;सभी दिखाएँयह लाइन है कि हम IERC20 इंटरफ़ेस का उपयोग करते समय हर बार SafeERC20 रैपर का उपयोग करने के लिए कैसे निर्दिष्ट करते हैं।
12 /********************************3 * बाहरी अनुबंध संदर्भ *4 ********************************/56 address public l2TokenBridge;L2StandardBridge का पता।
12 // जमा किए गए 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] के रूप में पहचाने जाते हैं।
डिफ़ॉल्ट मान शून्य है।
केवल वे सेल जो एक अलग मान पर सेट हैं, स्टोरेज में लिखे जाते हैं।
12 /***************3 * कंस्ट्रक्टर *4 ***************/56 // यह अनुबंध एक प्रॉक्सी के पीछे रहता है, इसलिए कंस्ट्रक्टर पैरामीटर का उपयोग नहीं किया जाएगा।7 constructor() CrossDomainEnabled(address(0)) {}स्टोरेज में सभी चर को कॉपी किए बिना इस अनुबंध को अपग्रेड करने में सक्षम होना चाहते हैं।
ऐसा करने के लिए हम एक Proxy (opens in a new tab) का उपयोग करते हैं, एक अनुबंध जो एक अलग अनुबंध में कॉल स्थानांतरित करने के लिए delegatecall (opens in a new tab) का उपयोग करता है जिसका पता प्रॉक्सी अनुबंध द्वारा संग्रहीत किया जाता है (जब आप अपग्रेड करते हैं तो आप प्रॉक्सी को उस पते को बदलने के लिए कहते हैं)।
जब आप delegatecall का उपयोग करते हैं तो स्टोरेज कॉलिंग अनुबंध का स्टोरेज बना रहता है, इसलिए सभी अनुबंध स्थिति चर के मान अप्रभावित रहते हैं।
इस पैटर्न का एक प्रभाव यह है कि delegatecall के कॉल किए गए अनुबंध का स्टोरेज उपयोग नहीं किया जाता है और इसलिए इसे पास किए गए कंस्ट्रक्टर मानों का कोई मतलब नहीं है।
यही कारण है कि हम CrossDomainEnabled कंस्ट्रक्टर को एक निरर्थक मान प्रदान कर सकते हैं।
यह भी कारण है कि नीचे आरंभीकरण कंस्ट्रक्टर से अलग है।
1 /******************2 * आरंभीकरण *3 ******************/45 /**6 * @param _l1messenger L1 Messenger पता जिसका उपयोग क्रॉस-चेन संचार के लिए किया जा रहा है।7 * @param _l2TokenBridge L2 स्टैंडर्ड ब्रिज पता।8 */9 // slither-disable-next-line external-functionसभी दिखाएँयह स्लिदर परीक्षण (opens in a new tab) उन फ़ंक्शन की पहचान करता है जिन्हें अनुबंध कोड से नहीं बुलाया जाता है और इसलिए उन्हें public के बजाय external घोषित किया जा सकता है।
external फ़ंक्शन की गैस लागत कम हो सकती है, क्योंकि उन्हें calldata में पैरामीटर के साथ प्रदान किया जा सकता है।
public घोषित फ़ंक्शन अनुबंध के भीतर से सुलभ होने चाहिए।
अनुबंध अपने स्वयं के calldata को संशोधित नहीं कर सकते हैं, इसलिए पैरामीटर मेमोरी में होने चाहिए।
जब ऐसे फ़ंक्शन को बाह्य रूप से कॉल किया जाता है, तो calldata को मेमोरी में कॉपी करना आवश्यक होता है, जिसमें गैस लगती है।
इस मामले में फ़ंक्शन को केवल एक बार कॉल किया जाता है, इसलिए अक्षमता हमारे लिए कोई मायने नहीं रखती है।
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) कर सकता है। लेकिन इसे रोकने के दो तरीके हैं:
- यदि अनुबंध सीधे एक EOA द्वारा नहीं बल्कि एक ट्रांज़ैक्शन में जिसमें एक और अनुबंध उन्हें बनाता है (opens in a new tab) तैनात किए जाते हैं, तो पूरी प्रक्रिया परमाणु हो सकती है, और किसी भी अन्य ट्रांज़ैक्शन के निष्पादित होने से पहले समाप्त हो सकती है।
- यदि
initializeके लिए वैध कॉल विफल हो जाती है, तो नए बनाए गए प्रॉक्सी और ब्रिज को अनदेखा करना और नए बनाना हमेशा संभव होता है।
1 messenger = _l1messenger;2 l2TokenBridge = _l2TokenBridge;3 }ये दो पैरामीटर हैं जिन्हें ब्रिज को जानना आवश्यक है।
12 /**************3 * जमा करना *4 **************/56 /** @dev प्रेषक को EOA होने की आवश्यकता वाला संशोधक। इस जाँच को एक दुर्भावनापूर्ण7 * अनुबंध द्वारा initcode के माध्यम से बायपास किया जा सकता है, लेकिन यह उस यूज़र त्रुटि का ध्यान रखता है जिससे हम बचना चाहते हैं।8 */9 modifier onlyEOA() {10 // अनुबंधों से जमा को रोकने के लिए उपयोग किया जाता है (गलती से खोए हुए टोकन से बचें)11 require(!Address.isContract(msg.sender), "Account not EOA");12 _;13 }सभी दिखाएँयही कारण है कि हमें ओपनज़ेपेलिन की Address उपयोगिताओं की आवश्यकता थी।
1 /**2 * @dev इस फ़ंक्शन को बिना किसी डेटा के कॉल किया जा सकता है3 * L2 पर कॉलर की शेष राशि में ETH की राशि जमा करने के लिए।4 * चूंकि प्राप्त फ़ंक्शन डेटा नहीं लेता है, एक रूढ़िवादी5 * डिफ़ॉल्ट राशि L2 को अग्रेषित की जाती है।6 */7 receive() external payable onlyEOA {8 _initiateETHDeposit(msg.sender, msg.sender, 200_000, bytes(""));9 }सभी दिखाएँयह फ़ंक्शन परीक्षण उद्देश्यों के लिए मौजूद है। ध्यान दें कि यह इंटरफ़ेस परिभाषाओं में दिखाई नहीं देता है - यह सामान्य उपयोग के लिए नहीं है।
1 /**2 * @inheritdoc IL1StandardBridge3 */4 function depositETH(uint32 _l2Gas, bytes calldata _data) external payable onlyEOA {5 _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);6 }78 /**9 * @inheritdoc IL1StandardBridge10 */11 function depositETHTo(12 address _to,13 uint32 _l2Gas,14 bytes calldata _data15 ) external payable {16 _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);17 }सभी दिखाएँये दो फ़ंक्शन _initiateETHDeposit के आसपास रैपर हैं, वह फ़ंक्शन जो वास्तविक ETH जमा को संभालता है।
1 /**2 * @dev ETH को संग्रहीत करके और L2 ETH गेटवे को3 * जमा के बारे में सूचित करके जमा के लिए तर्क करता है।4 * @param _from L1 पर जमा को खींचने के लिए खाता।5 * @param _to L2 पर जमा देने के लिए खाता।6 * @param _l2Gas L2 पर जमा को पूरा करने के लिए आवश्यक गैस सीमा।7 * @param _data L2 को अग्रेषित करने के लिए वैकल्पिक डेटा। यह डेटा प्रदान किया गया है8 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा9 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।10 */11 function _initiateETHDeposit(12 address _from,13 address _to,14 uint32 _l2Gas,15 bytes memory _data16 ) internal {17 // finalizeDeposit कॉल के लिए calldata का निर्माण करें18 bytes memory message = abi.encodeWithSelector(सभी दिखाएँजिस तरह से क्रॉस डोमेन संदेश काम करते हैं वह यह है कि गंतव्य अनुबंध को उसके calldata के रूप में संदेश के साथ कॉल किया जाता है।
सॉलिडिटी अनुबंध हमेशा अपने calldata की व्याख्या
ABI विनिर्देशों (opens in a new tab) के अनुसार करते हैं।
सॉलिडिटी फ़ंक्शन abi.encodeWithSelector (opens in a new tab) उस calldata को बनाता है।
1 IL2ERC20Bridge.finalizeDeposit.selector,2 address(0),3 Lib_PredeployAddresses.OVM_ETH,4 _from,5 _to,6 msg.value,7 _data8 );यहां संदेश इन मापदंडों के साथ finalizeDeposit फ़ंक्शन (opens in a new tab) को कॉल करना है:
| पैरामीटर | मूल्य | अर्थ |
|---|---|---|
| _l1Token | address(0) | L1 पर ETH (जो एक ERC-20 टोकन नहीं है) के लिए खड़े होने के लिए विशेष मान |
| _l2Token | Lib_PredeployAddresses.OVM_ETH | L2 अनुबंध जो ऑप्टिमिज्म पर ETH का प्रबंधन करता है, 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (यह अनुबंध केवल आंतरिक ऑप्टिमिज्म उपयोग के लिए है) |
| _from | _from | L1 पर वह पता जो ETH भेजता है |
| _to | _to | L2 पर वह पता जो ETH प्राप्त करता है |
| राशि | msg.value | भेजे गए wei की राशि (जो पहले ही ब्रिज को भेजी जा चुकी है) |
| _data | _data | जमा में संलग्न करने के लिए अतिरिक्त डेटा |
1 // L2 में calldata भेजें2 // slither-disable-next-line reentrancy-events3 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);क्रॉस डोमेन मैसेंजर के माध्यम से संदेश भेजें।
1 // slither-disable-next-line reentrancy-events2 emit ETHDepositInitiated(_from, _to, msg.value, _data);3 }इस स्थानांतरण के बारे में सुनने वाले किसी भी विकेंद्रीकृत अनुप्रयोग को सूचित करने के लिए एक इवेंट उत्सर्जित करें।
1 /**2 * @inheritdoc IL1ERC20Bridge3 */4 function depositERC20(5 .6 .7 .8 ) external virtual onlyEOA {9 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, msg.sender, _amount, _l2Gas, _data);10 }1112 /**13 * @inheritdoc IL1ERC20Bridge14 */15 function depositERC20To(16 .17 .18 .19 ) external virtual {20 _initiateERC20Deposit(_l1Token, _l2Token, msg.sender, _to, _amount, _l2Gas, _data);21 }सभी दिखाएँये दो फ़ंक्शन _initiateERC20Deposit के आसपास रैपर हैं, वह फ़ंक्शन जो वास्तविक ERC-20 जमा को संभालता है।
1 /**2 * @dev L2 जमा किए गए टोकन को सूचित करके जमा के लिए तर्क करता है3 * अनुबंध को जमा के बारे में और L1 फंड को लॉक करने के लिए एक हैंडलर को कॉल करता है। (जैसे, transferFrom)4 *5 * @param _l1Token हम जिस L1 ERC20 को जमा कर रहे हैं उसका पता6 * @param _l2Token L1 संबंधित L2 ERC20 का पता7 * @param _from L1 पर जमा को खींचने के लिए खाता8 * @param _to L2 पर जमा देने के लिए खाता9 * @param _amount जमा करने के लिए ERC20 की राशि।10 * @param _l2Gas L2 पर जमा को पूरा करने के लिए आवश्यक गैस सीमा।11 * @param _data L2 को अग्रेषित करने के लिए वैकल्पिक डेटा। यह डेटा प्रदान किया गया है12 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा13 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।14 */15 function _initiateERC20Deposit(16 address _l1Token,17 address _l2Token,18 address _from,19 address _to,20 uint256 _amount,21 uint32 _l2Gas,22 bytes calldata _data23 ) internal {सभी दिखाएँयह फ़ंक्शन ऊपर दिए गए _initiateETHDeposit के समान है, जिसमें कुछ महत्वपूर्ण अंतर हैं।
पहला अंतर यह है कि यह फ़ंक्शन टोकन पते और स्थानांतरित करने के लिए राशि को पैरामीटर के रूप में प्राप्त करता है।
ETH के मामले में ब्रिज पर कॉल में पहले से ही ब्रिज खाते में संपत्ति का स्थानांतरण शामिल है (msg.value)।
1 // जब L1 पर एक जमा शुरू किया जाता है, तो L1 ब्रिज भविष्य के लिए फंड को खुद को स्थानांतरित करता है2 // निकासी। safeTransferFrom यह भी जांचता है कि अनुबंध में कोड है या नहीं, इसलिए यह विफल हो जाएगा यदि3 // _from एक EOA या address(0) है।4 // slither-disable-next-line reentrancy-events, reentrancy-benign5 IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);ERC-20 टोकन स्थानांतरण ETH से एक अलग प्रक्रिया का पालन करते हैं:
- यूज़र (
_from) उपयुक्त टोकन स्थानांतरित करने के लिए ब्रिज को एक भत्ता देता है। - यूज़र टोकन अनुबंध के पते, राशि आदि के साथ ब्रिज को कॉल करता है।
- ब्रिज जमा प्रक्रिया के हिस्से के रूप में टोकन (स्वयं को) स्थानांतरित करता है।
पहला चरण अंतिम दो से एक अलग ट्रांज़ैक्शन में हो सकता है।
हालांकि, फ्रंट-रनिंग कोई समस्या नहीं है क्योंकि _initiateERC20Deposit (depositERC20 और depositERC20To) को कॉल करने वाले दो फ़ंक्शन केवल msg.sender को _from पैरामीटर के रूप में इस फ़ंक्शन को कॉल करते हैं।
1 // _l2Token.finalizeDeposit(_to, _amount) के लिए calldata का निर्माण करें2 bytes memory message = abi.encodeWithSelector(3 IL2ERC20Bridge.finalizeDeposit.selector,4 _l1Token,5 _l2Token,6 _from,7 _to,8 _amount,9 _data10 );1112 // L2 में calldata भेजें13 // slither-disable-next-line reentrancy-events, reentrancy-benign14 sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);1516 // slither-disable-next-line reentrancy-benign17 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;सभी दिखाएँdeposits डेटा संरचना में जमा किए गए टोकन की राशि जोड़ें।
L2 पर कई पते हो सकते हैं जो एक ही L1 ERC-20 टोकन के अनुरूप हों, इसलिए जमा का ट्रैक रखने के लिए ब्रिज के L1 ERC-20 टोकन की शेष राशि का उपयोग करना पर्याप्त नहीं है।
12 // slither-disable-next-line reentrancy-events3 emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);4 }56 /*************************7 * क्रॉस-चेन फ़ंक्शन *8 *************************/910 /**11 * @inheritdoc IL1StandardBridge12 */13 function finalizeETHWithdrawal(14 address _from,15 address _to,16 uint256 _amount,17 bytes calldata _dataसभी दिखाएँL2 ब्रिज L2 क्रॉस डोमेन मैसेंजर को एक संदेश भेजता है जो L1 क्रॉस डोमेन मैसेंजर को इस फ़ंक्शन को कॉल करने का कारण बनता है (एक बार जब संदेश को अंतिम रूप देने वाला ट्रांज़ैक्शन (opens in a new tab) L1 पर सबमिट किया जाता है, निश्चित रूप से)।
1 ) external onlyFromCrossDomainAccount(l2TokenBridge) {सुनिश्चित करें कि यह एक वैध संदेश है, जो क्रॉस डोमेन मैसेंजर से आ रहा है और L2 टोकन ब्रिज से उत्पन्न हो रहा है। इस फ़ंक्शन का उपयोग ब्रिज से ETH निकालने के लिए किया जाता है, इसलिए हमें यह सुनिश्चित करना होगा कि इसे केवल अधिकृत कॉलर द्वारा ही कॉल किया जाए।
1 // slither-disable-next-line reentrancy-events2 (bool success, ) = _to.call{ value: _amount }(new bytes(0));ETH स्थानांतरित करने का तरीका msg.value में wei की राशि के साथ प्राप्तकर्ता को कॉल करना है।
1 require(success, "TransferHelper::safeTransferETH: ETH transfer failed");23 // slither-disable-next-line reentrancy-events4 emit ETHWithdrawalFinalized(_from, _to, _amount, _data);निकासी के बारे में एक इवेंट उत्सर्जित करें।
1 }23 /**4 * @inheritdoc IL1ERC20Bridge5 */6 function finalizeERC20Withdrawal(7 address _l1Token,8 address _l2Token,9 address _from,10 address _to,11 uint256 _amount,12 bytes calldata _data13 ) external onlyFromCrossDomainAccount(l2TokenBridge) {सभी दिखाएँयह फ़ंक्शन ऊपर finalizeETHWithdrawal के समान है, जिसमें ERC-20 टोकन के लिए आवश्यक परिवर्तन हैं।
1 deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;deposits डेटा संरचना को अपडेट करें।
12 // जब L1 पर एक निकासी को अंतिम रूप दिया जाता है, तो L1 ब्रिज फंड को निकासीकर्ता को स्थानांतरित करता है3 // slither-disable-next-line reentrancy-events4 IERC20(_l1Token).safeTransfer(_to, _amount);56 // slither-disable-next-line reentrancy-events7 emit ERC20WithdrawalFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);8 }91011 /*****************************12 * अस्थायी - ETH माइग्रेट करना *13 *****************************/1415 /**16 * @dev खाते में ETH शेष राशि जोड़ता है। इसका उद्देश्य ETH को17 * एक पुराने गेटवे से एक नए गेटवे पर माइग्रेट करने की अनुमति देना है।18 * नोट: यह केवल एक अपग्रेड के लिए छोड़ा गया है ताकि हम पुराने अनुबंध से माइग्रेट किए गए ETH को प्राप्त करने में सक्षम हो सकें19 *20 */21 function donateETH() external payable {}22}सभी दिखाएँब्रिज का एक पहले का कार्यान्वयन था।
जब हम कार्यान्वयन से इस पर चले गए, तो हमें सभी संपत्तियों को स्थानांतरित करना पड़ा।
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) प्रदान करने की आवश्यकता है, जिसमें स्टैंडर्ड ब्रिज को आवश्यक फ़ंक्शन और इवेंट्स हैं।
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";स्टैंडर्ड ERC-20 इंटरफ़ेस (opens in a new tab) में mint और burn फ़ंक्शन शामिल नहीं हैं।
वे विधियाँ ERC-20 मानक (opens in a new tab) द्वारा आवश्यक नहीं हैं, जो टोकन बनाने और नष्ट करने के तंत्र को अनिर्दिष्ट छोड़ देता है।
1import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";ERC-165 इंटरफ़ेस (opens in a new tab) का उपयोग यह निर्दिष्ट करने के लिए किया जाता है कि एक अनुबंध कौन से फ़ंक्शन प्रदान करता है। आप मानक को यहां पढ़ सकते हैं (opens in a new tab)।
1interface IL2StandardERC20 is IERC20, IERC165 {2 function l1Token() external returns (address);यह फ़ंक्शन L1 टोकन का पता प्रदान करता है जिसे इस अनुबंध से ब्रिज किया गया है। ध्यान दें कि हमारे पास विपरीत दिशा में कोई समान फ़ंक्शन नहीं है। हमें किसी भी L1 टोकन को ब्रिज करने में सक्षम होना चाहिए, भले ही इसे लागू करते समय L2 समर्थन की योजना बनाई गई हो या नहीं।
12 function mint(address _to, uint256 _amount) external;34 function burn(address _from, uint256 _amount) external;56 event Mint(address indexed _account, uint256 _amount);7 event Burn(address indexed _account, uint256 _amount);8}टोकन को मिंट (बनाने) और बर्न (नष्ट) करने के लिए फ़ंक्शन और इवेंट्स। ब्रिज को एकमात्र इकाई होनी चाहिए जो यह सुनिश्चित करने के लिए इन कार्यों को चला सके कि टोकन की संख्या सही है (L1 पर लॉक किए गए टोकन की संख्या के बराबर)।
L2StandardERC20
यह IL2StandardERC20 इंटरफ़ेस का हमारा कार्यान्वयन है (opens in a new tab)।
जब तक आपको किसी प्रकार के कस्टम तर्क की आवश्यकता न हो, आपको इसका उपयोग करना चाहिए।
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";ओपनज़ेपेलिन ERC-20 अनुबंध (opens in a new tab)। ऑप्टिमिज्म पहिया को फिर से आविष्कार करने में विश्वास नहीं करता है, खासकर जब पहिया अच्छी तरह से ऑडिट किया गया हो और संपत्ति रखने के लिए पर्याप्त भरोसेमंद होना चाहिए।
1import "./IL2StandardERC20.sol";23contract L2StandardERC20 is IL2StandardERC20, ERC20 {4 address public l1Token;5 address public l2Bridge;ये दो अतिरिक्त कॉन्फ़िगरेशन पैरामीटर हैं जिनकी हमें आवश्यकता है और ERC-20 को सामान्य रूप से नहीं होती है।
12 /**3 * @param _l2Bridge L2 स्टैंडर्ड ब्रिज का पता।4 * @param _l1Token संबंधित L1 टोकन का पता।5 * @param _name ERC20 नाम।6 * @param _symbol ERC20 प्रतीक।7 */8 constructor(9 address _l2Bridge,10 address _l1Token,11 string memory _name,12 string memory _symbol13 ) ERC20(_name, _symbol) {14 l1Token = _l1Token;15 l2Bridge = _l2Bridge;16 }सभी दिखाएँपहले उस अनुबंध के लिए कंस्ट्रक्टर को कॉल करें जिससे हम इनहेरिट करते हैं (ERC20(_name, _symbol)) और फिर अपने स्वयं के चर सेट करें।
12 modifier onlyL2Bridge() {3 require(msg.sender == l2Bridge, "Only L2 Bridge can mint and burn");4 _;5 }678 // slither-disable-next-line external-function9 function supportsInterface(bytes4 _interfaceId) public pure returns (bool) {10 bytes4 firstSupportedInterface = bytes4(keccak256("supportsInterface(bytes4)")); // ERC16511 bytes4 secondSupportedInterface = IL2StandardERC20.l1Token.selector ^12 IL2StandardERC20.mint.selector ^13 IL2StandardERC20.burn.selector;14 return _interfaceId == firstSupportedInterface || _interfaceId == secondSupportedInterface;15 }सभी दिखाएँयह वह तरीका है जिससे ERC-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-function2 function mint(address _to, uint256 _amount) public virtual onlyL2Bridge {3 _mint(_to, _amount);45 emit Mint(_to, _amount);6 }78 // slither-disable-next-line external-function9 function burn(address _from, uint256 _amount) public virtual onlyL2Bridge {10 _burn(_from, _amount);1112 emit Burn(_from, _amount);13 }14}सभी दिखाएँकेवल L2 ब्रिज को संपत्ति मिंट और बर्न करने की अनुमति है।
_mint और _burn वास्तव में ओपनज़ेपेलिन ERC-20 अनुबंध में परिभाषित हैं।
वह अनुबंध बस उन्हें बाह्य रूप से उजागर नहीं करता है, क्योंकि टोकन को मिंट और बर्न करने की शर्तें उतनी ही विविध हैं जितनी ERC-20 का उपयोग करने के तरीके।
L2 ब्रिज कोड
यह कोड है जो ऑप्टिमिज्म पर ब्रिज चलाता है। इस अनुबंध का स्रोत यहाँ है (opens in a new tab)।
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.9;34/* इंटरफ़ेस इम्पोर्ट */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 समकक्ष के बहुत समान है। दो महत्वपूर्ण अंतर हैं:
- L1 पर आप जमा शुरू करते हैं और निकासी को अंतिम रूप देते हैं। यहां आप निकासी शुरू करते हैं और जमा को अंतिम रूप देते हैं।
- L1 पर ETH और ERC-20 टोकन के बीच अंतर करना आवश्यक है। L2 पर हम दोनों के लिए समान फ़ंक्शन का उपयोग कर सकते हैं क्योंकि आंतरिक रूप से ऑप्टिमिज्म पर ETH शेष राशि को पते 0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000 (opens in a new tab) के साथ एक ERC-20 टोकन के रूप में संभाला जाता है।
1/* लाइब्रेरी इम्पोर्ट */2import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";3import { CrossDomainEnabled } from "../../libraries/bridge/CrossDomainEnabled.sol";4import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";56/* अनुबंध इम्पोर्ट */7import { IL2StandardERC20 } from "../../standards/IL2StandardERC20.sol";89/**10 * @title L2StandardBridge11 * @dev L2 स्टैंडर्ड ब्रिज एक अनुबंध है जो L1 स्टैंडर्ड ब्रिज के साथ मिलकर काम करता है12 * L1 और L2 के बीच ETH और ERC20 संक्रमण को सक्षम करने के लिए।13 * यह अनुबंध नए टोकन के लिए एक मिंटर के रूप में कार्य करता है जब यह L1 स्टैंडर्ड में जमा के बारे में सुनता है14 * ब्रिज।15 * यह अनुबंध निकासी के लिए इच्छित टोकन के बर्नर के रूप में भी कार्य करता है, L1 को सूचित करता है16 * L1 फंड जारी करने के लिए ब्रिज।17 */18contract L2StandardBridge is IL2ERC20Bridge, CrossDomainEnabled {19 /********************************20 * बाहरी अनुबंध संदर्भ *21 ********************************/2223 address public l1TokenBridge;सभी दिखाएँL1 ब्रिज के पते का ट्रैक रखें। ध्यान दें कि L1 समकक्ष के विपरीत, यहाँ हमें इस चर की आवश्यकता है। L1 ब्रिज का पता पहले से ज्ञात नहीं है।
12 /***************3 * कंस्ट्रक्टर *4 ***************/56 /**7 * @param _l2CrossDomainMessenger इस अनुबंध द्वारा उपयोग किया जाने वाला क्रॉस-डोमेन मैसेंजर।8 * @param _l1TokenBridge मुख्य चेन पर तैनात L1 ब्रिज का पता।9 */10 constructor(address _l2CrossDomainMessenger, address _l1TokenBridge)11 CrossDomainEnabled(_l2CrossDomainMessenger)12 {13 l1TokenBridge = _l1TokenBridge;14 }1516 /***************17 * निकासी *18 ***************/1920 /**21 * @inheritdoc IL2ERC20Bridge22 */23 function withdraw(24 address _l2Token,25 uint256 _amount,26 uint32 _l1Gas,27 bytes calldata _data28 ) external virtual {29 _initiateWithdrawal(_l2Token, msg.sender, msg.sender, _amount, _l1Gas, _data);30 }3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function withdrawTo(36 address _l2Token,37 address _to,38 uint256 _amount,39 uint32 _l1Gas,40 bytes calldata _data41 ) external virtual {42 _initiateWithdrawal(_l2Token, msg.sender, _to, _amount, _l1Gas, _data);43 }सभी दिखाएँये दो फ़ंक्शन निकासी शुरू करते हैं। ध्यान दें कि L1 टोकन पता निर्दिष्ट करने की कोई आवश्यकता नहीं है। L2 टोकन से यह अपेक्षा की जाती है कि वे हमें L1 समकक्ष का पता बताएं।
12 /**3 * @dev टोकन को बर्न करके और सूचित करके निकासी के लिए तर्क करता है4 * निकासी के L1 टोकन गेटवे।5 * @param _l2Token L2 टोकन का पता जहां निकासी शुरू की गई है।6 * @param _from L2 पर निकासी को खींचने के लिए खाता।7 * @param _to L1 पर निकासी देने के लिए खाता।8 * @param _amount निकालने के लिए टोकन की राशि।9 * @param _l1Gas अप्रयुक्त, लेकिन संभावित फॉरवर्ड संगतता विचारों के लिए शामिल है।10 * @param _data L1 को अग्रेषित करने के लिए वैकल्पिक डेटा। यह डेटा प्रदान किया गया है11 * पूरी तरह से बाहरी अनुबंधों की सुविधा के रूप में। अधिकतम लागू करने के अलावा12 * लंबाई, ये अनुबंध इसकी सामग्री के बारे में कोई गारंटी नहीं देते हैं।13 */14 function _initiateWithdrawal(15 address _l2Token,16 address _from,17 address _to,18 uint256 _amount,19 uint32 _l1Gas,20 bytes calldata _data21 ) internal {22 // जब एक निकासी शुरू की जाती है, तो हम बाद के L2 को रोकने के लिए निकासीकर्ता के फंड को बर्न करते हैं23 // उपयोग24 // slither-disable-next-line reentrancy-events25 IL2StandardERC20(_l2Token).burn(msg.sender, _amount);सभी दिखाएँध्यान दें कि हम _from पैरामीटर पर निर्भर नहीं हैं, बल्कि msg.sender पर निर्भर हैं, जिसे नकली बनाना बहुत कठिन है (जहां तक मुझे पता है, असंभव है)।
12 // l1TokenBridge.finalizeERC20Withdrawal(_to, _amount) के लिए calldata का निर्माण करें3 // slither-disable-next-line reentrancy-events4 address l1Token = IL2StandardERC20(_l2Token).l1Token();5 bytes memory message;67 if (_l2Token == Lib_PredeployAddresses.OVM_ETH) {L1 पर ETH और ERC-20 के बीच अंतर करना आवश्यक है।
1 message = abi.encodeWithSelector(2 IL1StandardBridge.finalizeETHWithdrawal.selector,3 _from,4 _to,5 _amount,6 _data7 );8 } else {9 message = abi.encodeWithSelector(10 IL1ERC20Bridge.finalizeERC20Withdrawal.selector,11 l1Token,12 _l2Token,13 _from,14 _to,15 _amount,16 _data17 );18 }1920 // L1 ब्रिज तक संदेश भेजें21 // slither-disable-next-line reentrancy-events22 sendCrossDomainMessage(l1TokenBridge, _l1Gas, message);2324 // slither-disable-next-line reentrancy-events25 emit WithdrawalInitiated(l1Token, _l2Token, msg.sender, _to, _amount, _data);26 }2728 /************************************29 * क्रॉस-चेन फ़ंक्शन: जमा करना *30 ************************************/3132 /**33 * @inheritdoc IL2ERC20Bridge34 */35 function finalizeDeposit(36 address _l1Token,37 address _l2Token,38 address _from,39 address _to,40 uint256 _amount,41 bytes calldata _dataसभी दिखाएँयह फ़ंक्शन L1StandardBridge द्वारा कॉल किया जाता है।
1 ) external virtual onlyFromCrossDomainAccount(l1TokenBridge) {सुनिश्चित करें कि संदेश का स्रोत वैध है।
यह महत्वपूर्ण है क्योंकि यह फ़ंक्शन _mint को कॉल करता है और उन टोकन को देने के लिए उपयोग किया जा सकता है जो L1 पर ब्रिज के स्वामित्व वाले टोकन द्वारा कवर नहीं किए गए हैं।
1 // जांचें कि लक्ष्य टोकन अनुपालन कर रहा है और2 // सत्यापित करें कि L1 पर जमा किया गया टोकन यहां L2 जमा किए गए टोकन प्रतिनिधित्व से मेल खाता है3 if (4 // slither-disable-next-line reentrancy-events5 ERC165Checker.supportsInterface(_l2Token, 0x1d1d8b63) &&6 _l1Token == IL2StandardERC20(_l2Token).l1Token()सैनिटी चेक:
- सही इंटरफ़ेस समर्थित है
- L2 ERC-20 अनुबंध का L1 पता टोकन के L1 स्रोत से मेल खाता है
1 ) {2 // जब एक जमा को अंतिम रूप दिया जाता है, तो हम L2 पर खाते में समान राशि3 // टोकन जमा करते हैं।4 // slither-disable-next-line reentrancy-events5 IL2StandardERC20(_l2Token).mint(_to, _amount);6 // slither-disable-next-line reentrancy-events7 emit DepositFinalized(_l1Token, _l2Token, _from, _to, _amount, _data);यदि सैनिटी चेक पास हो जाते हैं, तो जमा को अंतिम रूप दें:
- टोकन मिंट करें
- उपयुक्त इवेंट उत्सर्जित करें
1 } else {2 // या तो L2 टोकन जिसमें जमा किया जा रहा है, उसके L1 टोकन के सही पते के बारे में असहमत है,3 // या सही इंटरफ़ेस का समर्थन नहीं करता है।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 _data9 );1011 // L1 ब्रिज तक संदेश भेजें12 // slither-disable-next-line reentrancy-events13 sendCrossDomainMessage(l1TokenBridge, 0, message);14 // slither-disable-next-line reentrancy-events15 emit DepositFailed(_l1Token, _l2Token, _from, _to, _amount, _data);16 }17 }18}सभी दिखाएँनिष्कर्ष
स्टैंडर्ड ब्रिज संपत्ति हस्तांतरण के लिए सबसे लचीला तंत्र है। हालांकि, क्योंकि यह बहुत सामान्य है, यह हमेशा उपयोग करने के लिए सबसे आसान तंत्र नहीं है। विशेष रूप से निकासी के लिए, अधिकांश यूज़र तृतीय पक्ष ब्रिज (opens in a new tab) का उपयोग करना पसंद करते हैं जो चुनौती अवधि की प्रतीक्षा नहीं करते हैं और निकासी को अंतिम रूप देने के लिए मर्केल प्रूफ की आवश्यकता नहीं होती है।
ये ब्रिज आमतौर पर L1 पर संपत्ति रखकर काम करते हैं, जिसे वे तुरंत एक छोटे से शुल्क (अक्सर एक स्टैंडर्ड ब्रिज निकासी के लिए गैस की लागत से कम) के लिए प्रदान करते हैं। जब ब्रिज (या इसे चलाने वाले लोग) L1 संपत्ति पर कमी का अनुमान लगाते हैं, तो यह L2 से पर्याप्त संपत्ति स्थानांतरित करता है। चूंकि ये बहुत बड़ी निकासी हैं, निकासी लागत एक बड़ी राशि पर परिशोधित होती है और यह बहुत छोटा प्रतिशत है।
उम्मीद है कि इस लेख ने आपको लेयर 2 कैसे काम करता है, और स्पष्ट और सुरक्षित सॉलिडिटी कोड कैसे लिखना है, के बारे में अधिक समझने में मदद की होगी।
मेरे और काम के लिए यहाँ देखें (opens in a new tab)।
पेज का अंतिम अपडेट: 3 मार्च 2026