Solidity में एक contract अन्य contracts को दो तरीकों से कॉल कर सकता है: contract interface के माध्यम से, जिसे high-level call माना जाता है, या call मेथड का उपयोग करके, जो एक low-level दृष्टिकोण है।
भले ही दोनों तरीके CALL opcode का उपयोग करते हैं, Solidity उन्हें अलग तरह से हैंडल करता है।
इस लेख में, हम इन दोनों की तुलना करेंगे: क्यों एक low-level call कभी revert नहीं होता है, जबकि एक high-level call revert हो सकता है, और क्यों एक empty address पर low-level call को सफल माना जाता है, जबकि एक non-existent contract को कॉल करने पर high-level call revert हो जाता है।
एक low-level call (या delegatecall) कभी revert क्यों नहीं होता है जबकि contract interface के माध्यम से किया गया कॉल revert हो सकता है
यह समझाने से पहले कि ऐसा क्यों होता है, मुझे Solidity documentation का हवाला देने दें जो इस मुद्दे को संबोधित करता है।
जब किसी sub-call में exceptions होते हैं, तो वे स्वचालित रूप से “bubble up” (यानी, exceptions फिर से throw किए जाते हैं) होते हैं जब तक कि उन्हें try/catch statement में catch न किया जाए। इस नियम के अपवाद send और low-level functions call, delegatecalll और staticcall हैं: exception के मामले में वे “bubbling up” के बजाय अपने पहले return value के रूप में false return करते हैं।
व्यवहार की तुलना करने के लिए हम नीचे एक high-level call और एक low-level call दोनों दिखा रहे हैं। मैं नीचे दिए गए उदाहरण में call मेथड का उपयोग करूंगा, लेकिन इन्हीं सिद्धांतों को delegatecall तक भी बढ़ाया जा सकता है।
Caller दो तरीकों से Called में ops() को कॉल कर सकता है। ध्यान दें कि ops() हमेशा revert होता है:
pragma solidity ^0.8.0;
contract Caller {
// first call to ops()
function callByCall(address _address) public returns (bool success) {
(success, ) = _address.call(abi.encodeWithSignature("ops()"));
}
// second call to ops()
function callByInterface(address _address) public {
Called called = Called(_address);
called.ops();
}
}
contract Called {
// ops() always reverts
function ops() public {
revert();
}
}
भले ही दोनों तरीकों का उपयोग एक ही फ़ंक्शन को कॉल करने के लिए किया जाता है, और दोनों तरीके opcode CALL का उपयोग करते हैं, solidity compiler विफलता के मामलों को अलग-अलग तरीकों से हैंडल करने के लिए bytecode जनरेट करता है। Caller contract के भीतर दोनों फ़ंक्शंस को execute करने से पता चलेगा कि Caller.callByInterface revert हो जाएगा, जबकि Caller.callByCall नहीं होगा।
EVM स्तर पर, CALL opcode एक boolean रिटर्न करता है जो यह दर्शाता है कि कॉल सफल रहा या नहीं, और इस रिटर्न को स्टैक पर रखता है। opcode स्वयं revert को ट्रिगर नहीं करता है।
जब कॉल contract के interface के माध्यम से किया जाता है, तो Solidity हमारे लिए इस return value को हैंडल करता है। यह स्पष्ट रूप से जांचता है कि क्या return value false है और यदि कॉल को try/catch ब्लॉक के भीतर नहीं किया गया था, तो यह एक revert आरंभ करता है।
हालाँकि, low-level calls का उपयोग करते समय, हमें इस return boolean को मैन्युअल रूप से हैंडल करना होता है और यदि आवश्यक हो तो स्पष्ट रूप से एक revert ट्रिगर करना होता है।
contract Caller {
//...
function callByCall(address address) public returns (bool success) {
(success, ) = address.call(abi.encodeWithSignature("ops()"));
if (!success) {
revert("Something went wront");
}
}
//...
}
एक high-level call और एक low-level call के बीच का यह अंतर नीचे दिए गए चित्र में दर्शाया गया है।

एक empty address को कॉल करते समय call और call by interface के बीच का अंतर
Solidity का low-level call मेथड यह सत्यापित करने के लिए कोई पूर्व जांच (prior check) नहीं करता है कि कॉल किया गया address किसी contract का है या नहीं। Contract EXTCODESIZE का उपयोग करके यह जांच सकता है कि address एक smart contract है या नहीं, जो कि परदे के पीछे address.code.length के लिए opcode है। यदि size शून्य है, तो यह दर्शाता है कि उस address पर कोई contract डिप्लॉय नहीं किया गया है। हालाँकि, call मेथड इस जांच को शामिल नहीं करता है; यह परवाह किए बिना सीधे CALL opcode को execute करता है।
Interface का उपयोग करते समय, यह लक्ष्य (target) के code size की जांच करता है। दूसरे शब्दों में, callByInterface फ़ंक्शन के लिए जनरेट किए गए bytecode में, CALL opcode को execute करने से पहले निर्दिष्ट address पर EXTCODESIZE opcode को execute किया जाता है। यदि EXTCODESIZE द्वारा रिटर्न किया गया size शून्य है, जो यह दर्शाता है कि उस address पर कोई contract नहीं है, तो फ़ंक्शन CALL opcode को execute करने से पहले revert हो जाता है। यह स्पष्ट करता है कि क्यों callByInterface फ़ंक्शन revert हो जाता है यदि इसे एक non-existent contract address के साथ execute किया जाता है, जबकि callByCall नहीं होता है।
एक low-level call और एक high-level call एक empty contract के साथ कैसे इंटरैक्ट करते हैं, इसके बीच का यह अंतर नीचे दर्शाया गया है।

मूल रूप से, एक execution revert हो सकता है यदि यह REVERT opcode का सामना करता है, गैस (gas) खत्म हो जाती है, या शून्य से विभाजित (dividing by zero) करने जैसी किसी वर्जित (prohibited) चीज़ का प्रयास करता है। जब किसी empty address पर कॉल किया जाता है, तो उपरोक्त में से कोई भी स्थिति नहीं हो सकती है।
RareSkills के साथ और जानें
यदि आप Solidity में नए हैं तो हमारा मुफ्त Solidity course देखें। यदि आप अधिक अनुभवी Solidity प्रोग्रामर हैं, तो कृपया हमारा उन्नत Solidity bootcamp देखें।
लेखक
यह लेख João Paulo Morais द्वारा RareSkills के सहयोग से लिखा गया था।
मूल रूप से 1 मई, 2024 को प्रकाशित