अनुबंध के आकार की सीमा से निपटने के लिए अनुबंधों का आकार कम करना
सीमा क्यों है?
22 नवंबर, 2016 (एक नए टैब में खुलता है) को Spurious Dragon हार्ड-फ़ोर्क ने EIP-170 (एक नए टैब में खुलता है) पेश किया, जिसने 24.576 kb की स्मार्ट अनुबंध आकार सीमा जोड़ दी। एक Solidity डेवलपर के रूप में आपके लिए इसका मतलब यह है कि जब आप अपने अनुबंध में अधिक से अधिक कार्यक्षमता जोड़ते हैं, तो किसी बिंदु पर आप सीमा तक पहुंच जाएंगे और तैनाती करते समय यह त्रुटि दिखाई देगी:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
यह सीमा डिनायल-ऑफ़-सर्विस (DOS) हमलों को रोकने के लिए पेश की गई थी। गैस के लिहाज से किसी अनुबंध को कॉल करना अपेक्षाकृत सस्ता है। हालाँकि, इथेरियम नोड्स के लिए अनुबंध कॉल का प्रभाव कॉल किए गए अनुबंध कोड के आकार (डिस्क से कोड पढ़ना, कोड की प्री-प्रोसेसिंग करना, मर्कल प्रमाण में डेटा जोड़ना) के आधार पर असंगत रूप से बढ़ जाता है। जब भी आपके पास ऐसी स्थिति होती है जहां हमलावर को दूसरों के लिए बहुत अधिक काम पैदा करने के लिए कम संसाधनों की आवश्यकता होती है, तो DOS हमलों की संभावना बन जाती है।
मूल रूप से यह कम समस्या थी क्योंकि एक प्राकृतिक अनुबंध आकार सीमा ब्लॉक गैस सीमा है। जाहिर है, एक अनुबंध को एक ऐसे लेन-देन के भीतर तैनात किया जाना चाहिए जिसमें अनुबंध का सारा बाइटकोड हो। यदि आप एक ब्लॉक में केवल उस एक लेन-देन को शामिल करते हैं, तो आप उस सारी गैस का उपयोग कर सकते हैं, लेकिन यह अनंत नहीं है। London Upgrade के बाद से, नेटवर्क की मांग के आधार पर ब्लॉक गैस सीमा 15M और 30M इकाइयों के बीच भिन्न होने में सक्षम है।
निम्नलिखित में हम उनके संभावित प्रभाव के क्रम में कुछ तरीकों को देखेंगे। इसे वजन कम करने के संदर्भ में सोचें। किसी के लिए अपने लक्षित वजन (हमारे मामले में 24kb) तक पहुंचने की सबसे अच्छी रणनीति पहले बड़े प्रभाव वाले तरीकों पर ध्यान केंद्रित करना है। ज्यादातर मामलों में केवल अपने आहार को ठीक करने से आप वहां पहुंच जाएंगे, लेकिन कभी-कभी आपको थोड़ा और चाहिए होता है। फिर आप कुछ व्यायाम (मध्यम प्रभाव) या सप्लीमेंट्स (छोटा प्रभाव) भी जोड़ सकते हैं।
बड़ा प्रभाव
अपने अनुबंधों को अलग करें
यह हमेशा आपका पहला दृष्टिकोण होना चाहिए। आप अनुबंध को कई छोटे अनुबंधों में कैसे अलग कर सकते हैं? यह आम तौर पर आपको अपने अनुबंधों के लिए एक अच्छा आर्किटेक्चर तैयार करने के लिए मजबूर करता है। कोड पठनीयता के दृष्टिकोण से हमेशा छोटे अनुबंधों को प्राथमिकता दी जाती है। अनुबंधों को विभाजित करने के लिए, अपने आप से पूछें:
- कौन से फ़ंक्शन एक साथ संबंधित हैं? फ़ंक्शंस का प्रत्येक सेट अपने स्वयं के अनुबंध में सबसे अच्छा हो सकता है।
- किन फ़ंक्शंस को अनुबंध की स्थिति या स्थिति के केवल एक विशिष्ट सबसेट को पढ़ने की आवश्यकता नहीं है?
- क्या आप स्टोरेज और कार्यक्षमता को विभाजित कर सकते हैं?
लाइब्रेरी
कार्यक्षमता कोड को स्टोरेज से दूर ले जाने का एक आसान तरीका लाइब्रेरी (एक नए टैब में खुलता है) का उपयोग करना है। लाइब्रेरी फ़ंक्शंस को इंटरनल (internal) घोषित न करें क्योंकि वे संकलन (compilation) के दौरान सीधे अनुबंध में जोड़ दिए जाएंगे (एक नए टैब में खुलता है)। लेकिन यदि आप पब्लिक (public) फ़ंक्शंस का उपयोग करते हैं, तो वे वास्तव में एक अलग लाइब्रेरी अनुबंध में होंगे। लाइब्रेरी के उपयोग को अधिक सुविधाजनक बनाने के लिए using for (एक नए टैब में खुलता है) पर विचार करें।
प्रॉक्सी
एक अधिक उन्नत रणनीति प्रॉक्सी सिस्टम होगी। लाइब्रेरी बैकग्राउंड में DELEGATECALL का उपयोग करती हैं जो कॉलिंग अनुबंध की स्थिति के साथ किसी अन्य अनुबंध के फ़ंक्शन को निष्पादित करता है। प्रॉक्सी सिस्टम के बारे में अधिक जानने के लिए इस ब्लॉग पोस्ट (एक नए टैब में खुलता है) को देखें। वे आपको अधिक कार्यक्षमता देते हैं, उदाहरण के लिए, वे अपग्रेड करने की क्षमता को सक्षम करते हैं, लेकिन वे बहुत अधिक जटिलता भी जोड़ते हैं। मैं उन्हें केवल अनुबंध के आकार को कम करने के लिए नहीं जोड़ूंगा जब तक कि किसी भी कारण से यह आपका एकमात्र विकल्प न हो।
मध्यम प्रभाव
फ़ंक्शंस हटाएं
यह स्पष्ट होना चाहिए। फ़ंक्शंस अनुबंध के आकार को काफी बढ़ा देते हैं।
- External: अक्सर हम सुविधा के कारणों से बहुत सारे व्यू (view) फ़ंक्शंस जोड़ते हैं। यह तब तक बिल्कुल ठीक है जब तक आप आकार की सीमा तक नहीं पहुंच जाते। फिर आप वास्तव में केवल अत्यंत आवश्यक फ़ंक्शंस को छोड़कर बाकी सभी को हटाने के बारे में सोच सकते हैं।
- Internal: आप इंटरनल/प्राइवेट फ़ंक्शंस को भी हटा सकते हैं और कोड को इनलाइन कर सकते हैं जब तक कि फ़ंक्शन को केवल एक बार कॉल किया जाता है।
अतिरिक्त वेरिएबल्स से बचें
function get(uint id) returns (address,address) {
MyStruct memory myStruct = myStructs[id];
return (myStruct.addr1, myStruct.addr2);
}
function get(uint id) returns (address,address) {
return (myStructs[id].addr1, myStructs[id].addr2);
}
इस तरह का एक साधारण बदलाव 0.28kb का अंतर लाता है। संभावना है कि आप अपने अनुबंधों में कई समान स्थितियां पा सकते हैं और वे वास्तव में महत्वपूर्ण मात्रा में जुड़ सकती हैं।
त्रुटि संदेश को छोटा करें
लंबे रिवर्ट संदेश और विशेष रूप से कई अलग-अलग रिवर्ट संदेश अनुबंध को बड़ा कर सकते हैं। इसके बजाय छोटे त्रुटि कोड का उपयोग करें और उन्हें अपने अनुबंध में डिकोड करें। एक लंबा संदेश बहुत छोटा हो सकता है:
require(msg.sender == owner, "Only the owner of this contract can call this function");
require(msg.sender == owner, "OW1");
त्रुटि संदेशों के बजाय कस्टम त्रुटियों का उपयोग करें
कस्टम त्रुटियों को Solidity 0.8.4 (एक नए टैब में खुलता है) में पेश किया गया है। वे आपके अनुबंधों के आकार को कम करने का एक शानदार तरीका हैं, क्योंकि वे चयनकर्ताओं (selectors) के रूप में ABI-एन्कोडेड होते हैं (ठीक वैसे ही जैसे फ़ंक्शंस होते हैं)।
error Unauthorized();
if (msg.sender != owner) {
revert Unauthorized();
}
ऑप्टिमाइज़र में कम रन वैल्यू पर विचार करें
आप ऑप्टिमाइज़र सेटिंग्स भी बदल सकते हैं। 200 के डिफ़ॉल्ट मान का मतलब है कि यह बाइटकोड को इस तरह से अनुकूलित करने का प्रयास कर रहा है जैसे कि किसी फ़ंक्शन को 200 बार कॉल किया गया हो। यदि आप इसे 1 में बदलते हैं, तो आप मूल रूप से ऑप्टिमाइज़र को प्रत्येक फ़ंक्शन को केवल एक बार चलाने के मामले के लिए अनुकूलित करने के लिए कहते हैं। केवल एक बार चलाने के लिए अनुकूलित फ़ंक्शन का अर्थ है कि यह स्वयं तैनाती के लिए अनुकूलित है। ध्यान रखें कि यह फ़ंक्शंस को चलाने के लिए गैस लागत को बढ़ाता है, इसलिए आप ऐसा नहीं करना चाह सकते हैं।
छोटा प्रभाव
फ़ंक्शंस में स्ट्रक्ट्स (structs) पास करने से बचें
यदि आप ABIEncoderV2 (एक नए टैब में खुलता है) का उपयोग कर रहे हैं, तो यह किसी फ़ंक्शन में स्ट्रक्ट्स (structs) पास न करने में मदद कर सकता है। पैरामीटर को स्ट्रक्ट के रूप में पास करने के बजाय, आवश्यक पैरामीटर सीधे पास करें। इस उदाहरण में हमने एक और 0.1kb बचाया।
function get(uint id) returns (address,address) {
return _get(myStruct);
}
function _get(MyStruct memory myStruct) private view returns(address,address) {
return (myStruct.addr1, myStruct.addr2);
}
function get(uint id) returns(address,address) {
return _get(myStructs[id].addr1, myStructs[id].addr2);
}
function _get(address addr1, address addr2) private view returns(address,address) {
return (addr1, addr2);
}
फ़ंक्शंस और वेरिएबल्स के लिए सही दृश्यता (visibility) घोषित करें
- फ़ंक्शंस या वेरिएबल्स जिन्हें केवल बाहर से कॉल किया जाता है? उन्हें
publicके बजायexternalके रूप में घोषित करें। - फ़ंक्शंस या वेरिएबल्स जिन्हें केवल अनुबंध के भीतर से कॉल किया जाता है? उन्हें
publicके बजायprivateयाinternalके रूप में घोषित करें।
मॉडिफायर्स (modifiers) हटाएं
मॉडिफायर्स, विशेष रूप से जब तीव्रता से उपयोग किए जाते हैं, तो अनुबंध के आकार पर महत्वपूर्ण प्रभाव डाल सकते हैं। उन्हें हटाने पर विचार करें और इसके बजाय फ़ंक्शंस का उपयोग करें।
modifier checkStuff() {}
function doSomething() checkStuff {}
function checkStuff() private {}
function doSomething() { checkStuff(); }
इन सुझावों से आपको अनुबंध के आकार को काफी कम करने में मदद मिलनी चाहिए। एक बार फिर, मैं इस बात पर पर्याप्त जोर नहीं दे सकता, सबसे बड़े प्रभाव के लिए यदि संभव हो तो हमेशा अनुबंधों को विभाजित करने पर ध्यान केंद्रित करें।