यार, जब मैं खुद hackathons में रातें जागकर कोडिंग किया करता था... अरे हाँ, रुको, कौन सी रातें, हम तो सर्वर रैक्स के नीचे टेबल के नीचे बस तीन घंटे ही सोते थे, तब मुझे ऐसा लगता था कि स्मार्ट कॉन्ट्रैक्ट्स की बेसिक कमियों (vulnerabilities) को तो कोई भी स्टैटिक एनालाइज़र चुटकियों में पकड़ लेगा। इसमें मुश्किल क्या है? Reentrancy (री-एंट्रेंसी अटैक) तो भाई एकदम क्लासिक चीज़ है, इस पर हर दूसरे बंदे ने लिखा है। Checks-Effects-Interactions पैटर्न, अलग-अलग मॉडिफायर्स... और फिर कुछ महीने पहले हमारी टीम में एक नया जूनियर जॉइन किया और एकदम शान से बोलता है: «मुझे ChatGPT ने एक दम परफेक्ट स्टेकिंग कॉन्ट्रैक्ट लिखकर दिया है, मैंने चेक कर लिया, एकदम सेफ है!»
भाई, मैं तो बस अपनी मैकेनिकल कीबोर्ड पर कॉफी गिराते-गिराते बचा।
चलो एकदम सीधी बात करते हैं: आज की मॉडर्न LLMs गजब का सॉलिडिटी (Solidity) कोड लिखती हैं। सिंटैक्स एकदम चमकता हुआ, इम्पोर्ट्स एकदम साफ-सुथरे, और OpenZeppelin तो ऐसे कनेक्ट होता है जैसे लेटेस्ट ट्रेंड हो। लेकिन जब बात आती है स्टेट डिस्ट्रीब्यूशन की उस बारीक लॉजिक की, तो यह न्यूरल नेटवर्क पूरे आत्मविश्वास के साथ — एकदम ढीठ और पक्के कॉन्फिडेंस के साथ — ऐसी भयंकर खामियां जनरेट कर देता है जो बाद में ऑडिट्स के दौरान लाखों डॉलर का चूना लगा जाती हैं। आइए ऐसे तीन अनसुने केसेज पर नजर डालते हैं जहां ChatGPT पूरी तरह मुंह के बल गिर जाता है, जबकि वो आपको यकीन दिला रहा होता है कि आपका कोड CertiK के ऑडिट को हंसते-हंसते पास कर लेगा।
केस #1: ब्लॉक रिवार्ड्स और छुपे हुए बाहरी कॉल वाला असिंक्रोनस स्टेकिंग
सुनो भाई, मान लो आपने AI से एक ऐसा कॉन्ट्रैक्ट लिखने को कहा जो पूल में बिताए गए समय के हिसाब से टोकन बांटे। मॉडल क्या करता है? जहाँ भी उसे यूजर को फंड ट्रांसफर करने का दिखता है, वो ईमानदारी से वहां nonReentrant मॉडिफायर चिपका देता है। लेकिन वो आर्किटेक्चर की एक बहुत बड़ी बारीकी भूल जाता है।
जरा इस कोड स्निपेट को देखो जिसे मैंने उसी जूनियर वाले कांड के बाद एक रियल कोड-ревью से निकाला है:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IERC20Mintable {
function mint(address to, uint256 amount) external;
}
contract SecureStakingVault {
IERC20Mintable public rewardToken;
mapping(address => uint256) public balances;
mapping(address => uint256) public lastUpdateBlock;
uint256 public rewardRatePerBlock = 10 * 10**18;
bool private locked;
// प्रोटेक्ट कर दिया ना भाई! अब और क्या चाहिए, ओ भाई साहब महा-पारानॉइड?
modifier noReentry() {
require(!locked, "LOCKED");
locked = true;
_;
locked = false;
}
constructor(address _token) {
rewardToken = IERC20Mintable(_token);
}
function stake() external payable {
require(msg.value > 0, "Zero stake");
balances[msg.sender] += msg.value;
lastUpdateBlock[msg.sender] = block.number;
}
// ChatGPT का वही मास्टरपीस जो देखने में एकदम सुरक्षित लगता है
function harvestAndUnstake(uint256 _amount) external noReentry {
require(balances[msg.sender] >= _amount, "Insufficient balance");
uint256 blocksPassed = block.number - lastUpdateBlock[msg.sender];
uint256 reward = blocksPassed * rewardRatePerBlock * _amount / balances[msg.sender];
// ध्यान दें: स्टेट अपडेट बाहरी mint कॉल से पहले हो रहा है
balances[msg.sender] -= _amount;
lastUpdateBlock[msg.sender] = block.number;
// रिवार्ड मिंटिंग से टोकन कंस्ट्रक्टर या हुक ट्रिगर होता है, और फिर... सरप्राइज!
rewardToken.mint(msg.sender, reward);
(bool success, ) = msg.sender.call{value: _amount}("");
require(success, "Transfer failed");
}
}यहाँ झोल कहाँ है? मॉडल पूरी तरह से इस गलतफहमी में रहता है कि चूंकि rewardToken.mint() से पहले बैलेंस वेरिएबल्स रीसेट कर दिए गए हैं, इसलिए अटैक मुमकिन ही नहीं है। लेकिन rewardToken एक कस्टम ERC-20 है, जिसके मिंट कॉन्ट्रैक्ट में _beforeTokenTransfer हुक या स्टैंडर्ड ERC-777 जैसा कॉलसैक (callback) मौजूद है (अगर टोकन कॉम्प्लेक्स या प्रॉक्सीड है)। जैसे ही mint कॉल होता है, कंट्रोल यूजर तक ETH पहुँचने से पहले ही किसी तीसरे पक्ष के कोड में कूद जाता है — और वो भी उस एक्टिव सेशन के दौरान जब पूल की स्टेट पहले ही एकतरफा बदल चुकी होती है और बाकी यूजर्स के लिए रिवार्ड्स का कैलकुलेशन पूरी तरह हिल चुका होता है। यह नेक्स्ट-जेन क्रॉस-कॉन्ट्रैक्ट री-एंट्रेंसी है जिसके बारे में स्टैंडर्ड किताबें खामोश रहती हैं।
केस #2: लिक्विडिटी ओरैक्ल्स के जरिए क्रॉस-फंक्शनल हाइजैकिंग
मॉडर्न एक्सप्लॉइट्स की चालाकी इसी में है कि री-एंट्रेंसी अक्सर किसी एक सिंगल फंक्शन के अंदर नहीं छिपी होती, बल्कि उन दो एकदम अलग एंट्री पॉइंट्स के बीच बैठी होती है जो एक कॉमन मैपिंग को शेयर कर रहे होते हैं। AI को लॉजिक को "क्लीन" मॉड्यूल्स में बांटना बहुत पसंद है, और इस चक्कर में वो ग्लोबल इनवेरिएंट के चेंज होने के ऑर्डर को पूरी तरह नजरअंदाज कर देता है।
- कमजोरी का पैरामीटर: स्टैटिक एनालाइज़र का मूल्यांकन
- वास्तविक स्थिति: मॉडिफायर्स
- मौजूद हैं (nonReentrant): क्रॉस-फंक्शन कॉल के खिलाफ पूरी तरह बेकार
- ऑपरेशंस का क्रम: Checks-Effects स्थानीय रूप से पालन किए जाते हैं
- प्रोटोकॉल का ग्लोबल बैलेंस टूटा: ChatGPT की प्रतिक्रिया
- «कोड री-एंट्रेंसी से पूरी तरह सुरक्षित है»: क्रॉस-स्टेट मैनिपुलेशन को जाने देता है
केस #3: फ्लोटिंग ड्यूरेशन कॉन्ट्रैक्ट्स और ERC-4626 वॉल्ट्स
ChatGPT को यील्ड वॉल्ट्स (ERC-4626) के लिए OpenZeppelin के स्टैंडर्ड टेंपलेट्स से पागलों वाला प्यार है। यह उनके रेडीमेड उदाहरण उठाता है, उसमें शेयर कैलकुलेशन का अपना कस्टम गणित जोड़ता है, और ऐसा कोड थमा देता है जो पहली बार में ही कंपाइल हो जाता है। बस दिक्कत यह है कि शेयर्स को एसेट्स में कन्वर्ट करने वाला फंक्शन (convertToAssets) एक इंटरमीडिएट कॉन्ट्रैक्ट के जरिए कुछ खास शर्तों पर कॉल होने पर, वर्चुअल लिक्विडिटी की गणना के दौरान मल्टी-लेवल कंट्रोल रिटर्न को ट्रिगर करने की छूट दे देता है।
तीन हफ्ते पहले मैं खुद आधी रात तक टेस्टनेट पर एक बग डीबग कर रहा था, जब एक सिमुलेशन बॉट ने ठीक इसी पैटर्न का इस्तेमाल करके पूल की सारी लिक्विडिटी साफ़ कर दी थी। न्यूरल नेटवर्क सलाह दे रहा था: «प्रिसिजन के लिए मल्टीप्लायर स्केलिंग का इस्तेमाल करें»। और अंत में हाथ क्या लगा? एक क्लासिक read-only reentrancy, जहाँ कोई बाहरी सिस्टम डिपॉजिट प्रोसेस के बीचों-बीच वॉल्ट की उस इंटरमीडिएट और अभी तक कमिट न की गई स्टेट को पढ़ लेता है।
आइए इस अजीबोगریब और बेतुके नाटक में अपनी गोताखोरी जारी रखें, जो भाषा मॉडल हमें तब देते हैं जब हम उनसे एक "सरल और सुरक्षित स्मार्ट कॉन्ट्रैक्ट" लिखने के लिए कहते हैं।
क्या आप जानते हैं कि एक एक्स-सिक्योरिटी ऑडिटर के तौर पर सबसे मजेदार बात क्या होती है? यह देखना कि डेवलपर्स खुद ChatGPT के कमेंट्स पर कितनी आँख मूंद कर भरोसा करते हैं। एआई ऊपर एक लाइन लिख देता है जैसे // Safe against reentrancy attacks thanks to Checks-Effects-Interactions — और बस, बंदे का क्रिटिकल थिंकिंग पूरी तरह बंद हो जाता है। आँखें बंद, दिमाग रेस्ट मोड में, और फिर मेननेट पर जोरदार आतिशबाजी होती है।
आइए ऐसे ही दो और खतरनाक उदाहरणों को डिकोड करते हैं जिन्हें एआई जिद पकड़कर सुरक्षा का गोल्ड स्टैंडर्ड मानता है, जबकि असलियत में वे चुटकी बजाते ही क्रैक हो जाते हैं।
केस नंबर 4: फ्लैश लोन के जरिए रीपेमेंट वाला मल्टी-स्टेप आर्बिट्रेज
यह एक क्लासिक प्रोब्लम है जो अक्सर हैकथॉनों में दी जाती है या जूनियर्स को थमा दी जाती है: एक ऐसा कॉन्ट्रैक्ट लिखना जो फ्लैश क्रिप्टो ले, उसे DEX के जरिए घुमाए, प्रॉफिट समेटे, मूलधन वापस करे और लिक्विडिटी पूल में फीस दर्ज कर ले। ChatGPT क्या करता है? वह ट्रांजैक्शन की शुरुआत और अंत में बैलेंस चेक करने के लिए बड़े करीने से require चेक लगा देता है, यह पूरी तरह मानते हुए कि अगर डेल्टा मैच हो रहा है, तो सिस्टम पूरी तरह सेफ है।
लेकिन शैतान, हमेशा की तरह, uniswapV2Call कॉलबैक फंक्शन (या दूसरे प्रोटोकॉल्स के ऐसे ही इंटरफेस) को कॉल करने की बारीकियों में छिपा होता है।
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface IUniswapV2Pair {
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}
contract AIArbitrageBotHolder {
address private immutable owner;
bool private unlocked = true;
// एआई को लगता है कि पूरी लॉजिक को सुरक्षित रखने के लिए यह बैरियर काफी है
modifier lock() {
require(unlocked, "LOCKED");
unlocked = false;
_;
unlocked = true;
}
constructor() {
owner = msg.sender;
}
// न्यूरल नेटवर्क के हिसाब से वही "सुरक्षित" मेथड
function executeFlashLoan(address _pair, uint256 _amountA, bytes calldata _data) external lock {
// पूल से लिक्विडिटी की मांग
IUniswapV2Pair(_pair).swap(_amountA, 0, address(this), _data);
}
// कॉलबैक, जहाँ पूल कंट्रोल वापस लौटाता है
function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
// भयंकर गलती: कोड को "साफ-सुथरा" दिखाने के चक्कर में एआई अक्सर msg.sender चेक करना भूल जाता है
// वॉयला, यहाँ कोई भी रैंडम कॉन्ट्रैक्ट आकर रीएंट्रेंसी लूप ट्रिगर कर सकता है
uint256 fee = amount0 * 3 / 997 + 1;
uint256 repayment = amount0 + fee;
// ChatGPT द्वारा लिखी गई कस्टम आर्बिट्रेज लॉजिक
_executeInternalArbitrage(amount0);
// पूल को कर्ज चुकाना
// (यहाँ यह भूल जाते हैं कि बाहरी पूल्स का स्टेट अभी पूरी तरह फिक्स नहीं हुआ है)
bool success = IERC20(msg.sender).transfer(msg.sender, repayment);
require(success, "Repayment failed");
}
function _internalArbitrage(uint256 amount) internal {
// जटिल स्वैप लॉजिक के लिए स्टब
}
}दिखी खोट? न्यूरल नेटवर्क बाहरी एंट्री पॉइंट executeFlashLoan पर lock मॉडिफायर लगा देता है, लेकिन पूरी तरह भूल जाता है कि कॉलबैक फंक्शन uniswapV2Call को अलग से आइसोलेट किया जाना चाहिए या कॉलिंग एड्रेस (msg.sender) को सख्ती से वैलिडेट करना चाहिए। एक हमलावर बीच की गणनाओं के दौरान सीधे अपने कॉन्ट्रैक्ट से इस कॉलबैक को कॉल कर सकता है, जब रैपर कॉन्ट्रैक्ट के बैलेंस अभी कंसिस्टेंट स्टेट में नहीं आए होते हैं, और पूरे कोलैटरल को पूरी तरह साफ कर सकता है। ChatGPT आपको कभी इस बारे में नहीं चेताएगा, वह चैट में अपनी स्माइली इमोजी के साथ बस इतना कहेगा: "कोड ईआरसी स्टैंडर्ड के अनुसार ऑप्टिमाइज़ किया गया है।"
केस नंबर 5: कैशिंग के साथ ऑप्टिमाइज़्ड ERC-721 बैच मिंटिंग कॉन्ट्रैक्ट्स
मॉडर्न एनएफटी मार्केट को सस्ती गैस चाहिए। एआई इसे बहुत अच्छे से समझता है और सीधे कॉन्ट्रैक्ट की मेमोरी में कस्टम ओनर मैपिंग स्ट्रक्चर्स और कैश काउंटर लागू करके कोड को "ऑप्टिमाइज़" करना शुरू कर देता है।
- ऑडिट मीट्रिक: ChatGPT का फैसला — वास्तविक वल्नरएबिलिटी स्टेटस
- गैस की खपत: बेहद कम (Gas Optimized) — स्टेट आइसोलेशन का उल्लंघन करके हासिल की जाती है
- स्टोरेज पैटर्न: मेमोरी में लोकल स्ट्रक्चर्स का इस्तेमाल — onERC721Received हुक के जरिए मैनिपुलेशन के प्रति संवेदनशील
- स्टेबिलिटी: स्टैंडर्ड हार्डैट टेस्ट पास कर लेता है — गहरे रीएंट्रेंसी कॉल स्टैक पर क्रैश हो जाता है
जब कॉन्ट्रैक्ट safeMint के जरिए एनएफटी भेजता है, तो स्टैंडर्ड के मुताबिक रिसीविंग स्मार्ट कॉन्ट्रैक्ट पर कन्फर्मेशन फंक्शन कॉल करना जरूरी होता है। अगर आप बैच मिंट कर रहे हैं (मान लीजिए एक बार में 10 टोकन) और हर टोकन भेजने के बाद (या बाहरी कॉल के अंदर लूप में) कुल बैलेंस काउंटर को अपडेट करते हैं, तो हमलावर तीसरे इटरेशन पर ही कंट्रोल हथिया लेता है, तब जब इंटरनल टोकन ऐरे आधा ही भरा होता है, और ट्रांजैक्शन काउंटर को अभी भी यही लग रहा होता है कि प्रक्रिया बस शुरू ही हुई है। नतीजा? इंडेक्स की डुप्लीकेट्सी और हवा से एसेट्स की अंतहीन छपाई।
मेरे, आपके और पूरी इंडस्ट्री के लिए इसका क्या मतलब है?
सुनिए, मैं खुद रोज कोड लिखता हूँ, लेकिन क्रिप्टो में जनरेटिव मॉडल पर आँख मूंदकर भरोसा करना ऐसा ही है जैसे रशियन रूलेट खेलना, जिसमें एक को छोड़कर बाकी सभी गोलियां भरी हुई हों। अगर आप प्रोटोकॉल जनरेट करने के लिए एआई का इस्तेमाल कर रहे हैं, तो तीन कड़े नियम गांठ बांध लें:
- टेम्पलेट्स के मॉडिफायर्स पर कभी भरोसा न करें। अगर न्यूरल नेटवर्क ने
nonReentrantठोक दिया है, तो इसका मतलब सिर्फ इतना है कि वह इस शब्द को जानता है, लेकिन आपके खास ब्लॉकचेन के असिंक्रोनस कॉन्टेक्स्ट को नहीं समझता। - कॉलबैक फंक्शंस में हमेशा
msg.senderकी जांच करें।uniswapV2Call,onERC721Received,tokensReceivedजैसे किसी भी इंटरफेस को सैन्य बंकर के गेट से भी ज्यादा सख्ती से सुरक्षित किया जाना चाहिए। - फाउंड्री पर अपने खुद के फज़-टेस्ट (फज़र्स) लिखें। कोई भी स्टैटिक यूनिट टेस्ट क्रॉस-फंक्शन रीएंट्रेंसी को उस तरह से नहीं ढूंढ पाएगा जैसे कुछ मिलियन रैंडम इनवेरिएंट रन ढूंढ सकते हैं।
जब मैं इन vulnerabilties के बारे में बात कर रहा हूँ, तो मेरे दिमाग में एक बात बार-बार आ रही है: आखिरकार हमने खुद ही मार्केट को इतना ढीला कर दिया है। हम रूटीन टास्क को कोड जनरेशन पर छोड़ने के आदी हो गए हैं, यह भूले बिना कि ब्लॉकचेन लॉजिक कंपाइलेशन की गलतियों को बिल्कुल माफ नहीं करता। ट्रेडिशनल वेब डेवलपमेंट में आप कमिट रोलबैक कर सकते हैं, पैच निकाल सकते हैं, और पांच मिनट के डाउनटाइम के लिए यूजर्स से माफी मांग सकते हैं। लेकिन EVM एनवायरनमेंट में आपका बग हमेशा के लिए एक इम्यूटेबल लेजर में इंसान की लापरवाही और एल्गोरिद्मिक अंधेपन की जीत के स्मारक के रूप में दर्ज हो जाता है।
चलिए, इस टॉपिक के बचे हुए हिस्से को खत्म करते हैं और एक और ऐसे अनओवियस वेक्टर (non-obvious vector) का विश्लेषण करते हैं जिसे ChatGPT डरावनी निरंतरता के साथ जनरेट करता है।
केस नंबर 6: लिक्विडिटी एग्रीगेटर्स और स्टेबलकॉइन पूल में वर्चुअल शेयर्स
DeFi डेवलपर्स के बीच यह एकदम लेटेस्ट ट्रेंड है—कॉन्स्टेंट-प्रोडक्ट कर्व्स के आधार पर टोकन वेट की डायनेमिक कैलकुलेशन के साथ कस्टम स्वैप पूल बनाना। न्यूरल नेटवर्क को यह मैथ बहुत पसंद है क्योंकि इंटरनेट पर Curve और Uniswap v2 का टेराबाइट ओपन-सोर्स कोड पड़ा हुआ है। मॉडल फॉर्मूला उठाता है, उसे आपकी ज़रूरतों के हिसाब से ढालता है, addLiquidity फंक्शन जोड़ता है और गर्व से रिपोर्ट करता है कि प्रोडक्ट डिप्लॉयमेंट के लिए तैयार है।
लेकिन समस्या यह है कि मॉडल को यह बिल्कुल नहीं पता कि फ्लोइंग फीस (जैसे USDT या ट्रांसफर टैक्स वाले कस्टम deflationary टोकन) वाले बाहरी टोकन को कॉल करते समय वर्चुअल बैलेंस की असिंक्रोनस कैलकुलेशन कैसे काम करती है।
उस पैटर्न को देखिए जिसे AI पूरी तरह से सेफ मानता है:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
interface ITokenWithFee {
function transferFrom(address sender, address recipient, amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract AIWarpPool {
mapping(address => uint256) public userShares;
uint256 public totalShares;
// AI को लगता है कि अगर हम पहले बैलेंस कैलकुलेट करें और फिर मैपिंग में लिखें, तो हम सेफ हैं
function deposit(address token, uint256 amount) external {
uint256 balanceBefore = ITokenWithFee(token).balanceOf(address(this));
// एक्सटर्नल टोकन ट्रांसफर कॉल
bool success = ITokenWithFee(token).transferFrom(msg.sender, address(this), amount);
require(success, "Transfer failed");
uint256 balanceAfter = ITokenWithFee(token).balanceOf(address(this));
uint256 realReceived = balanceAfter - balanceBefore;
// वास्तव में आए फंड्स के आधार पर शेयर्स की मिनिंग
uint256 shares = totalShares == 0 ? realReceived : (realReceived * totalShares) / balanceBefore;
userShares[msg.sender] += shares;
totalShares += shares;
}
function withdraw(uint256 shares) external {
require(userShares[msg.sender] >= shares, "Not enough shares");
uint256 amountToReturn = (shares * ITokenWithFee(msg.sender).balanceOf(address(this))) / totalShares;
// पहले स्टेट अपडेट करते हैं...
userShares[msg.sender] -= shares;
totalShares -= shares;
// ...और फिर फंड्स देते हैं। क्लासिक Checks-Effects-Interactions!
// पर ज़रा रुकिए...
(bool success, ) = msg.sender.call{value: 0}(""); // कंडीशनल एक्सटर्नल टोकन ट्रांसफर कॉल
require(success, "Withdraw failed");
}
}क्या आपको ट्रैप दिखा? न्यूरल नेटवर्क ने withdraw फंक्शन के अंदर ईमानदारी से Checks-Effects-Interactions ऑर्डर का पालन किया। लेकिन वह इस बात को पूरी तरह भूल गया कि amountToReturn की कैलकुलेशन कॉल के समय के मौजूदा balanceOf(address(this)) पर निर्भर करती है। यदि कोई हमलावर एक ऐसा रैपर कॉन्ट्रैक्ट बनाता है जो कंट्रोल मिलने पर (या टोकन के कॉलबैक के जरिए) किसी दूसरे जुड़े हुए पूल में ग्लोबल totalShares बदलने या क्रॉस-कॉन्ट्रैक्ट ओरेकल कॉल से पहले withdraw में रीएंट्रेंसी शुरू कर देता है, तो वह पुरानी लिक्विडिटी वैल्यू के आधार पर प्रोपोर्शन कैलकुलेट कर लेता है।
AI द्वारा लिखे गए कोड को जांचने के लिए फाइनल चेकलिस्ट
यह उम्मीद करना बंद करें कि डिस्ट्रीब्यूटेड सिस्टम आर्किटेक्चर में न्यूरल नेटवर्क आपसे ज़्यादा स्मार्ट है, और बार-बार एक ही गलती करना छोड़ दें। जनरेट किए गए कोड को प्रोडक्शन में या असली पैसों वाले टेस्टनेट में डालने से पहले, इस लिस्ट से गो-थ्रू कर लें:
- सभी कॉलबैक फंक्शन्स को ऐसे आइसोलेट करें जैसे उनमें रेडी-टू-यूज़ exploit के साथ कोई हैकर बैठा हो।
- हर एक्सटर्नल कॉल की जांच करें कि क्या यह कॉन्ट्रैक्ट को तब कंट्रोल वापस दे सकता है जब ग्लोबल इनवेरिएंट्स अभी मैच नहीं हुए हैं।
- मॉडिफायर्स पर आँख बंद करके भरोसा करना भूल जाएं — वे केवल उसी फंक्शन में डायरेक्ट रीएंट्रेंसी से बचाते हैं, लेकिन जटिल क्रॉस-कॉन्ट्रैक्ट चेन के सामने वे बेअसर होते हैं।