کال ڈیٹا آپٹمائزیشن کے لیے مختصر ABIs
تعارف
اس مضمون میں، آپ optimistic rollups، ان پر ٹرانزیکشنز کی لاگت، اور اس بارے میں جانیں گے کہ کس طرح یہ مختلف لاگت کا ڈھانچہ ہمیں Ethereum Mainnet کے مقابلے میں مختلف چیزوں کے لیے آپٹمائز کرنے کا تقاضا کرتا ہے۔ آپ یہ بھی سیکھیں گے کہ اس آپٹمائزیشن کو کیسے نافذ کیا جائے۔
مکمل انکشاف
میں Optimismopens in a new tab کا کل وقتی ملازم ہوں، اس لیے اس مضمون میں مثالیں Optimism پر چلیں گی۔ تاہم، یہاں بیان کردہ تکنیک دیگر رول اپس کے لیے بھی اتنی ہی اچھی طرح کام کرنی چاہیے۔
اصطلاحات
رول اپس پر بحث کرتے وقت، 'لیئر 1' (L1) کی اصطلاح Mainnet، یعنی پروڈکشن Ethereum نیٹ ورک کے لیے استعمال ہوتی ہے۔ 'لیئر 2' (L2) کی اصطلاح رول اپ یا کسی دوسرے ایسے سسٹم کے لیے استعمال ہوتی ہے جو سیکیورٹی کے لیے L1 پر انحصار کرتا ہے لیکن اپنی زیادہ تر پروسیسنگ آف چین کرتا ہے۔
ہم L2 ٹرانزیکشنز کی لاگت کو مزید کیسے کم کر سکتے ہیں؟
آپٹیمسٹک رول اپس کو ہر تاریخی ٹرانزیکشن کا ریکارڈ محفوظ رکھنا ہوتا ہے تاکہ کوئی بھی ان کا جائزہ لے سکے اور اس بات کی تصدیق کر سکے کہ موجودہ اسٹیٹ درست ہے۔ Ethereum Mainnet میں ڈیٹا داخل کرنے کا سب سے سستا طریقہ اسے کال ڈیٹا کے طور پر لکھنا ہے۔ یہ حل Optimismopens in a new tab اور Arbitrumopens in a new tab دونوں نے منتخب کیا تھا۔
L2 ٹرانزیکشنز کی لاگت
L2 ٹرانزیکشنز کی لاگت دو اجزاء پر مشتمل ہے:
- L2 پروسیسنگ، جو عام طور پر انتہائی سستی ہوتی ہے
- L1 اسٹوریج، جو Mainnet گیس کی لاگت سے منسلک ہے
جب میں یہ لکھ رہا ہوں، Optimism پر L2 گیس کی لاگت 0.001 Gwei ہے۔ دوسری طرف، L1 گیس کی لاگت تقریباً 40 gwei ہے۔ آپ موجودہ قیمتیں یہاں دیکھ سکتے ہیںopens in a new tab۔
کال ڈیٹا کے ایک بائٹ کی لاگت یا تو 4 گیس ہے (اگر یہ صفر ہے) یا 16 گیس ہے (اگر یہ کوئی اور قدر ہے)۔ EVM پر سب سے مہنگے آپریشنز میں سے ایک اسٹوریج میں لکھنا ہے۔ L2 پر اسٹوریج میں 32 بائٹ کا ورڈ لکھنے کی زیادہ سے زیادہ لاگت 22100 گیس ہے۔ فی الحال، یہ 22.1 gwei ہے۔ لہذا اگر ہم کال ڈیٹا کا ایک صفر بائٹ بچا سکتے ہیں، تو ہم اسٹوریج میں تقریباً 200 بائٹ لکھ سکیں گے اور پھر بھی فائدے میں رہیں گے۔
ABI
ٹرانزیکشنز کی بڑی اکثریت ایک بیرونی ملکیت والے اکاؤنٹ سے ایک کنٹریکٹ تک رسائی حاصل کرتی ہے۔ زیادہ تر کنٹریکٹس Solidity میں لکھے جاتے ہیں اور اپنے ڈیٹا فیلڈ کی تشریح ایپلیکیشن بائنری انٹرفیس (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،
صفحہ 27،
Gtxdatazeroکی قدر)۔ - رقم: اگر ہم فرض کریں کہ اس کنٹریکٹ میں
decimalsاٹھارہ (عام قدر) ہے اور ٹوکنز کی زیادہ سے زیادہ رقم جو ہم منتقل کریں گے 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 Gives the caller 1000 tokens to play with3 */4 function faucet() external {5 _mint(msg.sender, 1000);6 } // function faucetCalldataInterpreter.sol
یہ وہ کنٹریکٹ ہے جسے ٹرانزیکشنز کو مختصر کال ڈیٹا کے ساتھ کال کرنا چاہیےopens in a new tab۔ آئیے اس کا لائن بہ لائن جائزہ لیں۔
1//SPDX-License-Identifier: Unlicense2pragma solidity ^0.8.0;345import { OrisUselessToken } from "./Token.sol";ہمیں ٹوکن فنکشن کی ضرورت ہے تاکہ یہ جان سکیں کہ اسے کیسے کال کرنا ہے۔
1contract CalldataInterpreter {23 OrisUselessToken public immutable token;اس ٹوکن کا پتہ جس کے لیے ہم پراکسی ہیں۔
12 /**3 * @dev Specify the token address4 * @param tokenAddr_ ERC-20 contract address5 */6 constructor(7 address tokenAddr_8 ) {9 token = OrisUselessToken(tokenAddr_);10 } // constructorسب دکھائیںٹوکن کا پتہ ہی واحد پیرامیٹر ہے جسے ہمیں متعین کرنے کی ضرورت ہے۔
1 function calldataVal(uint startByte, uint length)2 private pure returns (uint) {کال ڈیٹا سے ایک قدر پڑھیں۔
1 uint _retVal;23 require(length < 0x21,4 "calldataVal length limit is 32 bytes");56 require(length + startByte <= msg.data.length,7 "calldataVal trying to read beyond calldatasize");ہم میموری میں ایک 32-بائٹ (256-بٹ) ورڈ لوڈ کرنے جا رہے ہیں اور ان بائٹس کو ہٹانے جا رہے ہیں جو اس فیلڈ کا حصہ نہیں ہیں جسے ہم چاہتے ہیں۔ یہ الگورتھم 32 بائٹس سے لمبی قدروں کے لیے کام نہیں کرتا، اور یقیناً ہم کال ڈیٹا کے آخر سے آگے نہیں پڑھ سکتے۔ L1 پر گیس بچانے کے لیے ان ٹیسٹوں کو چھوڑنا ضروری ہو سکتا ہے، لیکن L2 پر گیس انتہائی سستی ہے، جو ہمارے ذہن میں آنے والے کسی بھی قسم کے سیفٹی چیکس کو ممکن بناتی ہے۔
1 assembly {2 _retVal := calldataload(startByte)3 }ہم fallback() (نیچے دیکھیں) کی کال سے ڈیٹا کاپی کر سکتے تھے، لیکن Yulopens in a new tab کا استعمال کرنا آسان ہے، جو EVM کی اسمبلی لینگویج ہے۔
یہاں ہم CALLDATALOAD opcodeopens in a new tab کا استعمال کرتے ہیں تاکہ بائٹس startByte سے startByte+31 کو اسٹیک میں پڑھ سکیں۔
عام طور پر، Yul میں ایک opcode کا نحو یہ ہے <opcode name>(<first stack value, if any>,<second stack value, if any>...)
12 _retVal = _retVal >> (256-length*8);صرف سب سے اہم length بائٹس فیلڈ کا حصہ ہیں، اس لیے ہم دوسری قدروں سے چھٹکارا پانے کے لیے رائٹ-شفٹopens in a new tab کرتے ہیں۔
اس کا اضافی فائدہ یہ ہے کہ یہ قدر کو فیلڈ کے دائیں طرف منتقل کر دیتا ہے، لہذا یہ خود قدر ہے بجائے اس کے کہ قدر کو 256کچھ سے ضرب دیا جائے۔
12 return _retVal;3 }456 fallback() external {جب ایک Solidity کنٹریکٹ کو کی گئی کال کسی بھی فنکشن کے دستخط سے مماثل نہیں ہوتی ہے، تو یہ the fallback() functionopens in a new tab کو کال کرتی ہے (یہ فرض کرتے ہوئے کہ کوئی موجود ہے)۔
CalldataInterpreter کے معاملے میں، کوئی بھی کال یہاں پہنچتی ہے کیونکہ کوئی دوسرا external یا public فنکشن نہیں ہے۔
1 uint _func;23 _func = calldataVal(0, 1);کال ڈیٹا کا پہلا بائٹ پڑھیں، جو ہمیں فنکشن بتاتا ہے۔ دو وجوہات ہیں کہ کوئی فنکشن یہاں دستیاب کیوں نہیں ہوگا:
- جو فنکشنز
pureیاviewہیں وہ اسٹیٹ کو تبدیل نہیں کرتے اور گیس کی لاگت نہیں اٹھاتے (جب آف چین کال کیا جاتا ہے)۔ ان کی گیس کی لاگت کو کم کرنے کی کوشش کرنا کوئی معنی نہیں رکھتا۔ - وہ فنکشنز جو
msg.senderopens in a new tab پر انحصار کرتے ہیں۔msg.senderکی قدرCalldataInterpreterکا پتہ ہوگی، کالر کا نہیں۔
بدقسمتی سے، looking at the ERC-20 specificationsopens in a new tab کو دیکھنے پر، یہ صرف ایک فنکشن، transfer چھوڑتا ہے۔
یہ ہمارے پاس صرف دو فنکشنز چھوڑتا ہے: transfer (کیونکہ ہم transferFrom کو کال کر سکتے ہیں) اور faucet (کیونکہ ہم ٹوکنز کو واپس اسی کو منتقل کر سکتے ہیں جس نے ہمیں کال کیا)۔
12 // Call the state changing methods of token using3 // information from the calldata45 // faucet6 if (_func == 1) {faucet() کو ایک کال، جس میں پیرامیٹرز نہیں ہوتے۔
1 token.faucet();2 token.transfer(msg.sender,3 token.balanceOf(address(this)));4 }جب ہم token.faucet() کو کال کرتے ہیں تو ہمیں ٹوکن ملتے ہیں۔ تاہم، پراکسی کنٹریکٹ کے طور پر، ہمیں ٹوکنز کی ضرورت نہیں ہے۔
EOA (بیرونی ملکیت والا اکاؤنٹ) یا وہ کنٹریکٹ جس نے ہمیں کال کیا تھا اسے ضرورت ہے۔
لہذا ہم اپنے تمام ٹوکنز اس کو منتقل کر دیتے ہیں جس نے ہمیں کال کیا تھا۔
1 // transfer (assume we have an allowance for it)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 } // fallback23} // contract CalldataInterpretertest.js
یہ JavaScript یونٹ ٹیسٹopens in a new tab ہمیں دکھاتا ہے کہ اس میکانزم کا استعمال کیسے کریں (اور اس کی تصدیق کیسے کریں کہ یہ صحیح طریقے سے کام کرتا ہے)۔ میں یہ فرض کر رہا ہوں کہ آپ chaiopens in a new tab اور ethersopens in a new tab کو سمجھتے ہیں اور صرف ان حصوں کی وضاحت کروں گا جو خاص طور پر کنٹریکٹ پر لاگو ہوتے ہیں۔
1const { expect } = require("chai");23describe("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)910 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)1415 const signer = await ethers.getSigner()سب دکھائیںہم دونوں کنٹریکٹس کو تعینات کرکے شروع کرتے ہیں۔
1 // Get tokens to play with2 const faucetTx = {ہم ٹرانزیکشنز بنانے کے لیے عام طور پر استعمال ہونے والے اعلیٰ سطحی فنکشنز (جیسے token.faucet()) کا استعمال نہیں کر سکتے، کیونکہ ہم ABI کی پیروی نہیں کرتے۔
اس کے بجائے، ہمیں خود ٹرانزیکشن بنانا ہوگا اور پھر اسے بھیجنا ہوگا۔
1 to: cdi.address,2 data: "0x01"ٹرانزیکشن کے لیے ہمیں دو پیرامیٹرز فراہم کرنے کی ضرورت ہے:
to، منزل کا پتہ۔ یہ کال ڈیٹا انٹرپریٹر کنٹریکٹ ہے۔data، بھیجنے کے لیے کال ڈیٹا۔ faucet کال کی صورت میں، ڈیٹا ایک بائٹ،0x01ہے۔
12 }3 await (await signer.sendTransaction(faucetTx)).wait()ہم the signer's sendTransaction methodopens in a new tab کو کال کرتے ہیں کیونکہ ہم نے پہلے ہی منزل (faucetTx.to) کی وضاحت کر دی ہے اور ہمیں ٹرانزیکشن پر دستخط کرنے کی ضرورت ہے۔
1// Check the faucet provides the tokens correctly2expect(await token.balanceOf(signer.address)).to.equal(1000)یہاں ہم بیلنس کی تصدیق کرتے ہیں۔
view فنکشنز پر گیس بچانے کی ضرورت نہیں ہے، لہذا ہم انہیں عام طور پر چلاتے ہیں۔
1// Give the CDI an allowance (approvals cannot be proxied)2const approveTX = await token.approve(cdi.address, 10000)3await approveTX.wait()4expect(await token.allowance(signer.address, cdi.address)).to.equal(10000)منتقلی کرنے کے قابل ہونے کے لیے کال ڈیٹا انٹرپریٹر کو ایک الاؤنس دیں۔
1// Transfer tokens2const 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()23 // Check that we have 256 tokens less4 expect (await token.balanceOf(signer.address)).to.equal(1000-256)56 // And that our destination got them7 expect (await token.balanceOf(destAddr)).to.equal(256)8 }) // it9}) // describeسب دکھائیںلاگت کو کم کرنا جب آپ منزل کے کنٹریکٹ کو کنٹرول کرتے ہیں
اگر آپ کا منزل کے کنٹریکٹ پر کنٹرول ہے تو آپ ایسے فنکشنز بنا سکتے ہیں جو msg.sender چیکس کو بائی پاس کرتے ہیں کیونکہ وہ کال ڈیٹا انٹرپریٹر پر بھروسہ کرتے ہیں۔
آپ یہاں اس کی ایک مثال دیکھ سکتے ہیں کہ یہ کیسے کام کرتا ہے، control-contract برانچ میںopens in a new tab۔
اگر کنٹریکٹ صرف بیرونی ٹرانزیکشنز کا جواب دے رہا ہوتا، تو ہم صرف ایک کنٹریکٹ کے ساتھ کام چلا سکتے تھے۔ تاہم، اس سے composability ٹوٹ جائے گی۔ ایک ایسا کنٹریکٹ رکھنا بہت بہتر ہے جو عام ERC-20 کالز کا جواب دے، اور دوسرا کنٹریکٹ جو مختصر کال ڈیٹا والے ٹرانزیکشنز کا جواب دے۔
Token.sol
اس مثال میں ہم Token.sol میں ترمیم کر سکتے ہیں۔
یہ ہمیں کئی ایسے فنکشنز رکھنے کی اجازت دیتا ہے جنہیں صرف پراکسی ہی کال کر سکتا ہے۔
یہاں نئے حصے ہیں:
1 // The only address allowed to specify the CalldataInterpreter address2 address owner;34 // The CalldataInterpreter address5 address proxy = address(0);ERC-20 کنٹریکٹ کو مجاز پراکسی کی شناخت جاننے کی ضرورت ہے۔ تاہم، ہم اس متغیر کو کنسٹرکٹر میں سیٹ نہیں کر سکتے، کیونکہ ہمیں ابھی تک قدر کا علم نہیں ہے۔ یہ کنٹریکٹ پہلے انسٹینٹی ایٹ کیا جاتا ہے کیونکہ پراکسی اپنے کنسٹرکٹر میں ٹوکن کے پتے کی توقع کرتا ہے۔
1 /**2 * @dev Calls the ERC20 constructor.3 */4 constructor(5 ) ERC20("Oris useless token-2", "OUT-2") {6 owner = msg.sender;7 }تخلیق کار کا پتہ (جسے owner کہا جاتا ہے) یہاں ذخیرہ کیا جاتا ہے کیونکہ یہ واحد پتہ ہے جسے پراکسی سیٹ کرنے کی اجازت ہے۔
1 /**2 * @dev set the address for the proxy (the CalldataInterpreter).3 * Can only be called once by the owner4 */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");89 proxy = _proxy;10 } // function setProxyسب دکھائیںپراکسی کو مراعات یافتہ رسائی حاصل ہے، کیونکہ یہ سیکیورٹی چیک کو بائی پاس کر سکتا ہے۔
یہ یقینی بنانے کے لیے کہ ہم پراکسی پر بھروسہ کر سکتے ہیں، ہم صرف owner کو اس فنکشن کو کال کرنے دیتے ہیں، اور صرف ایک بار۔
ایک بار جب proxy کی کوئی حقیقی قدر (صفر نہیں) ہو جاتی ہے، تو وہ قدر تبدیل نہیں ہو سکتی، لہذا یہاں تک کہ اگر مالک بدمعاش بننے کا فیصلہ کرتا ہے، یا اس کے لیے میمونک ظاہر ہو جاتا ہے، ہم پھر بھی محفوظ ہیں۔
1 /**2 * @dev Some functions may only be called by the proxy.3 */4 modifier onlyProxy {یہ ایک modifier functionopens in a new tab ہے، یہ دوسرے فنکشنز کے کام کرنے کے طریقے میں ترمیم کرتا ہے۔
1 require(msg.sender == proxy);سب سے پہلے، تصدیق کریں کہ ہمیں پراکسی نے کال کیا ہے اور کسی اور نے نہیں۔
اگر نہیں، تو revert کریں۔
1 _;2 }اگر ایسا ہے تو، اس فنکشن کو چلائیں جس میں ہم ترمیم کرتے ہیں۔
1 /* Functions that allow the proxy to actually proxy for accounts */23 function transferProxy(address from, address to, uint256 amount)4 public virtual onlyProxy() returns (bool)5 {6 _transfer(from, to, amount);7 return true;8 }910 function approveProxy(address from, address spender, uint256 amount)11 public virtual onlyProxy() returns (bool)12 {13 _approve(from, spender, amount);14 return true;15 }1617 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 // transfer (no need for allowance)2 if (_func == 2) {3 token.transferProxy(4 msg.sender,5 address(uint160(calldataVal(1, 20))),6 calldataVal(21, 2)7 );8 }910 // approve11 if (_func == 3) {12 token.approveProxy(13 msg.sender,14 address(uint160(calldataVal(1, 20))),15 calldataVal(21, 2)16 );17 }1819 // 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)23// Need two signers to verify allowances4const signers = await ethers.getSigners()5const signer = signers[0]6const poorSigner = signers[1]approve() اور transferFrom() کو چیک کرنے کے لیے ہمیں دوسرے دستخط کنندہ کی ضرورت ہے۔
ہم اسے poorSigner کہتے ہیں کیونکہ اسے ہمارے کوئی ٹوکن نہیں ملتے (اس کے پاس ETH ہونا ضروری ہے، یقیناً)۔
1// Transfer tokens2const 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// approval and transferFrom2const approveTx = {3 to: cdi.address,4 data: "0x03" + poorSigner.address.slice(2, 42) + "00FF",5}6await (await signer.sendTransaction(approveTx)).wait()78const destAddr2 = "0xE1165C689C0c3e9642cA7606F5287e708d846206"910const 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()1516// Check the approve / transferFrom combo was done correctly17expect(await token.balanceOf(destAddr2)).to.equal(255)سب دکھائیںدو نئے فنکشنز کی جانچ کریں۔
نوٹ کریں کہ transferFromTx کو دو ایڈریس پیرامیٹرز کی ضرورت ہے: الاؤنس دینے والا اور وصول کنندہ۔
نتیجہ
دونوں Optimismopens in a new tab اور Arbitrumopens in a new tab L1 پر لکھے گئے کال ڈیٹا کے سائز کو کم کرنے اور اس وجہ سے ٹرانزیکشنز کی لاگت کو کم کرنے کے طریقے تلاش کر رہے ہیں۔ تاہم، عام حل تلاش کرنے والے بنیادی ڈھانچے فراہم کرنے والوں کے طور پر، ہماری صلاحیتیں محدود ہیں۔ ڈ ایپ ڈیولپر کے طور پر، آپ کے پاس ایپلیکیشن کے لیے مخصوص علم ہے، جو آپ کو اپنے کال ڈیٹا کو ہم سے کہیں بہتر طور پر آپٹمائز کرنے دیتا ہے جتنا کہ ہم ایک عام حل میں کر سکتے ہیں۔ امید ہے کہ یہ مضمون آپ کو اپنی ضروریات کے لیے مثالی حل تلاش کرنے میں مدد کرے گا۔
میرے مزید کام کے لیے یہاں دیکھیںopens in a new tab۔
صفحہ کی آخری تازہ کاری: 22 اگست، 2025