कॉलडेटा ऑप्टिमाइज़ेशन के लिए शॉर्ट ABI
परिचय
इस लेख में, आप आशावादी रोलअप, उन पर लेनदेन की लागत और विभिन्न लागत संरचना के लिए हमें एथेरियम मेननेट की तुलना में विभिन्न चीजों के लिए ऑप्टिमाइज़ करने की आवश्यकता कैसे है, के बारे में सीखते हैं। आप यह भी सीखते हैं कि इस ऑप्टिमाइज़ेशन को कैसे लागू किया जाए।
पूर्ण प्रकटीकरण
मैं एक पूर्णकालिक ऑप्टिमिज़्म (opens in a new tab) कर्मचारी हूं, इसलिए इस लेख के उदाहरण ऑप्टिमिज़्म पर चलेंगे। हालांकि, यहां बताई गई तकनीक को अन्य रोलअप के लिए भी उतना ही अच्छा काम करना चाहिए।
शब्दावली
रोलअप पर चर्चा करते समय, 'परत 1' (L1) शब्द का उपयोग मेननेट, उत्पादन एथेरियम नेटवर्क के लिए किया जाता है। 'परत 2' (L2) शब्द का उपयोग रोलअप या किसी अन्य सिस्टम के लिए किया जाता है जो सुरक्षा के लिए L1 पर निर्भर करता है लेकिन अपनी अधिकांश प्रोसेसिंग ऑफ-चेन करता है।
हम L2 लेनदेन की लागत को और कैसे कम कर सकते हैं?
आशावादी रोलअप को हर ऐतिहासिक लेनदेन का रिकॉर्ड रखना होता है ताकि कोई भी उनके माध्यम से जाकर यह सत्यापित कर सके कि वर्तमान स्थिति सही है। एथेरियम मेननेट में डेटा प्राप्त करने का सबसे सस्ता तरीका इसे कॉलडेटा के रूप में लिखना है। यह समाधान ऑप्टिमिज़्म (opens in a new tab) और आर्बिट्रम (opens in a new tab) दोनों द्वारा चुना गया था।
L2 लेनदेन की लागत
L2 लेनदेन की लागत दो घटकों से बनी है:
- L2 प्रोसेसिंग, जो आमतौर पर बहुत सस्ती होती है
- L1 भंडारण, जो मेननेट गैस लागत से बंधा है
जैसा कि मैं यह लिख रहा हूं, ऑप्टिमिज़्म पर L2 गैस की लागत 0.001 Gwei है। दूसरी ओर, L1 गैस की लागत लगभग 40 gwei है। आप यहां वर्तमान मूल्य देख सकते हैं (opens in a new tab)।
कॉलडेटा के एक बाइट की लागत या तो 4 गैस (यदि यह शून्य है) या 16 गैस (यदि यह कोई अन्य मान है) है। EVM पर सबसे महंगे ऑपरेशनों में से एक भंडारण में लिखना है। L2 पर भंडारण के लिए 32-बाइट शब्द लिखने की अधिकतम लागत 22100 गैस है। वर्तमान में, यह 22.1 gwei है। इसलिए यदि हम कॉलडेटा का एक भी शून्य बाइट बचा सकते हैं, तो हम भंडारण में लगभग 200 बाइट्स लिखने में सक्षम होंगे और फिर भी आगे निकल जाएंगे।
ABI
ज़्यादातर लेनदेन बाहरी स्वामित्व वाले खाते से अनुबंध तक पहुंचते हैं। अधिकांश अनुबंध सॉलिडिटी में लिखे गए हैं और एप्लिकेशन बाइनरी इंटरफ़ेस (ABI) (opens in a new tab) के अनुसार उनके डेटा फ़ील्ड की व्याख्या करते हैं।
हालांकि, ABI को L1 के लिए डिज़ाइन किया गया था, जहां कॉलडेटा के एक बाइट की लागत लगभग चार अंकगणितीय ऑपरेशनों के बराबर होती है, न कि L2 जहां कॉलडेटा के एक बाइट की लागत एक हजार से अधिक अंकगणितीय ऑपरेशनों से अधिक होती है। कॉलडेटा को इस तरह विभाजित किया गया है:
| सेक्शन | लंबाई | बाइट्स | व्यर्थ बाइट्स | व्यर्थ गैस | आवश्यक बाइट्स | आवश्यक गैस |
|---|---|---|---|---|---|---|
| फ़ंक्शन चयनकर्ता | 4 | 0-3 | 3 | 48 | 1 | 16 |
| शून्य | 12 | 4-15 | 12 | 48 | 0 | 0 |
| गंतव्य पता | 20 | 16-35 | 0 | 0 | 20 | 320 |
| राशि | 32 | 36-67 | 17 | 64 | 15 | 240 |
| कुल | 68 | 160 | 576 |
स्पष्टीकरण:
- फ़ंक्शन चयनकर्ता: अनुबंध में 256 से कम फ़ंक्शन हैं, इसलिए हम उन्हें एक बाइट से अलग कर सकते हैं। ये बाइट्स आमतौर पर गैर-शून्य होते हैं और इसलिए सोलह गैस की लागत (opens in a new tab) होती है।
- शून्य: ये बाइट्स हमेशा शून्य होते हैं क्योंकि बीस-बाइट के पते को रखने के लिए बत्तीस-बाइट शब्द की आवश्यकता नहीं होती है।
शून्य रखने वाले बाइट्स की लागत चार गैस होती है (येलो पेपर देखें (opens in a new tab), परिशिष्ट G,
p. 27,
Gtxdatazeroके लिए मान)। - राशि: यदि हम मानते हैं कि इस अनुबंध में
डेसिमलअठारह (सामान्य मान) है और हमारे द्वारा हस्तांतरित किए जाने वाले टोकन की अधिकतम राशि 1018 होगी, तो हमें 1036 की अधिकतम राशि मिलती है। 25615 > 1036, इसलिए पंद्रह बाइट्स पर्याप्त हैं।
L1 पर 160 गैस की बर्बादी सामान्य रूप से नगण्य है। एक लेनदेन में कम से कम 21,000 गैस (opens in a new tab) की लागत आती है, इसलिए अतिरिक्त 0.8% कोई मायने नहीं रखता।
हालांकि, L2 पर, चीजें अलग हैं। लगभग लेनदेन की पूरी लागत इसे L1 पर लिख रही है।
लेनदेन कॉलडेटा के अलावा, लेनदेन हेडर के 109 बाइट्स (गंतव्य पता, हस्ताक्षर, आदि) हैं।
इसलिए कुल लागत 109*16+576+160=2480 है, और हम इसका लगभग 6.5% बर्बाद कर रहे हैं।
जब आप गंतव्य को नियंत्रित नहीं करते हैं तो लागत कम करना
यह मानते हुए कि आपका गंतव्य अनुबंध पर नियंत्रण नहीं है, आप अभी भी इस (opens in a new tab) जैसे समाधान का उपयोग कर सकते हैं। चलिए प्रासंगिक फाइलों पर चलते हैं।
Token.sol
यह गंतव्य अनुबंध है (opens in a new tab)।
यह एक मानक ERC-20 अनुबंध है, जिसमें एक अतिरिक्त सुविधा है।
यह faucet फंक्शन किसी भी यूज़र को उपयोग करने के लिए कुछ टोकन प्राप्त करने देता है।
यह एक उत्पादन ERC-20 अनुबंध को बेकार बना देगा, लेकिन यह जीवन को आसान बनाता है जब एक ERC-20 केवल परीक्षण की सुविधा के लिए मौजूद होता है।
1 /**2 * @dev कॉलर को खेलने के लिए 1000 टोकन देता है3 */4 function faucet() external {5 _mint(msg.sender, 1000);6 } // फंक्शन फोसेटCalldataInterpreter.sol
यह वह अनुबंध है जिसे लेनदेन छोटे कॉलडेटा के साथ कॉल करने वाले हैं (opens in a new tab)। आइए इसे लाइन-दर-लाइन देखें।
1//SPDX-License-Identifier: Unlicense2pragma solidity ^0.8.0;3
4
5import { OrisUselessToken } from "./Token.sol";हमें यह जानने के लिए टोकन फ़ंक्शन की आवश्यकता है कि इसे कैसे कॉल किया जाए।
1contract CalldataInterpreter {2
3 OrisUselessToken public immutable token;टोकन का पता जिसके लिए हम एक प्रॉक्सी हैं।
1
2 /**3 * @dev टोकन पता निर्दिष्ट करें4 * @param tokenAddr_ ERC-20 अनुबंध पता5 */6 constructor(7 address tokenAddr_8 ) {9 token = OrisUselessToken(tokenAddr_);10 } // कंस्ट्रक्टरटोकन पता एकमात्र पैरामीटर है जिसे हमें निर्दिष्ट करने की आवश्यकता है।
1 function calldataVal(uint startByte, uint length)2 private pure returns (uint) {कॉलडेटा से एक मान पढ़ें।
1 uint _retVal;2
3 require(length < 0x21,4 "calldataVal लंबाई सीमा 32 बाइट्स है");5
6 require(length + startByte <= msg.data.length,7 "calldataVal कॉलगैस आकार से परे पढ़ने की कोशिश कर रहा है");हम मेमोरी में एक 32-बाइट (256-बिट) शब्द लोड करने जा रहे हैं और उन बाइट्स को हटाने जा रहे हैं जो हमारे इच्छित फ़ील्ड का हिस्सा नहीं हैं। यह एल्गोरिथम 32 बाइट्स से अधिक लंबे मानों के लिए काम नहीं करता है, और निश्चित रूप से हम कॉलडेटा के अंत से आगे नहीं पढ़ सकते हैं। L1 पर गैस बचाने के लिए इन परीक्षणों को छोड़ना आवश्यक हो सकता है, लेकिन L2 पर गैस बहुत सस्ती है, जो हमारे द्वारा सोचे जा सकने वाले किसी भी विवेक जांच को सक्षम करती है।
1 assembly {2 _retVal := calldataload(startByte)3 }हम fallback() (नीचे देखें) पर कॉल से डेटा कॉपी कर सकते थे, लेकिन EVM की असेंबली भाषा युल (opens in a new tab) का उपयोग करना आसान है।
यहां हम स्टैक में बाइट्स startByte से startByte+31 पढ़ने के लिए CALLDATALOAD ओपकोड (opens in a new tab) का उपयोग करते हैं।
सामान्य तौर पर, युल में एक ओपकोड का सिंटैक्स <opcode name>(<पहला स्टैक मान, यदि कोई हो>,<दूसरा स्टैक मान, यदि कोई हो>...) होता है।
1
2 _retVal = _retVal >> (256-length*8);केवल सबसे महत्वपूर्ण length बाइट्स फ़ील्ड का हिस्सा हैं, इसलिए हम अन्य मानों से छुटकारा पाने के लिए राइट-शिफ्ट (opens in a new tab) करते हैं।
इसका अतिरिक्त लाभ यह है कि यह मान को फ़ील्ड के दाईं ओर ले जाता है, इसलिए यह मान स्वयं है बजाय मान गुणा 256something के।
1
2 return _retVal;3 }4
5
6 fallback() external {जब एक सॉलिडिटी अनुबंध के लिए एक कॉल किसी भी फ़ंक्शन हस्ताक्षर से मेल नहीं खाती है, तो यह the fallback() फ़ंक्शन (opens in a new tab) को कॉल करता है (यह मानते हुए कि एक है)।
CalldataInterpreter के मामले में, कोई भी कॉल यहां पहुंचती है क्योंकि कोई अन्य external या public फ़ंक्शन नहीं हैं।
1 uint _func;2
3 _func = calldataVal(0, 1);कॉलडेटा का पहला बाइट पढ़ें, जो हमें फ़ंक्शन बताता है। दो कारण हैं कि कोई फ़ंक्शन यहां उपलब्ध क्यों नहीं होगा:
pureयाviewफ़ंक्शन स्थिति नहीं बदलते हैं और गैस की लागत नहीं लेते हैं (जब ऑफ-चेन कहा जाता है)। उनकी गैस लागत को कम करने की कोशिश करने का कोई मतलब नहीं है।- फ़ंक्शन जो
msg.sender(opens in a new tab) पर निर्भर करते हैं।msg.senderका मानCalldataInterpreterका पता होगा, न कि कॉलर का।
दुर्भाग्य से, ERC-20 विनिर्देशों को देखते हुए (opens in a new tab), यह केवल एक फ़ंक्शन, transfer छोड़ता है।
यह हमें केवल दो कार्यों के साथ छोड़ देता है: transfer (क्योंकि हम transferFrom को कॉल कर सकते हैं) और faucet (क्योंकि हम जिसने हमें कॉल किया है, उसे टोकन वापस ट्रांसफर कर सकते हैं)।
1
2 // कॉलडेटा से जानकारी का उपयोग करके3 // टोकन की स्थिति बदलने के तरीकों को कॉल करें4
5 // फोसेट6 if (_func == 1) {faucet() पर एक कॉल, जिसमें पैरामीटर नहीं हैं।
1 token.faucet();2 token.transfer(msg.sender,3 token.balanceOf(address(this)));4 }जब हम token.faucet() को कॉल करते हैं तो हमें टोकन मिलते हैं। हालांकि, प्रॉक्सी अनुबंध के रूप में, हमें टोकन की आवश्यकता नहीं है।
EOA (बाहरी स्वामित्व वाला खाता) या हमें कॉल करने वाले अनुबंध को इसकी आवश्यकता है।
इसलिए हम अपने सभी टोकन उस व्यक्ति को हस्तांतरित करते हैं जिसने हमें बुलाया था।
1 // ट्रांसफर (मान लें कि हमारे पास इसके लिए भत्ता है)2 if (_func == 2) {टोकन ट्रांसफर करने के लिए दो पैरामीटर की आवश्यकता होती है: गंतव्य पता और राशि।
1 token.transferFrom(2 msg.sender,हम केवल कॉल करने वालों को उनके स्वामित्व वाले टोकन को स्थानांतरित करने की अनुमति देते हैं
1 address(uint160(calldataVal(1, 20))),गंतव्य पता बाइट #1 पर शुरू होता है (बाइट #0 फ़ंक्शन है)। एक पते के रूप में, यह 20-बाइट्स लंबा है।
1 calldataVal(21, 2)इस विशेष अनुबंध के लिए हम मानते हैं कि कोई भी व्यक्ति जो अधिकतम संख्या में टोकन स्थानांतरित करना चाहता है, वह दो बाइट्स (65536 से कम) में फिट बैठता है।
1 );2 }कुल मिलाकर, एक हस्तांतरण में 35 बाइट्स कॉलडेटा लगते हैं:
| सेक्शन | लंबाई | बाइट्स |
|---|---|---|
| फ़ंक्शन चयनकर्ता | 1 | 0 |
| गंतव्य पता | 32 | 1-32 |
| राशि | 2 | 33-34 |
1 } // फॉलबैक2
3} // अनुबंध CalldataInterpretertest.js
यह जावास्क्रिप्ट यूनिट परीक्षण (opens in a new tab) हमें दिखाता है कि इस तंत्र का उपयोग कैसे करें (और यह कैसे सत्यापित करें कि यह सही तरीके से काम करता है)। मैं यह मानने जा रहा हूं कि आप chai (opens in a new tab) और ethers (opens in a new tab) को समझते हैं और केवल उन हिस्सों की व्याख्या करते हैं जो विशेष रूप से अनुबंध पर लागू होते हैं।
1const { expect } = require("chai");2
3describe("CalldataInterpreter", function () {4 it("Should let us use tokens", async function () {5 const Token = await ethers.getContractFactory("OrisUselessToken")6 const token = await Token.deploy()7 await token.deployed()8 console.log("Token addr:", token.address)9
10 const Cdi = await ethers.getContractFactory("CalldataInterpreter")11 const cdi = await Cdi.deploy(token.address)12 await cdi.deployed()13 console.log("CalldataInterpreter addr:", cdi.address)14
15 const signer = await ethers.getSigner()हम दोनों अनुबंधों को परिनियोजित करके शुरू करते हैं।
1 // खेलने के लिए टोकन प्राप्त करें2 const faucetTx = {हम लेनदेन बनाने के लिए सामान्य रूप से उपयोग किए जाने वाले उच्च-स्तरीय कार्यों (जैसे token.faucet()) का उपयोग नहीं कर सकते हैं, क्योंकि हम ABI का पालन नहीं करते हैं।
इसके बजाय, हमें स्वयं लेनदेन का निर्माण करना होगा और फिर उसे भेजना होगा।
1 to: cdi.address,2 data: "0x01"लेनदेन के लिए हमें दो पैरामीटर प्रदान करने की आवश्यकता है:
to, गंतव्य पता। यह कॉलडेटा इंटरप्रेटर अनुबंध है।data, भेजने के लिए कॉलडेटा। एक फोसेट कॉल के मामले में, डेटा एक बाइट,0x01है।
1
2 }3 await (await signer.sendTransaction(faucetTx)).wait()हम हस्ताक्षरकर्ता की sendTransaction विधि (opens in a new tab) को कॉल करते हैं क्योंकि हमने पहले ही गंतव्य (faucetTx.to) निर्दिष्ट कर दिया है और हमें लेनदेन पर हस्ताक्षर करने की आवश्यकता है।
1// जांचें कि फोसेट टोकन सही ढंग से प्रदान करता है या नहीं2expect(await token.balanceOf(signer.address)).to.equal(1000)यहां हम शेष राशि की पुष्टि करते हैं।
view फ़ंक्शन पर गैस बचाने की कोई आवश्यकता नहीं है, इसलिए हम बस उन्हें सामान्य रूप से चलाते हैं।
1// CDI को एक भत्ता दें (अनुमोदन को प्रॉक्सी नहीं किया जा सकता है)2const approveTX = await token.approve(cdi.address, 10000)3await approveTX.wait()4expect(await token.allowance(signer.address, cdi.address)).to.equal(10000)स्थानांतरण करने में सक्षम होने के लिए कॉलडेटा दुभाषिया को एक भत्ता दें।
1// टोकन ट्रांसफर करें2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}एक स्थानांतरण लेनदेन बनाएं। पहला बाइट "0x02" है, इसके बाद गंतव्य पता है, और अंत में राशि (0x0100, जो दशमलव में 256 है)।
1 await (await signer.sendTransaction(transferTx)).wait()2
3 // जांचें कि हमारे पास 256 टोकन कम हैं4 expect (await token.balanceOf(signer.address)).to.equal(1000-256)5
6 // और यह कि हमारे गंतव्य को वे मिल गए हैं7 expect (await token.balanceOf(destAddr)).to.equal(256)8 }) // यह9}) // वर्णनजब आप गंतव्य अनुबंध को नियंत्रित करते हैं तो लागत कम करना
यदि आपका गंतव्य अनुबंध पर नियंत्रण है, तो आप ऐसे फ़ंक्शन बना सकते हैं जो msg.sender जांच को बायपास करते हैं क्योंकि वे कॉलडेटा दुभाषिया पर भरोसा करते हैं।
आप control-contract शाखा में यहां इसका एक उदाहरण देख सकते हैं (opens in a new tab)।
यदि अनुबंध केवल बाहरी लेनदेन का जवाब दे रहा होता, तो हम केवल एक अनुबंध के साथ काम चला सकते थे। हालांकि, यह कंपोज़ेबिलिटी को तोड़ देगा। सामान्य ERC-20 कॉल का जवाब देने वाला एक अनुबंध होना बहुत बेहतर है, और दूसरा अनुबंध जो छोटे कॉल डेटा के साथ लेनदेन का जवाब देता है।
Token.sol
इस उदाहरण में हम Token.sol को संशोधित कर सकते हैं।
यह हमें कई ऐसे कार्य करने देता है जिन्हें केवल प्रॉक्सी ही कॉल कर सकता है।
यहां नए हिस्से दिए गए हैं:
1 // CalldataInterpreter पते को निर्दिष्ट करने के लिए अनुमत एकमात्र पता2 address owner;3
4 // The CalldataInterpreter address5 address proxy = address(0);ERC-20 अनुबंध को अधिकृत प्रॉक्सी की पहचान जानने की आवश्यकता है। हालांकि, हम इस चर को कंस्ट्रक्टर में सेट नहीं कर सकते हैं, क्योंकि हम अभी तक मान नहीं जानते हैं। यह अनुबंध पहले इंस्टेंट किया गया है क्योंकि प्रॉक्सी अपने कंस्ट्रक्टर में टोकन के पते की अपेक्षा करता है।
1 /**2 * @dev ERC20 कंस्ट्रक्टर को कॉल करता है।3 */4 constructor(5 ) ERC20("Oris useless token-2", "OUT-2") {6 owner = msg.sender;7 }निर्माता का पता (जिसे owner कहा जाता है) यहां संग्रहीत किया जाता है क्योंकि प्रॉक्सी सेट करने के लिए यह एकमात्र पता है।
1 /**2 * @dev प्रॉक्सी (CalldataInterpreter) के लिए पता सेट करें।3 * मालिक द्वारा केवल एक बार कॉल किया जा सकता है4 */5 function setProxy(address _proxy) external {6 require(msg.sender == owner, "Can only be called by owner");7 require(proxy == address(0), "Proxy is already set");8
9 proxy = _proxy;10 } // फ़ंक्शन setProxyप्रॉक्सी के पास विशेषाधिकार प्राप्त पहुंच है, क्योंकि यह सुरक्षा जांच को बायपास कर सकता है।
यह सुनिश्चित करने के लिए कि हम प्रॉक्सी पर भरोसा कर सकते हैं, हम केवल owner को इस फ़ंक्शन को कॉल करने देते हैं, और केवल एक बार।
एक बार जब proxy का वास्तविक मान (शून्य नहीं) हो जाता है, तो वह मान नहीं बदल सकता है, इसलिए भले ही मालिक दुष्ट बनने का फैसला करता है, या इसके लिए मेमोनिक का खुलासा हो जाता है, हम अभी भी सुरक्षित हैं।
1 /**2 * @dev कुछ फंक्शन केवल प्रॉक्सी द्वारा ही कॉल किए जा सकते हैं।3 */4 modifier onlyProxy {यह एक modifier फ़ंक्शन (opens in a new tab) है, यह अन्य फ़ंक्शन के काम करने के तरीके को संशोधित करता है।
1 require(msg.sender == proxy);सबसे पहले, सत्यापित करें कि हमें प्रॉक्सी द्वारा बुलाया गया था और किसी और ने नहीं।
यदि नहीं, तो revert।
1 _;2 }यदि हां, तो उस फ़ंक्शन को चलाएं जिसे हम संशोधित करते हैं।
1 /* फ़ंक्शन जो प्रॉक्सी को वास्तव में खातों के लिए प्रॉक्सी करने की अनुमति देते हैं */2
3 function transferProxy(address from, address to, uint256 amount)4 public virtual onlyProxy() returns (bool)5 {6 _transfer(from, to, amount);7 return true;8 }9
10 function approveProxy(address from, address spender, uint256 amount)11 public virtual onlyProxy() returns (bool)12 {13 _approve(from, spender, amount);14 return true;15 }16
17 function transferFromProxy(18 address spender,19 address from,20 address to,21 uint256 amount22 ) public virtual onlyProxy() returns (bool)23 {24 _spendAllowance(from, spender, amount);25 _transfer(from, to, amount);26 return true;27 }ये तीन ऑपरेशन हैं जिनके लिए सामान्य रूप से संदेश को सीधे टोकन स्थानांतरित करने या भत्ता स्वीकृत करने वाली इकाई से आने की आवश्यकता होती है। यहां हमारे पास एक प्रॉक्सी संस्करण है जो इन कार्यों को करता है:
onlyProxy()द्वारा संशोधित किया गया है ताकि किसी और को उन्हें नियंत्रित करने की अनुमति न हो।- एक अतिरिक्त पैरामीटर के रूप में वह पता प्राप्त करता है जो सामान्य रूप से
msg.senderहोगा।
CalldataInterpreter.sol
कॉलडेटा दुभाषिया ऊपर वाले के लगभग समान है, सिवाय इसके कि प्रॉक्सी किए गए फ़ंक्शन को msg.sender पैरामीटर प्राप्त होता है और transfer के लिए भत्ते की कोई आवश्यकता नहीं होती है।
1 // ट्रांसफर (भत्ते की कोई आवश्यकता नहीं)2 if (_func == 2) {3 token.transferProxy(4 msg.sender,5 address(uint160(calldataVal(1, 20))),6 calldataVal(21, 2)7 );8 }9
10 // स्वीकार11 if (_func == 3) {12 token.approveProxy(13 msg.sender,14 address(uint160(calldataVal(1, 20))),15 calldataVal(21, 2)16 );17 }18
19 // transferFrom20 if (_func == 4) {21 token.transferFromProxy(22 msg.sender,23 address(uint160(calldataVal( 1, 20))),24 address(uint160(calldataVal(21, 20))),25 calldataVal(41, 2)26 );27 }Test.js
पिछले परीक्षण कोड और इस एक के बीच कुछ बदलाव हैं।
1const Cdi = await ethers.getContractFactory("CalldataInterpreter")2const cdi = await Cdi.deploy(token.address)3await cdi.deployed()4await token.setProxy(cdi.address)हमें ERC-20 अनुबंध को यह बताने की आवश्यकता है कि किस प्रॉक्सी पर भरोसा करना है
1console.log("CalldataInterpreter addr:", cdi.address)2
3// भत्ते को सत्यापित करने के लिए दो हस्ताक्षरकर्ताओं की आवश्यकता है4const signers = await ethers.getSigners()5const signer = signers[0]6const poorSigner = signers[1]approve() और transferFrom() की जांच के लिए हमें दूसरे हस्ताक्षरकर्ता की आवश्यकता है।
हम इसे poorSigner कहते हैं क्योंकि इसे हमारे कोई टोकन नहीं मिलते हैं (निश्चित रूप से, इसमें ETH होना आवश्यक है)।
1// टोकन ट्रांसफर करें2const destAddr = "0xf5a6ead936fb47f342bb63e676479bddf26ebe1d"3const transferTx = {4 to: cdi.address,5 data: "0x02" + destAddr.slice(2, 42) + "0100",6}7await (await signer.sendTransaction(transferTx)).wait()क्योंकि ERC-20 अनुबंध प्रॉक्सी (cdi) पर भरोसा करता है, हमें स्थानान्तरण को रिले करने के लिए एक भत्ते की आवश्यकता नहीं है।
1// अनुमोदन और transferFrom2const approveTx = {3 to: cdi.address,4 data: "0x03" + poorSigner.address.slice(2, 42) + "00FF",5}6await (await signer.sendTransaction(approveTx)).wait()7
8const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206"9
10const transferFromTx = {11 to: cdi.address,12 data: "0x04" + signer.address.slice(2, 42) + destAddr2.slice(2, 42) + "00FF",13}14await (await poorSigner.sendTransaction(transferFromTx)).wait()15
16// जांचें कि approve / transferFrom कॉम्बो सही तरीके से किया गया था17expect(await token.balanceOf(destAddr2)).to.equal(255)दो नए कार्यों का परीक्षण करें।
ध्यान दें कि transferFromTx के लिए दो पता पैरामीटर की आवश्यकता होती है: भत्ता देने वाला और प्राप्त करने वाला।
निष्कर्ष
ऑप्टिमिज़्म (opens in a new tab) और आर्बिट्रम (opens in a new tab) दोनों ही L1 पर लिखे गए कॉलडेटा के आकार को कम करने और इसलिए लेनदेन की लागत को कम करने के तरीके खोज रहे हैं। हालांकि, सामान्य समाधानों की तलाश करने वाले बुनियादी ढांचा प्रदाताओं के रूप में, हमारी क्षमताएं सीमित हैं। डैप डेवलपर के रूप में, आपके पास एप्लिकेशन-विशिष्ट ज्ञान है, जो आपको अपने कॉलडेटा को सामान्य समाधान की तुलना में कहीं बेहतर तरीके से अनुकूलित करने देता है। उम्मीद है, यह लेख आपको अपनी आवश्यकताओं के लिए आदर्श समाधान खोजने में मदद करेगा।
मेरे और काम के लिए यहाँ देखें (opens in a new tab)।
पेज का अंतिम अपडेट: 3 मार्च 2026