यह लेख उन सभी प्रकार की त्रुटियों (errors) का वर्णन करता है जो किसी स्मार्ट कॉन्ट्रैक्ट (smart contract) को कॉल करने पर हो सकती हैं, और Solidity का Try / Catch ब्लॉक उनमें से प्रत्येक पर कैसे प्रतिक्रिया (या प्रतिक्रिया करने में विफल) देता है।
Solidity में Try / Catch कैसे काम करता है, यह समझने के लिए हमें यह समझना होगा कि जब कोई low-level call विफल हो जाता है तो कौन सा डेटा वापस (return) होता है। कंपाइलर (compiler) इस व्यवहार को निर्धारित करता है, न कि एथेरियम वर्चुअल मशीन (EVM)। इसलिए, वैकल्पिक भाषाओं या असेंबली (assembly) में लिखे गए कॉन्ट्रैक्ट्स आवश्यक रूप से यहां बताई गई सभी एरर फॉर्मेटिंग का पालन नहीं करेंगे।
जब किसी बाहरी कॉन्ट्रैक्ट के लिए लो-लेवल कॉल विफल हो जाता है, तो यह एक बूलियन वैल्यू false रिटर्न करता है। यह false इंगित करता है कि कॉल सफलतापूर्वक निष्पादित (execute) नहीं हुआ। कॉल निम्नलिखित मामलों में false रिटर्न कर सकता है:
- कॉल किया गया कॉन्ट्रैक्ट revert हो जाता है
- कॉल किया गया कॉन्ट्रैक्ट कोई अवैध ऑपरेशन (illegal operation) करता है (जैसे शून्य से भाग देना या आउट-ऑफ़-बाउंड्स ऐरे इंडेक्स तक पहुंचना)
- कॉल किया गया कॉन्ट्रैक्ट पूरी गैस (gas) का उपयोग कर लेता है
किसी कॉन्ट्रैक्ट को Self-destructing करने से लो-लेवल कॉल EVM-संगत चेन (EVM-compatible chains) पर false रिटर्न नहीं करता है जो डिप्लॉय किए गए कॉन्ट्रैक्ट्स को सेल्फ-डिस्ट्रक्ट करने की अनुमति देते हैं।
निम्नलिखित अनुभागों में, हम 10 परिदृश्यों (scenarios) की जांच करेंगे जिनके कारण एक लो-लेवल कॉल false रिटर्न कर सकता है, साथ ही वे जो भी रिटर्न डेटा प्रदान कर सकते हैं।
फिर हम जानेंगे कि Try / Catch प्रत्येक स्थिति को कैसे संभालता है (या संभालने में विफल रहता है)।
Part 1: revert के दौरान क्या रिटर्न होता है
1. बिना किसी एरर स्ट्रिंग के revert से क्या रिटर्न होता है?
revert का उपयोग करने का सबसे सरल तरीका revert का कोई कारण दिए बिना है।
contract ContractA {
function mint() external pure {
revert();
}
}
यदि हम उपरोक्त कॉन्ट्रैक्ट (ContractA) को डिप्लॉय करते हैं और दूसरे कॉन्ट्रैक्ट (ContractB) से mint() फंक्शन के लिए इस तरह एक लो-लेवल कॉल करते हैं:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
contract ContractB {
function call_failure(address contractAAddress) external {
(, bytes memory err) = contractAAddress.call(
abi.encodeWithSignature("mint()")
);
console.logBytes(err);
}
}
revert() एरर ट्रिगर हो जाएगी और कोई डेटा रिटर्न नहीं होगा, जैसा कि नीचे दिए गए स्क्रीनशॉट में दिखाया गया है:

ऊपर दी गई छवि में, हम देख सकते हैं कि एरर से रिटर्न डेटा 0x है जो बिना किसी अतिरिक्त डेटा के सिर्फ एक हेक्साडेसिमल नोटेशन है।
2. एक एरर स्ट्रिंग के साथ revert से क्या रिटर्न होता है?
revert का उपयोग करने का एक अन्य तरीका स्ट्रिंग मैसेज प्रदान करना है। यह यह पहचानने में मदद करता है कि आपके कॉन्ट्रैक्ट में ट्रांजैक्शन क्यों विफल रहा।
आइए हमारे पिछले उदाहरण का अनुसरण करते हुए एक स्ट्रिंग के साथ revert को ट्रिगर करें और देखें कि क्या रिटर्न होता है:
contract ContractB {
function mint() external pure {
revert("Unauthorized");
}
}
और कॉलिंग कॉन्ट्रैक्ट होगा:
import "hardhat/console.sol";
contract ContractA {
function call_failure(address contractBAddress) external {
(, bytes memory err) = contractBAddress.call(
abi.encodeWithSignature("mint()")
);
console.logBytes(err); // just so we can see the error data
}
}
यदि हम दोनों कॉन्ट्रैक्ट्स को डिप्लॉय करते हैं और ContractB के कॉन्ट्रैक्ट एड्रेस के साथ ContractA को निष्पादित करते हैं, तो हमें नीचे दिया गया परिणाम मिलना चाहिए:

जब revert को स्ट्रिंग आर्गुमेंट के साथ ट्रिगर किया जाता है, तो यह कॉलर को Error फंक्शन Error(string) की ABI एनकोडिंग रिटर्न करता है।
हमारे revert के लिए रिटर्न डेटा फंक्शन कॉल की ABI encoding Error("Unauthorized") होगा।
इस मामले में, इसमें Error(string) फंक्शन का function selector, स्ट्रिंग का ऑफसेट (offset), लंबाई (length), और हेक्साडेसिमल में एनकोड की गई स्ट्रिंग की सामग्री (content) होगी।

आइए आउटपुट को आगे समझाते हैं:
- सिलेक्टर
08c379a0,keccak256("Error(string)")के पहले चार बाइट्स हैं जहां string कारण स्ट्रिंग को संदर्भित करता है। अगले 96 बाइट्स (3 पंक्तियां) स्ट्रिंगUnauthorizedकी ABI encoding हैं। - पहले 32 बाइट्स स्ट्रिंग की लंबाई के स्थान का ऑफसेट (offset) हैं।
- दूसरे 32 बाइट्स स्ट्रिंग की लंबाई हैं (12 बाइट्स हेक्स में
cके रूप में दर्शाए गए हैं)। - स्ट्रिंग
Unauthorizedकी वास्तविक सामग्री UTF-8 के रूप में बाइट्स556e617574686f72697a6564में एनकोड की गई है।
3. कस्टम revert से क्या रिटर्न होता है?
Solidity 0.8.4 ने error टाइप पेश किया जिसे कस्टम एरर बनाने के लिए revert स्टेटमेंट के साथ उपयोग किया जा सकता है जो पढ़ने योग्य और गैस-कुशल (gas-efficient) दोनों हैं।
कस्टम एरर टाइप बनाने के लिए आप एरर को परिभाषित करने के लिए error कीवर्ड का उपयोग करेंगे, ठीक उसी तरह जैसे आप events को परिभाषित करते हैं:
error Unauthorized();
यदि आपको एरर जानकारी के भाग के रूप में कुछ विवरणों पर जोर देने की आवश्यकता है, तो आप आर्गुमेंट्स के साथ कस्टम एरर को भी परिभाषित कर सकते हैं: error CustomError(arg1, arg2, etc)।
error Unauthorized(address caller);
बिना आर्गुमेंट्स के कस्टम revert
आइए आर्गुमेंट के साथ कस्टम revert के उदाहरण की तुलना बिना आर्गुमेंट वाले से करें:
pragma solidity >=0.8.4;
error Unauthorized();
contract ContractA {
function mint() external pure {
revert Unauthorized();
}
}
उपरोक्त उदाहरण में, हम ट्रांजैक्शन को revert करना चाहते हैं और एरर Unauthorized रिटर्न करना चाहते हैं। हमारा कॉलिंग कॉन्ट्रैक्ट वैसा ही रहेगा:
import "hardhat/console.sol";
contract ContractB {
function call_failure(address contractAAddress) external {
(, bytes memory err) = contractAAddress.call(
abi.encodeWithSignature("mint()")
);
console.logBytes(err); // just so we can see the error data
}
}
कस्टम revert (बिना आर्गुमेंट्स के) कॉलर को केवल Unauthorized एरर का फंक्शन सिलेक्टर ( keccak256("Unauthorized()")) के पहले चार बाइट्स) रिटर्न करेगा, जो 0x82b42900 है।

आर्गुमेंट्स के साथ कस्टम revert
यदि आपके कस्टम revert में आर्गुमेंट्स हैं, तो यह कस्टम एरर फंक्शन कॉल की ABI एनकोडिंग रिटर्न करेगा। यहाँ एक उदाहरण है:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.4;
error Unauthorized(address caller);
contract ContractA {
function mint() external view {
revert Unauthorized(msg.sender);
}
}
कॉलिंग कॉन्ट्रैक्ट वैसा ही रहेगा और एरर का परिणाम इस तरह दिखेगा:

इसमें कस्टम एरर Unauthorized(address) की ABI एनकोडिंग शामिल है। एनकोडिंग में फंक्शन सिलेक्टर और address आर्गुमेंट शामिल है। चूंकि address एक स्टैटिक टाइप है, इसलिए इसकी एनकोडिंग सीधी है।
यहाँ संरचना (structure) है:
- पहले चार बाइट्स फंक्शन सिलेक्टर का प्रतिनिधित्व करते हैं:
0x8e4a23d6 - अगले 32 बाइट्स कॉलर के एड्रेस का प्रतिनिधित्व करते हैं:
0000000000000000000000009c84abe0d64a1a27fc82821f88adae290eab5e07
एक अतिरिक्त जानकारी के रूप में, आप कस्टम error error Error(string) या error Panic(uint256) को परिभाषित नहीं कर सकते हैं क्योंकि वे उन एरर्स के साथ संघर्ष (conflict) करते हैं जो क्रमशः require और assert रिटर्न करते हैं (हम बाद के अनुभाग में assert पर आएंगे)।
4. require स्टेटमेंट के कारण revert से क्या रिटर्न होता है?
require स्टेटमेंट if स्टेटमेंट का उपयोग किए बिना revert को ट्रिगर करने का एक और तरीका है। उदाहरण के लिए, यह लिखने के बजाय:
if (msg.sender != owner) {
revert();
}
आप इस तरह require स्टेटमेंट का उपयोग कर सकते हैं:
require(msg.sender == owner);
जब बिना किसी एरर मैसेज के require(false) को कॉल किया जाता है, तो यह revert() के समान बिना किसी डेटा के ट्रांजैक्शन को revert कर देता है। परिणामी आउटपुट एक खाली डेटा पेलोड (0x) होता है।

एक स्ट्रिंग के साथ revert के समान, जब require(false, "Unauthorized") जैसी स्ट्रिंग के साथ require को ट्रिगर किया जाता है, तो यह एरर फंक्शन Error(string) की ABI एनकोडिंग रिटर्न करता है।
5. require(false, CustomError()) से क्या रिटर्न होता है?
21 मई, 2024 से, require स्टेटमेंट के लिए कस्टम एरर्स जारी की गई हैं; हालाँकि, वर्तमान में उनका उपयोग केवल via-ir के माध्यम से किया जा सकता है। (Solidity टीम का यह video from the Solidity team describing via-ir देखें)।
Solidity में
via-irin Solidity एक कंपाइलेशन पाइपलाइन है जो आपके Solidity कोड को अनुकूलित (optimize) करने के लिए Yul में एक मध्यवर्ती प्रतिनिधित्व (IR) का उपयोग करती है। यह डिफ़ॉल्ट रूप से सक्षम नहीं है, इसलिए आपकोsolcके साथ--via-irफ़्लैग का उपयोग करने या इसे अपने पसंदीदा विकास वातावरण (development environment) में कॉन्फ़िगर करने की आवश्यकता होगी।
Foundry में via-ir सक्षम करना
यदि आप Foundry का उपयोग कर रहे हैं, तो आपको बस इसे इस तरह सक्रिय करने के लिए foundry.toml कॉन्फ़िग फ़ाइल में via-ir को true पर सेट करना होगा:
[profile.default]
…
via-ir = true
Hardhat में via-ir सक्षम करना
HardHat में, अपनी hardhat.config फ़ाइल में इस तरह viaIR:true जोड़ें:
module.exports = {
solidity: {
settings: {
viaIR: true,
},
},
};
Remix में via-ir सक्षम करना
यदि आप Remix का उपयोग करते हैं, तो आपको Advance Compiler Configurations सेटिंग्स में कॉन्फ़िगरेशन फ़ाइल को सक्षम करने की आवश्यकता होगी, जैसा कि नीचे दिए गए स्क्रीनशॉट में दिखाया गया है:

रूट डायरेक्टरी में एक खाली compiler_config.json फ़ाइल बनाएँ। और कॉन्फ़िगरेशन में पथ (path) जोड़ें जैसा कि ऊपर की छवि में दिखाया गया है।
एक बार “Use configuration file” विकल्प सक्षम हो जाने के बाद, नीचे दिखाए अनुसार सेटिंग्स में "viaIR":true को शामिल करने के लिए कॉन्फ़िगरेशन फ़ाइल को अपडेट करें। आपको कुछ लिंट एरर मिल सकती हैं लेकिन आपका कोड सफलतापूर्वक कंपाइल हो जाएगा।

एक बार यह हो जाने के बाद, आप इस तरह require के साथ अपना कस्टम एरर लिख सकते हैं:
require(msg.sender == owner, Unauthorized());
जो इसके समान है:
if (msg.sender != owner) {
revert Unauthorized();
}
और हाँ, यह वही आउटपुट रिटर्न करता है जो कस्टम revert में हमने पहले ही चर्चा की है।
6. assert से क्या रिटर्न होता है?
जब कोई assert स्टेटमेंट विफल हो जाता है, तो यह Panic(uint256) एरर को ट्रिगर करता है। रिटर्न वैल्यू फंक्शन सिलेक्टर ( keccak256(”Panic(uint256)”)) के पहले 4 बाइट्स) और एरर कोड का संयोजन (concatenation) है।
इसे स्पष्ट करने के लिए निम्नलिखित कोड का उपयोग किया जाएगा, contractB में assert पर ध्यान दें:
import "hardhat/console.sol";
contract ContractB {
function mint() external pure {
assert(false); // we will test what this returns
}
}
contract ContractA {
function call_failure(address contractBAddress) external {
(, bytes memory err) = contractBAddress.call(
abi.encodeWithSignature("mint()")
);
console.logBytes(err);
}
}
जब हम कॉन्ट्रैक्ट को डिप्लॉय और निष्पादित करते हैं, तो हमें नीचे दिखाए अनुसार assert एरर मिलेगी:

err में निम्नलिखित डेटा होगा:
0x4e487b71 // <- the function selector
0000000000000000000000000000000000000000000000000000000000000001 // the error code
4e487b71, keccak256("Panic(uint256)") के पहले चार बाइट्स हैं जहां uint256 एरर कोड को संदर्भित करता है। इस मामले में, एरर कोड 1 है। हम अगले अनुभाग में अन्य एरर कोड देखेंगे।
7. किसी अवैध ऑपरेशन (illegal operation) से क्या रिटर्न होता है?
assert स्टेटमेंट की तरह ही, जब शून्य से भाग देना (division by zero), एक खाली ऐरे को पॉप करना, या ऐरे-आउट-ऑफ़-बाउंड्स एरर जैसे अवैध ऑपरेशन होते हैं, तो ट्रांजैक्शन पैनिक (panic) हो जाता है और फंक्शन सिलेक्टर—keccak256("Panic(uint256)") के पहले 4 बाइट्स, और uint256 एरर कोड का एक संयोजन (concatenation) रिटर्न करता है।
यहाँ एक अवैध ऑपरेशन का उदाहरण दिया गया है; ऐरे-आउट-ऑफ़-बाउंड्स — नीचे ContractB में outOfbounds() फंक्शन के numbers ऐरे में केवल 3 एलीमेंट्स हैं।
import "hardhat/console.sol";
contract ContractB {
uint256[] numbers;
constructor() {
numbers.push(1);
numbers.push(2);
numbers.push(3);
}
function outOfbounds(uint256 index) public view returns (uint256) {
return numbers[index];
}
}
contract ContractA {
function call_failure(address contractBAddress) external {
(, bytes memory err) = contractBAddress.call(
abi.encodeWithSignature("outOfbounds(uint256)", 10)
);
console.logBytes(err);
}
}
यदि हम 10वें आइटम तक पहुँचने का प्रयास करते हैं — जो कि निश्चित रूप से मौजूद नहीं है, तो हमें ऐरे-आउट-ऑफ़-बाउंड एरर मिलेगी:

err में होगा:
0x4e487b71 //<- function selector for Panic(uint256)
0000000000000000000000000000000000000000000000000000000000000032 // <-the error code
0x32 आउट-ऑफ़-बाउंड्स ऐरे एरर के लिए एरर कोड है।
यहाँ एक और उदाहरण है, क्या होगा यदि हम शून्य से भाग देने का प्रयास करें?
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract ContractB {
function divide(uint256 a, uint256 b) public pure returns (uint256) {
return a / b;
}
}
…और फिर पैरामीटर्स 10 और 0 के साथ divide फंक्शन को कॉल करना:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
contract ContractA {
function call_failure(address contractBAddress) external {
(, bytes memory err) = contractBAddress.call(
abi.encodeWithSignature("divide(uint256, uint256)", 10, 0)
);
console.logBytes(err);
}
}
परिणाम वही फंक्शन सिलेक्टर होगा, और शून्य से भाग देने के लिए एरर कोड जो 0x12 है।

8. Solidity स्तर बनाम असेंबली स्तर पर शून्य से भाग देने पर क्या रिटर्न होता है?
Solidity स्तर पर शून्य से भाग देने पर 18 (0x12) के एरर कोड के साथ revert ट्रिगर होता है। हालाँकि, असेंबली स्तर पर शून्य से भाग देने पर revert नहीं होता है, इसके बजाय यह 0 रिटर्न करता है। ऐसा इसलिए है क्योंकि कंपाइलर Solidity स्तर पर चेक्स (checks) डालता है, जो असेंबली स्तर पर नहीं किया जाता है।

यदि आप असेंबली स्तर पर भाग (division) का ऑपरेशन कर रहे हैं, तो डिनामिनेटर (हर) की जांच करना सुनिश्चित करें। यदि यह शून्य है, तो ट्रांजैक्शन को रोल बैक करने के लिए revert को ट्रिगर करें।
function divideByZeroInAssembly(uint256 numerator, uint256 denominator)
public
pure
returns (uint256 result)
{
assembly {
if iszero(denominator) {
revert(0, 0)
}
result := div(numerator, denominator)
}
}
लेकिन इस एरर को solidity से शून्य से नियमित भाग (regular division) की तरह नहीं संभाला जाएगा जो डेसिमल में 18 या हेक्स में 0x12 के एरर कोड के साथ पैनिक (panic) करता है।
यदि आप OpenZeppelin का उपयोग करते हैं, तो आप इस तरह अपने कस्टम एरर कोड के साथ पैनिक को ट्रिगर करने के लिए OZ custom Panic utility का लाभ उठा सकते हैं:

इसके साथ, आप Solidity स्तर पर assert के सामान्य व्यवहार का अनुकरण (simulate) कर सकते हैं।
एरर कोड्स
Solidity docs, the following error codes के अनुसार, नीचे दिए गए एरर कोड विभिन्न प्रकार के पैनिक को संदर्भित करते हैं जो हो सकते हैं, जैसा कि नीचे दिए गए स्क्रीनशॉट से पता चलता है:

9. आउट-ऑफ़-गैस (out-of-gas) के दौरान क्या रिटर्न होता है?
लो-लेवल कॉल में आउट-ऑफ़-गैस एरर स्थिति के दौरान, कॉलिंग कॉन्ट्रैक्ट को कुछ भी रिटर्न नहीं होता है। कोई डेटा नहीं, कोई एरर मैसेज नहीं।
contract E {
function outOfGas() external pure {
while (true) {}
}
}
contract C {
function call_outOfGas(address e) external returns (bytes memory err) {
(, err) = e.call{gas: 2300}(abi.encodeWithSignature("outOfGas()"));
}
}
वेरिएबल err खाली होगा। गैस के लिए 63/64 rule for gas के कारण, कॉन्ट्रैक्ट C के पास अभी भी मूल गैस का 1/64वां हिस्सा बचा होगा, इसलिए call_outOfGas फंक्शन में होने वाला ट्रांजैक्शन स्वयं जरूरी नहीं कि आउट-ऑफ़-गैस के कारण revert हो जाए, भले ही आप कॉन्ट्रैक्ट E को सभी उपलब्ध गैस को फॉरवर्ड करने का प्रयास करें।
10. असेंबली revert के दौरान क्या रिटर्न होता है?
Solidity revert की तुलना में असेंबली revert का उपयोग करने से आप गैस के मामले में अधिक कुशलता से एरर डेटा रिटर्न कर सकते हैं।
असेंबली में revert दो पैरामीटर लेता है: एक मेमोरी स्लॉट और बाइट्स में डेटा का आकार:
revert(startingMemorySlot, totalMemorySize)
आप इस बात को पूरी तरह से नियंत्रित करते हैं कि असेंबली revert से कौन सा एरर डेटा रिटर्न होता है। उदाहरण के लिए, हम OpenZeppelin Proxy.sol की तरह रिटर्न किए गए डेटा का कुल मेमोरी आकार निर्धारित करने के लिए returndatasize() का उपयोग करके delegatecall से रिटर्न हुए एरर मैसेज का उपयोग करके revert करना चुन सकते हैं:
function _delegate(address implementation) internal {
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(
gas(),
implementation,
0,
calldatasize(),
0,
0
)
returndatacopy(0, 0, returndatasize())
if iszero(result) {
revert(0, returndatasize())
}
return(0, returndatasize())
}
}
लो लेवल असेंबली का उपयोग करते हुए, आइए असेंबली revert में रिटर्न किए गए डेटा की संरचना (structure) को बेहतर ढंग से समझने के लिए Solidity के revert स्टेटमेंट्स और उनके रिटर्न डेटा का अनुकरण (simulate) करें।
बिना किसी कारण स्ट्रिंग के revert का अनुकरण
Solidity में revert() के समान, revert(0,0) इनलाइन (Inline) असेंबली में समकक्ष (equivalent) है। यह कोई एरर डेटा रिटर्न नहीं करता है क्योंकि प्रारंभिक मेमोरी स्लॉट को 0 के रूप में परिभाषित किया गया है, और इसका डेटा आकार 0 है, जो यह दर्शाता है कि कोई डेटा रिटर्न नहीं किया जाना चाहिए।
contract ContractB {
function revertWithAssembly() external pure {
assembly {
revert(0, 0) // no returned data
}
}
}
एक कारण स्ट्रिंग के साथ revert का अनुकरण
Solidity में एक कारण के साथ Revert — revert(string) में आंतरिक रूप से कई चरण (steps) शामिल हैं:
Error(string)की ABI एनकोडिंग- लंबाई और ऑफसेट जैसे स्ट्रिंग मेटाडेटा को स्टोर करने के लिए मेमोरी आवंटित (allocating) करना
- और वास्तविक स्ट्रिंग के लिए मेमोरी आवंटित करना।
ये सभी चरण गैस लागत (gas costs) बढ़ा सकते हैं।
गैस लागत को अनुकूलित (optimize) करने के लिए, आप असेंबली का उपयोग करके समान कार्यक्षमता प्राप्त कर सकते हैं। यह विधि आवश्यक चरणों और ओपकोड (opcodes) को कम करती है क्योंकि हम जानते हैं और नियंत्रित करते हैं कि डेटा को कैसे संग्रहीत किया जाता है, जबकि अभी भी वही एरर डेटा रिटर्न किया जा रहा है। नीचे दिए गए उदाहरण में, हमने मैन्युअल रूप से मेमोरी में हेरफेर किया और सीधे संग्रहीत किया:
Error(string)का फंक्शन सिलेक्टर — हम कॉन्ट्रैक्ट के बाहर सिलेक्टर प्राप्त कर सकते हैं और बस इसका उपयोग कर सकते हैं। मैंने स्पष्टता के लिए उदाहरण में एनकोडिंग जोड़ी है।- ऑफसेट
- स्ट्रिंग की लंबाई
- वास्तविक स्ट्रिंग
- और एक revert ट्रिगर किया
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract ContractB {
function revertwithAssembly() external pure {
bytes4 selector = bytes4(abi.encodeWithSignature("Error(string)")); //selector with leading zeros
assembly {
mstore(0x00, selector) //- Store the function selector for `Error(string)`
mstore(0x04, 0x20) //- Store the offset to the error message string
mstore(0x24, 0xc) //- Store the length of the error message string
mstore(0x44, "Unauthorized") //- Store the actual error message
revert(0x00, 0x64) //- Trigger the revert revert(StartingMemorySlot, totalMemorySize)
}
}
}
और जब हम इस बाहरी कॉन्ट्रैक्ट से कॉन्ट्रैक्ट को कॉल करते हैं:
import "hardhat/console.sol";
contract ContractA {
function call_failure(address contractBAddress)
external
returns (bytes memory err)
{
(, err) = contractBAddress.call(
abi.encodeWithSignature("revertwithAssembly()")
);
console.logBytes(err);
}
}
परिणाम हेक्साडेसिमल में एनकोड किया गया डेटा होगा:

जो कि वैसा ही है जैसा हमें Solidity revert(string) का उपयोग करते समय मिला था।
कस्टम एरर के साथ revert का अनुकरण
हम आगे गैस बचाने के उद्देश्य से असेंबली के साथ एक कस्टम revert का अनुकरण कर सकते हैं। असेंबली में कस्टम revert, Solidity कस्टम revert की तरह ही फंक्शन सिलेक्टर रिटर्न करता है।
हालाँकि, Solidity के विपरीत, जहाँ कस्टम एरर की पूरी एनकोडिंग होती है, हम उस चरण को समाप्त कर सकते हैं और सीधे सिलेक्टर को स्टोर कर सकते हैं और revert को ट्रिगर कर सकते हैं।
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
contract ContractB {
function customRevertWithAssembly() external pure {
bytes32 selector = bytes32(abi.encodeWithSignature("Unauthorized()"));
assembly {
mstore(0x00, selector) //- Store the function selector for the custom error
revert(0x00, 0x04)
}
}
}
contract ContractA {
function call_failure(address contractBAddress)
external
returns (bytes memory err)
{
(, err) = contractBAddress.call(
abi.encodeWithSignature("customRevertWithAssembly()")
);
console.logBytes(err);
}
}
जब हम इसे रन करते हैं तो हमें रिटर्न वैल्यू के रूप में सिलेक्टर देखना चाहिए जैसा कि नीचे दिखाया गया है:

उन सभी तरीकों का सारांश जिनसे Solidity कॉन्ट्रैक्ट revert हो सकता है
जब कोई ट्रांजैक्शन किसी कारण स्ट्रिंग के साथ require स्टेटमेंट के माध्यम से या एक स्ट्रिंग वाले revert के माध्यम से revert होता है, तो एरर के लिए रिटर्न वैल्यू Error(string) का सिलेक्टर होता है जिसके बाद कारण स्ट्रिंग की ABI एनकोडिंग होती है।
जब कोई ट्रांजैक्शन किसी assert या अवैध ऑपरेशन के कारण revert होता है, तो एरर डेटा Panic(uint256) का सिलेक्टर होता है जिसके बाद uint256 के रूप में ABI एनकोडेड एरर कोड होता है।
एरर डेटा तब खाली होता है जब:
- एक ट्रांजैक्शन बिना किसी कारण स्ट्रिंग के
require()याrevert()स्टेटमेंट के साथ revert होता है - कॉल किया गया कॉन्ट्रैक्ट गैस का उपयोग कर लेता है
- कॉल किया गया कॉन्ट्रैक्ट असेंबली का उपयोग करता है और
revert(0, 0)के साथ revert होता है
Part 2: try/catch प्रत्येक स्थिति को कैसे संभालता है
इस गाइड के पहले भाग में, हमने विभिन्न प्रकार के reverts द्वारा एरर रिटर्न करने के विभिन्न तरीके देखे हैं। अब, आइए देखें कि Solidity में try/catch स्टेटमेंट इनमें से प्रत्येक स्थिति को कैसे संभालता है।
try/catch स्टेटमेंट बाहरी फंक्शन कॉल या इंटरेक्शन के दौरान उत्पन्न होने वाले अपवादों (exceptions) को बिना revert किए और पूरे ट्रांजैक्शन को रोल बैक किए बिना संभालने का एक संरचित तरीका प्रदान करता है। हालाँकि, यदि कोई एरर होती है तो कॉल किए गए कॉन्ट्रैक्ट में स्थिति परिवर्तन (state changes) अभी भी revert हो जाएंगे।
try/catch स्टेटमेंट
यह try/catch स्टेटमेंट की एक विशिष्ट संरचना है। ध्यान दें कि यह उन सभी तरीकों का पैटर्न मैचिंग है जिनसे Solidity revert हो सकता है:
function callContractB() external view {
try functionFromAnotherContract() {
//<-- Handle the success case if needed
} catch Panic(uint256 errorCode) {
//<-- handle Panic errors
} catch Error(string memory reason) {
//<-- handle revert with a reason
} catch (bytes memory lowLevelData) {
//<-- handle every other errors apart from Panic and Error with a reason
}
}
जिन विभिन्न प्रकार के revert पर हमने पहले चर्चा की थी, उन्हें उनके रिटर्न वैल्यू के आधार पर try/catch ब्लॉक के विभिन्न वर्गों में पकड़ा (catch) जा सकता है।
catch Error(string memory reason) ब्लॉक एक कारण स्ट्रिंग के साथ सभी reverts को संभालता है। इसका मतलब है, revert(string) और require(false, “reason”) एरर यहां पकड़ी जाएंगी। ऐसा इसलिए है क्योंकि ट्रिगर होने पर वे एरर्स Error(string) एरर रिटर्न करती हैं।
catch Panic(uint256 errorCode) सभी अवैध ऑपरेशनों को पकड़ेगा, जैसे कि Solidity स्तर पर शून्य से भाग देना, और assert एरर्स क्योंकि वे एरर्स ट्रिगर होने पर Panic(uint256 errorCode) रिटर्न करती हैं।
अंत में, कोई भी अन्य एरर जो Panic या Error रिटर्न नहीं करती है, उसे जेनेरिक catch ब्लॉक catch (bytes memory lowLevelData) में पकड़ा जाएगा, जिसमें कस्टम एरर्स और बिना मैसेज स्ट्रिंग वाले एरर्स शामिल हैं।
यदि आप एरर डेटा में रुचि नहीं रखते हैं तो आप catch{ } ब्लॉक का भी उपयोग कर सकते हैं। और यह कॉल किए गए कॉन्ट्रैक्ट से किसी भी एरर को पकड़ लेगा।
आइए try/catch सिंटैक्स के एक उदाहरण पर नजर डालते हैं। इस सरल उदाहरण में, हम विभिन्न प्रकार की एरर्स का अनुकरण करने का प्रयास करेंगे और उनके एरर रिटर्न वैल्यूज़ के आधार पर उन्हें संभालने के लिए एक try/catch लिखेंगे।
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract ContractB {
error CustomError(uint256 balance);
uint256 public balance = 10;
function decrementBalance() external {
require(balance > 0, "Balance is already zero");
balance -= 1;
}
function revertTest() external view {
if (balance == 9) {
// revert without a message
revert();
}
if (balance == 8) {
uint256 a = 1;
uint256 b = 0;
// This is an illegal operation and should cause a panic (Panic(uint256)) due to division by zero
a / b;
}
if (balance == 7) {
// revert with a message
revert("not allowed");
}
if (balance == 6) {
// revert with a message
revert CustomError(100);
}
}
}
इन एरर्स को संभालने के लिए हमारा try/catch ब्लॉक कॉल इस तरह दिखेगा:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
import {ContractB} from "contracts/revert/contractB.sol";
contract ContractA {
event Errorhandled(uint256 balance);
ContractB public contractB;
constructor(address contractBAddress) {
contractB = ContractB(contractBAddress);
}
function callContractB() external view {
try contractB.revertTest() {
// Handle the success case if needed
} catch Panic(uint256 errorCode) {
// handle illegal operation and `assert` errors
console.log("error occurred with this error code: ", errorCode);
} catch Error(string memory reason) {
// handle revert with a reason
console.log("error occured with this reason: ", reason);
} catch (bytes memory lowLevelData) {
// revert without a message
if (lowLevelData.length == 0) {
console.log("revert without a message occured");
}
// Decode the error data to check if it's the custom error
if ( bytes4(abi.encodeWithSignature("CustomError(uint256)")) == bytes4(lowLevelData)
) {
// handle custom error
console.log("CustomError occured here");
}
}
}
}
प्रत्येक catch ब्लॉक में, हमने विभिन्न प्रकार की एरर के प्रकारों को संभालने का अनुकरण किया है। बेहतर समझ के लिए, मैंने यह समझाने के लिए टिप्पणियां (comments) जोड़ी हैं कि प्रत्येक ब्लॉक में क्या हो रहा है।
ध्यान दें कि कस्टम एरर्स के लिए कोई catch ब्लॉक नहीं है जैसे catch CustomError {}। इसके बजाय, हम इसे एक मैन्युअल प्रक्रिया के साथ जेनेरिक catch-all ब्लॉक में संभालते हैं क्योंकि अभी तक कस्टम एरर्स को डिकोड करने का कोई आधिकारिक तरीका नहीं है।
इसके बारे में एक open issue भी है जिसमें बहुत सारे सुझाव दिए गए हैं कि आप सिलेक्टर से मिलान करने के लिए अंतिम catch ब्लॉक में if स्टेटमेंट्स जोड़कर इसे कैसे हैक कर सकते हैं। try/catch release blog में उल्लेख किया गया है कि भविष्य में कस्टम एरर्स को ठीक से संभालने के लिए try/catch स्टेटमेंट में सुधार करने की योजना है।

हमारे मामले में, हमने जांच की कि क्या लो-लेवल एरर डेटा एक विशिष्ट कस्टम एरर सिग्नेचर से मेल खाता है जिसे हम पकड़ने (catch) का प्रयास कर रहे हैं।
if (bytes4(abi.encodeWithSignature("CustomError(uint256)")) == bytes4(lowLevelData)
){}
वे कौन से परिदृश्य (scenarios) हैं जहाँ try/catch आपकी एरर्स को संभालने में विफल रहेगा?
चूंकि try / catch सिंटैक्स केवल बाहरी कॉन्ट्रैक्ट्स से एरर्स को पकड़ता है:
1) try या catch ब्लॉक (कॉलिंग कॉन्ट्रैक्ट में) के अंदर होने वाली कोई भी एरर नहीं पकड़ी जाएगी।
उदाहरण के लिए, इस छवि में से कोई भी reverts नहीं पकड़ा जाएगा (run via remix):

2) यदि कॉन्ट्रैक्ट में सही प्रकार का “catch” नहीं है
उदाहरण के लिए, यदि कॉन्ट्रैक्ट पैनिक के साथ revert होता है, लेकिन इसमें केवल एक एरर catch ब्लॉक है और कोई सामान्य catch ब्लॉक नहीं है, जैसा कि नीचे दिए गए स्क्रीनशॉट में दिखाया गया है (run via Remix):

3) Revert जब कोई इंटरफ़ेस रिटर्न डेटा की अपेक्षा करता है लेकिन कोई प्रदान नहीं किया जाता है
यदि दूसरे कॉन्ट्रैक्ट को कॉल परिभाषित करने वाला इंटरफ़ेस रिटर्न डेटा की अपेक्षा करता है और कॉन्ट्रैक्ट कोई डेटा रिटर्न नहीं करता है, या इसे अनपेक्षित प्रारूप (unexpected format) में रिटर्न करता है, तो पूरा ट्रांजैक्शन revert हो जाएगा और पकड़ा (caught) नहीं जाएगा, जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है (run via Remix):

Solidity में try/catch के साथ समस्याएँ
try/catch के साथ समस्याएं बहुत सारे प्रस्तावों के साथ topic of discussion का विषय रही हैं और हमने इस गाइड के दौरान उनमें से कुछ को देखा है।
चर्चा में हाइलाइट की गई समस्याओं में शामिल हैं:
सिंटैक्स द्वारा निर्मित गलत उम्मीदें
यह गलत धारणा है कि try/catch सिंटैक्स Solidity में अन्य भाषाओं की तरह काम करता है, जैसा कि हमने पहले ही देखा है, यह समान नहीं है। उदाहरण के लिए, आप उम्मीद करेंगे कि निम्नलिखित कोड प्रवाह को काम करना चाहिए।
try <expression> {
revert();
} catch {
// also handle the revert in the try block
}
लेकिन यह उम्मीद के मुताबिक काम नहीं करेगा। catch ब्लॉक revert को नहीं पकड़ेगा। यह पूरे ट्रांजैक्शन को समाप्त कर देगा।
कंपाइलर-जनरेटेड चेक्स में reverts को संभालने के लिए तंत्र (mechanism) का अभाव
जब हम दूसरे कॉन्ट्रैक्ट से किसी फंक्शन को high-level calls करते हैं, तो Solidity कंपाइलर कॉल किए गए कॉन्ट्रैक्ट पर कई जांच (checks) करता है, जैसे:
- टारगेट कॉन्ट्रैक्ट के
extcodesizeकी जांच करना ताकि यह पता लगाया जा सके if the target is a contract। यदि एड्रेस एक कॉन्ट्रैक्ट नहीं है तो यह विफल हो जाता है। returndatasizeकी जांच करना — यदि विधि से कुछ डेटा रिटर्न करने की उम्मीद है, तो यह सत्यापित करता है किreturndatasizeखाली नहीं है। यदि रिटर्न वैल्यूज़ हैं, तो यह उन्हें डिकोड करता है और मान्य करता है कि वे सही ढंग से एन्कोड किए गए थे।- यह एनकोडिंग और डिकोडिंग जांच भी करता है। कॉलर रिटर्न किए गए डेटा को ABI-डिकोड करने का भी प्रयास करेगा, और यदि डेटा विकृत (malformed) या गैर-मौजूद है तो revert कर देगा।
यदि इनमें से कोई भी जांच विफल हो जाती है, तो try/catch सिंटैक्स एरर को नहीं पकड़ेगा।
गायब विशेषताएं (Missing features)
इसके अलावा, catch Panic(uint256 errorCode) और catch Error(string memory reason) के साथ एक ऐसी सुविधा होना जो आपको catch CustomError() जैसी कस्टम एरर्स को पकड़ने की अनुमति देती है, एक ऐसी विशेषता है जो स्वाभाविक रूप से try/catch सिंटैक्स में अपेक्षित है। हालाँकि, उन एरर मामलों के लिए कोई सिंटैक्स नहीं है, उन्हें catch ब्लॉक में मैन्युअल रूप से संभाला जाना चाहिए।
Solidity में try/catch समस्याओं के लिए सुझाये गए समाधान
proposal discussion से जिसका हमने पहले उल्लेख किया था, यह सुझाए गए समाधानों का एक संक्षिप्त सारांश है जो इस लेखन के समय लागू (implemented) नहीं हैं:
- अतिरिक्त सुविधाओं के साथ
try/catchसिंटैक्स का विस्तार करना जो स्पष्ट रूप से आपके द्वारा संभाली जा रही एरर के प्रकार को परिभाषित करता है। उदाहरण के लिए,internalcatch कंपाइलर द्वारा जोड़े गए अतिरिक्त चेक्स द्वारा ट्रिगर किए गए स्थानीय reverts को संभालेगा (यह अभी भी उसी कॉन्ट्रैक्ट के भीतर ट्रिगर किए गए reverts को नहीं पकड़ेगा), जबकिexternalcatch मौजूदा catch कार्यान्वयन (implementation) के साथ काम करना जारी रखेगा। - स्थानीय reverts के लिए नए catch क्लॉज़ (clauses) जोड़ना जैसे
- catch NoContract {}
- catch DecodingFailure {}
- catch Other {}
tryCall()औरmatch— इस सुविधा से बाहरी फंक्शन पर पैटर्न मैच चलाने की उम्मीद है और आपको परिणाम और एरर के प्रकार के आधार पर मैच कंस्ट्रक्ट के विभिन्न आर्म्स (arms) में विभिन्न एरर्स को संभालने की अनुमति देता है। यहाँproposalका एक उदाहरण दिया गया है:
import { tryCall } from "std/errors";
error MyError(string);
match tryCall(token.transfer, (exampleAddress, 100)) {
CallSuccess(transferSuccessful) => {
...
}
CallFailure(MyError(reason)) => {
...
}
NotAContract => {
...
}
DecodingFailure(errorCode) => {
...
}
}
यह प्रस्ताव फरवरी 2023 में पोस्ट किया गया था। इसलिए, हम उस दृष्टिकोण की प्रतीक्षा कर रहे हैं जो जीतता है और भविष्य में लागू होता है।
निष्कर्ष (Conclusion)
जब कोई Solidity कॉन्ट्रैक्ट revert होता है, तो वह ABI-एनकोडेड Error(string), Panic(uint256), 4-बाइट कस्टम एरर, या बिल्कुल कुछ नहीं रिटर्न कर सकता है। Try-catch में Error(string), Panic(uint256) और एक सामान्य catch को संभालने के लिए कैच (catches) होते हैं। यह मूल रूप से (natively) कस्टम एरर्स को नहीं संभाल सकता है।
Try catch विफल हो जाता है यदि कॉलर revert हो जाता है या कॉल किया गया (callee) डेटा ऐसे प्रारूप (format) में रिटर्न करता है जिसकी कॉलर को उम्मीद नहीं है (जैसे कि खाली या विकृत (malformed) डेटा को पार्स करने का प्रयास करना)।
लेखक (Authorship)
यह लेख Eze Sunday द्वारा RareSkills के सहयोग से लिखा गया था।
मूल रूप से 25 जुलाई, 2024 को प्रकाशित