سب کچھ جو آپ کیش کر سکتے ہیں
رول اپس (rollups) کا استعمال کرتے وقت ٹرانزیکشن میں ایک بائٹ کی قیمت اسٹوریج سلاٹ کی قیمت سے کہیں زیادہ مہنگی ہوتی ہے۔ اس لیے، زیادہ سے زیادہ معلومات کو آن چین (onchain) کیش (cache) کرنا سمجھداری کی بات ہے۔
اس مضمون میں آپ سیکھیں گے کہ کیشنگ کنٹریکٹ کو کس طرح بنایا اور استعمال کیا جائے تاکہ کسی بھی پیرامیٹر کی ویلیو جس کے متعدد بار استعمال ہونے کا امکان ہو، اسے کیش کیا جا سکے اور (پہلی بار کے بعد) بہت کم بائٹس کے ساتھ استعمال کے لیے دستیاب ہو، اور اس کیش کو استعمال کرنے والا آف چین (offchain) کوڈ کیسے لکھا جائے۔
اگر آپ مضمون کو چھوڑ کر صرف سورس کوڈ دیکھنا چاہتے ہیں، تو وہ یہاں ہے (opens in a new tab)۔ ڈیولپمنٹ اسٹیک Foundry (opens in a new tab) ہے۔
مجموعی ڈیزائن
سادگی کی خاطر ہم فرض کریں گے کہ تمام ٹرانزیکشن پیرامیٹرز uint256 ہیں، جو 32 بائٹس طویل ہیں۔ جب ہمیں کوئی ٹرانزیکشن موصول ہوتی ہے، تو ہم ہر پیرامیٹر کو اس طرح پارس (parse) کریں گے:
-
اگر پہلی بائٹ
0xFFہے، تو اگلی 32 بائٹس کو پیرامیٹر ویلیو کے طور پر لیں اور اسے کیش میں لکھیں۔ -
اگر پہلی بائٹ
0xFEہے، تو اگلی 32 بائٹس کو پیرامیٹر ویلیو کے طور پر لیں لیکن اسے کیش میں نہ لکھیں۔ -
کسی بھی دوسری ویلیو کے لیے، اوپر والی چار بٹس کو اضافی بائٹس کی تعداد کے طور پر لیں، اور نیچے والی چار بٹس کو کیش کی (key) کی سب سے اہم بٹس (most significant bits) کے طور پر لیں۔ یہاں کچھ مثالیں ہیں:
کال ڈیٹا میں بائٹس کیش کی (key) 0x0F 0x0F 0x10,0x10 0x10 0x12,0xAC 0x02AC 0x2D,0xEA, 0xD6 0x0DEAD6
کیش مینیپولیشن
کیش کو Cache.sol (opens in a new tab) میں لاگو کیا گیا ہے۔ آئیے اس کا لائن بہ لائن جائزہ لیتے ہیں۔
1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;345contract Cache {67 bytes1 public constant INTO_CACHE = 0xFF;8 bytes1 public constant DONT_CACHE = 0xFE;یہ کنسٹنٹس (constants) ان خاص صورتوں کی تشریح کے لیے استعمال ہوتے ہیں جہاں ہم تمام معلومات فراہم کرتے ہیں اور یا تو اسے کیش میں لکھنا چاہتے ہیں یا نہیں۔ کیش میں لکھنے کے لیے پہلے سے غیر استعمال شدہ اسٹوریج سلاٹس میں دو SSTORE (opens in a new tab) آپریشنز کی ضرورت ہوتی ہے جن میں سے ہر ایک کی قیمت 22100 گیس ہوتی ہے، اس لیے ہم اسے اختیاری بناتے ہیں۔
12 mapping(uint => uint) public val2key;ویلیوز اور ان کی کیز (keys) کے درمیان ایک میپنگ (mapping) (opens in a new tab)۔ ٹرانزیکشن بھیجنے سے پہلے ویلیوز کو انکوڈ کرنے کے لیے یہ معلومات ضروری ہے۔
1 // مقام n میں کلید n+1 کی قدر ہے، کیونکہ ہمیں محفوظ رکھنے کی ضرورت ہے2 // صفر کو بطور "کیشے میں نہیں"۔3 uint[] public key2val;ہم کیز سے ویلیوز تک میپنگ کے لیے ایک ایرے (array) استعمال کر سکتے ہیں کیونکہ ہم کیز تفویض کرتے ہیں، اور سادگی کے لیے ہم اسے ترتیب وار کرتے ہیں۔
1 function cacheRead(uint _key) public view returns (uint) {2 require(_key <= key2val.length, "Reading uninitialize cache entry");3 return key2val[_key-1];4 } // cacheReadکیش سے ایک ویلیو پڑھیں۔
1 // اگر کوئی قدر پہلے سے کیشے میں موجود نہیں ہے تو اسے کیشے میں لکھیں2 // صرف پبلک ہے تاکہ ٹیسٹ کام کر سکے3 function cacheWrite(uint _value) public returns (uint) {4 // اگر قدر پہلے سے کیشے میں موجود ہے، تو موجودہ کلید واپس کریں5 if (val2key[_value] != 0) {6 return val2key[_value];7 }ایک ہی ویلیو کو ایک سے زیادہ بار کیش میں رکھنے کا کوئی فائدہ نہیں۔ اگر ویلیو پہلے سے موجود ہے، تو بس موجودہ کی (key) واپس کر دیں۔
1 // چونکہ 0xFE ایک خاص صورت ہے، اس لیے سب سے بڑی کلید جو کیشے2 // رکھ سکتا ہے وہ 0x0D ہے جس کے بعد 15 0xFF آتے ہیں۔ اگر کیشے کی لمبائی پہلے ہی اتنی3 // بڑی ہے، تو ناکام ہو جائیں۔4 // 1 2 3 4 5 6 7 8 9 A B C D E F5 require(key2val.length+1 < 0x0DFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,6 "cache overflow");مجھے نہیں لگتا کہ ہمیں کبھی اتنی بڑی کیش ملے گی (تقریباً 1.8*1037 اندراجات، جنہیں اسٹور کرنے کے لیے تقریباً 1027 TB درکار ہوں گے)۔ تاہم، میں اتنا پرانا ہوں کہ مجھے یاد ہے "640kB ہمیشہ کافی ہوگا" (opens in a new tab)۔ یہ ٹیسٹ بہت سستا ہے۔
1 // اگلی کلید کا استعمال کرتے ہوئے قدر لکھیں2 val2key[_value] = key2val.length+1;ریورس لک اپ (ویلیو سے کی تک) شامل کریں۔
1 key2val.push(_value);فارورڈ لک اپ (کی سے ویلیو تک) شامل کریں۔ چونکہ ہم ویلیوز کو ترتیب وار تفویض کرتے ہیں اس لیے ہم اسے آخری ایرے ویلیو کے بعد شامل کر سکتے ہیں۔
1 return key2val.length;2 } // cacheWritekey2val کی نئی لمبائی واپس کریں، جو وہ سیل ہے جہاں نئی ویلیو اسٹور کی گئی ہے۔
1 function _calldataVal(uint startByte, uint length)2 private pure returns (uint)یہ فنکشن صوابدیدی لمبائی (32 بائٹس تک، جو ورڈ سائز ہے) کے کال ڈیٹا سے ایک ویلیو پڑھتا ہے۔
1 {2 uint _retVal;34 require(length < 0x21,5 "_calldataVal length limit is 32 bytes");6 require(length + startByte <= msg.data.length,7 "_calldataVal trying to read beyond calldatasize");یہ فنکشن انٹرنل (internal) ہے، لہذا اگر باقی کوڈ درست طریقے سے لکھا گیا ہے تو ان ٹیسٹس کی ضرورت نہیں ہے۔ تاہم، ان پر زیادہ لاگت نہیں آتی اس لیے ہم انہیں رکھ سکتے ہیں۔
1 assembly {2 _retVal := calldataload(startByte)3 }یہ کوڈ Yul (opens in a new tab) میں ہے۔ یہ کال ڈیٹا سے 32 بائٹ کی ویلیو پڑھتا ہے۔ یہ تب بھی کام کرتا ہے اگر کال ڈیٹا startByte+32 سے پہلے رک جائے کیونکہ EVM میں غیر شروع شدہ (uninitialized) جگہ کو صفر سمجھا جاتا ہے۔
1 _retVal = _retVal >> (256-length*8);ہمیں لازمی طور پر 32 بائٹ کی ویلیو نہیں چاہیے۔ یہ اضافی بائٹس سے چھٹکارا دلاتا ہے۔
1 return _retVal;2 } // _calldataVal345 // کال ڈیٹا (calldata) سے ایک پیرامیٹر پڑھیں، جس کی شروعات _fromByte سے ہو6 function _readParam(uint _fromByte) internal7 returns (uint _nextByte, uint _parameterValue)8 {کال ڈیٹا سے ایک واحد پیرامیٹر پڑھیں۔ نوٹ کریں کہ ہمیں نہ صرف وہ ویلیو واپس کرنے کی ضرورت ہے جو ہم نے پڑھی ہے، بلکہ اگلی بائٹ کا مقام بھی کیونکہ پیرامیٹرز 1 بائٹ سے لے کر 33 بائٹس تک طویل ہو سکتے ہیں۔
1 // پہلا بائٹ ہمیں بتاتا ہے کہ باقی کو کیسے سمجھنا ہے2 uint8 _firstByte;34 _firstByte = uint8(_calldataVal(_fromByte, 1));Solidity ممکنہ طور پر خطرناک امپلیسٹ ٹائپ کنورژنز (implicit type conversions) (opens in a new tab) کو منع کر کے بگز کی تعداد کو کم کرنے کی کوشش کرتی ہے۔ ایک ڈاؤن گریڈ، مثال کے طور پر 256 بٹس سے 8 بٹس تک، واضح (explicit) ہونا چاہیے۔
12 // قدر پڑھیں، لیکن اسے کیشے میں نہ لکھیں3 if (_firstByte == uint8(DONT_CACHE))4 return(_fromByte+33, _calldataVal(_fromByte+1, 32));56 // قدر پڑھیں، اور اسے کیشے میں لکھیں7 if (_firstByte == uint8(INTO_CACHE)) {8 uint _param = _calldataVal(_fromByte+1, 32);9 cacheWrite(_param);10 return(_fromByte+33, _param);11 }1213 // اگر ہم یہاں پہنچ گئے ہیں تو اس کا مطلب ہے کہ ہمیں کیشے سے پڑھنے کی ضرورت ہے1415 // پڑھنے کے لیے اضافی بائٹس کی تعداد16 uint8 _extraBytes = _firstByte / 16;سب دکھائیںنچلا نبل (nibble) (opens in a new tab) لیں اور اسے کیش سے ویلیو پڑھنے کے لیے دوسری بائٹس کے ساتھ جوڑیں۔
1 uint _key = (uint256(_firstByte & 0x0F) << (8*_extraBytes)) +2 _calldataVal(_fromByte+1, _extraBytes);34 return (_fromByte+_extraBytes+1, cacheRead(_key));56 } // _readParam789 // n پیرامیٹرز پڑھیں (فنکشنز جانتے ہیں کہ انہیں کتنے پیرامیٹرز کی توقع ہے)10 function _readParams(uint _paramNum) internal returns (uint[] memory) {سب دکھائیںہم کال ڈیٹا سے ہی اپنے پاس موجود پیرامیٹرز کی تعداد حاصل کر سکتے ہیں، لیکن جو فنکشنز ہمیں کال کرتے ہیں وہ جانتے ہیں کہ انہیں کتنے پیرامیٹرز کی توقع ہے۔ یہ زیادہ آسان ہے کہ ہم انہیں بتانے دیں۔
1 // وہ پیرامیٹرز جو ہم نے پڑھے2 uint[] memory params = new uint[](_paramNum);34 // پیرامیٹرز بائٹ 4 سے شروع ہوتے ہیں، اس سے پہلے یہ فنکشن کے دستخط (signature) ہیں5 uint _atByte = 4;67 for(uint i=0; i<_paramNum; i++) {8 (_atByte, params[i]) = _readParam(_atByte);9 }سب دکھائیںپیرامیٹرز کو اس وقت تک پڑھیں جب تک کہ آپ کے پاس مطلوبہ تعداد نہ آجائے۔ اگر ہم کال ڈیٹا کے اختتام سے آگے نکل جاتے ہیں، تو _readParams کال کو ریورٹ (revert) کر دے گا۔
12 return(params);3 } // readParams45 // _readParams کی جانچ کے لیے، چار پیرامیٹرز پڑھنے کا ٹیسٹ کریں6 function fourParam() public7 returns (uint256,uint256,uint256,uint256)8 {9 uint[] memory params;10 params = _readParams(4);11 return (params[0], params[1], params[2], params[3]);12 } // fourParamسب دکھائیںFoundry کا ایک بڑا فائدہ یہ ہے کہ یہ ٹیسٹس کو Solidity میں لکھنے کی اجازت دیتا ہے (نیچے کیش کی ٹیسٹنگ دیکھیں)۔ اس سے یونٹ ٹیسٹس بہت آسان ہو جاتے ہیں۔ یہ ایک فنکشن ہے جو چار پیرامیٹرز پڑھتا ہے اور انہیں واپس کرتا ہے تاکہ ٹیسٹ تصدیق کر سکے کہ وہ درست تھے۔
1 // ایک قدر حاصل کریں، وہ بائٹس واپس کریں جو اسے انکوڈ کریں گے (اگر ممکن ہو تو کیشے کا استعمال کرتے ہوئے)2 function encodeVal(uint _val) public view returns(bytes memory) {encodeVal ایک فنکشن ہے جسے آف چین کوڈ کال کرتا ہے تاکہ ایسا کال ڈیٹا بنانے میں مدد ملے جو کیش استعمال کرتا ہو۔ یہ ایک واحد ویلیو وصول کرتا ہے اور اسے انکوڈ کرنے والی بائٹس واپس کرتا ہے۔ یہ فنکشن ایک view ہے، اس لیے اسے ٹرانزیکشن کی ضرورت نہیں ہوتی اور جب اسے بیرونی طور پر کال کیا جاتا ہے تو اس پر کوئی گیس خرچ نہیں ہوتی۔
1 uint _key = val2key[_val];23 // قدر ابھی کیشے میں نہیں ہے، اسے شامل کریں4 if (_key == 0)5 return bytes.concat(INTO_CACHE, bytes32(_val));EVM میں تمام غیر شروع شدہ (uninitialized) اسٹوریج کو صفر فرض کیا جاتا ہے۔ لہذا اگر ہم کسی ایسی ویلیو کی کی (key) تلاش کرتے ہیں جو وہاں نہیں ہے، تو ہمیں صفر ملتا ہے۔ اس صورت میں اسے انکوڈ کرنے والی بائٹس INTO_CACHE ہوتی ہیں (تاکہ اگلی بار اسے کیش کیا جا سکے)، جس کے بعد اصل ویلیو ہوتی ہے۔
1 // اگر کلید <0x10 ہے، تو اسے سنگل بائٹ کے طور پر واپس کریں2 if (_key < 0x10)3 return bytes.concat(bytes1(uint8(_key)));سنگل بائٹس سب سے آسان ہیں۔ ہم صرف bytes.concat (opens in a new tab) کا استعمال کرتے ہیں تاکہ bytes<n> ٹائپ کو بائٹ ایرے میں تبدیل کیا جا سکے جو کسی بھی لمبائی کی ہو سکتی ہے۔ نام کے باوجود، یہ تب بھی ٹھیک کام کرتا ہے جب اسے صرف ایک آرگومنٹ فراہم کیا جائے۔
1 // دو بائٹ کی قدر، جسے 0x1vvv کے طور پر انکوڈ کیا گیا ہے2 if (_key < 0x1000)3 return bytes.concat(bytes2(uint16(_key) | 0x1000));جب ہمارے پاس کوئی کی (key) ہوتی ہے جو 163 سے کم ہوتی ہے، تو ہم اسے دو بائٹس میں ظاہر کر سکتے ہیں۔ ہم پہلے _key کو، جو کہ 256 بٹ کی ویلیو ہے، 16 بٹ کی ویلیو میں تبدیل کرتے ہیں اور پہلی بائٹ میں اضافی بائٹس کی تعداد شامل کرنے کے لیے لاجیکل OR کا استعمال کرتے ہیں۔ پھر ہم اسے bytes2 ویلیو میں ڈال دیتے ہیں، جسے bytes میں تبدیل کیا جا سکتا ہے۔
1 // شاید مندرجہ ذیل لائنوں کو لوپ کے طور پر کرنے کا کوئی ہوشیار طریقہ موجود ہے،2 // لیکن یہ ایک ویو (view) فنکشن ہے اس لیے میں پروگرامر کے وقت اور3 // سادگی کے لیے آپٹمائز کر رہا ہوں۔45 if (_key < 16*256**2)6 return bytes.concat(bytes3(uint24(_key) | (0x2 * 16 * 256**2)));7 if (_key < 16*256**3)8 return bytes.concat(bytes4(uint32(_key) | (0x3 * 16 * 256**3)));9 .10 .11 .12 if (_key < 16*256**14)13 return bytes.concat(bytes15(uint120(_key) | (0xE * 16 * 256**14)));14 if (_key < 16*256**15)15 return bytes.concat(bytes16(uint128(_key) | (0xF * 16 * 256**15)));سب دکھائیںدیگر ویلیوز (3 بائٹس، 4 بائٹس، وغیرہ) کو اسی طرح ہینڈل کیا جاتا ہے، بس مختلف فیلڈ سائز کے ساتھ۔
1 // اگر ہم یہاں پہنچتے ہیں، تو کچھ غلط ہے۔2 revert("Error in encodeVal, should not happen");اگر ہم یہاں پہنچتے ہیں تو اس کا مطلب ہے کہ ہمیں ایک ایسی کی (key) ملی ہے جو 16*25615 سے کم نہیں ہے۔ لیکن cacheWrite کیز کو محدود کرتا ہے اس لیے ہم 14*25616 تک بھی نہیں پہنچ سکتے (جس کی پہلی بائٹ 0xFE ہوگی، اس لیے یہ DONT_CACHE کی طرح نظر آئے گی)۔ لیکن اگر مستقبل کا کوئی پروگرامر کوئی بگ متعارف کراتا ہے تو اس صورت میں ٹیسٹ شامل کرنے پر ہمیں زیادہ لاگت نہیں آتی۔
کیش کی ٹیسٹنگ
Foundry کے فوائد میں سے ایک یہ ہے کہ یہ آپ کو Solidity میں ٹیسٹ لکھنے کی اجازت دیتا ہے (opens in a new tab)، جس سے یونٹ ٹیسٹ لکھنا آسان ہو جاتا ہے۔ Cache کلاس کے ٹیسٹ یہاں (opens in a new tab) ہیں۔ چونکہ ٹیسٹنگ کوڈ دہرایا جاتا ہے، جیسا کہ ٹیسٹس میں ہوتا ہے، یہ مضمون صرف دلچسپ حصوں کی وضاحت کرتا ہے۔
1 } // encodeVal23} // کیشے1// SPDX-License-Identifier: UNLICENSED2pragma solidity ^0.8.13;34import "forge-std/Test.sol";567// کنسول کے لیے `forge test -vv` چلانے کی ضرورت ہے۔8import "forge-std/console.sol";یہ صرف بوائلرپلیٹ (boilerplate) ہے جو ٹیسٹ پیکج اور console.log استعمال کرنے کے لیے ضروری ہے۔
1import "src/Cache.sol";ہمیں اس کنٹریکٹ کو جاننے کی ضرورت ہے جس کا ہم ٹیسٹ کر رہے ہیں۔
1contract CacheTest is Test {2 Cache cache;34 function setUp() public {5 cache = new Cache();6 }setUp فنکشن ہر ٹیسٹ سے پہلے کال کیا جاتا ہے۔ اس صورت میں ہم صرف ایک نئی کیش بناتے ہیں، تاکہ ہمارے ٹیسٹ ایک دوسرے کو متاثر نہ کریں۔
1 function testCaching() public {ٹیسٹس وہ فنکشنز ہوتے ہیں جن کے نام test سے شروع ہوتے ہیں۔ یہ فنکشن بنیادی کیش فعالیت کو چیک کرتا ہے، ویلیوز لکھتا ہے اور انہیں دوبارہ پڑھتا ہے۔
1 for(uint i=1; i<5000; i++) {2 cache.cacheWrite(i*i);3 }45 for(uint i=1; i<5000; i++) {6 assertEq(cache.cacheRead(i), i*i);اس طرح آپ اصل ٹیسٹنگ کرتے ہیں، assert... فنکشنز (opens in a new tab) کا استعمال کرتے ہوئے۔ اس صورت میں، ہم چیک کرتے ہیں کہ جو ویلیو ہم نے لکھی ہے وہی ہم نے پڑھی ہے۔ ہم cache.cacheWrite کے نتیجے کو مسترد کر سکتے ہیں کیونکہ ہم جانتے ہیں کہ کیش کیز لینیئر (linearly) طور پر تفویض کی جاتی ہیں۔
1 }2 } // testCaching345 // ایک ہی قدر کو کئی بار کیشے کریں، یقینی بنائیں کہ کلید وہی6 // رہتی ہے7 function testRepeatCaching() public {8 for(uint i=1; i<100; i++) {9 uint _key1 = cache.cacheWrite(i);10 uint _key2 = cache.cacheWrite(i);11 assertEq(_key1, _key2);12 }سب دکھائیںپہلے ہم ہر ویلیو کو دو بار کیش میں لکھتے ہیں اور اس بات کو یقینی بناتے ہیں کہ کیز ایک جیسی ہیں (یعنی دوسری بار لکھنا واقعی نہیں ہوا)۔
1 for(uint i=1; i<100; i+=3) {2 uint _key = cache.cacheWrite(i);3 assertEq(_key, i);4 }5 } // testRepeatCachingنظریاتی طور پر کوئی ایسا بگ ہو سکتا ہے جو لگاتار کیش رائٹس (writes) کو متاثر نہ کرے۔ لہذا یہاں ہم کچھ ایسی رائٹس کرتے ہیں جو لگاتار نہیں ہیں اور دیکھتے ہیں کہ ویلیوز اب بھی دوبارہ نہیں لکھی گئیں۔
1 // میموری بفر سے ایک uint پڑھیں (یہ یقینی بنانے کے لیے کہ ہمیں وہ پیرامیٹرز واپس ملیں2 // جو ہم نے بھیجے تھے)3 function toUint256(bytes memory _bytes, uint256 _start) internal pure4 returns (uint256)bytes memory بفر سے 256 بٹ کا ورڈ پڑھیں۔ یہ یوٹیلیٹی فنکشن ہمیں اس بات کی تصدیق کرنے دیتا ہے کہ جب ہم کیش استعمال کرنے والی فنکشن کال چلاتے ہیں تو ہمیں درست نتائج موصول ہوتے ہیں۔
1 {2 require(_bytes.length >= _start + 32, "toUint256_outOfBounds");3 uint256 tempUint;45 assembly {6 tempUint := mload(add(add(_bytes, 0x20), _start))7 }Yul uint256 سے آگے ڈیٹا اسٹرکچرز کو سپورٹ نہیں کرتا، لہذا جب آپ کسی زیادہ نفیس ڈیٹا اسٹرکچر کا حوالہ دیتے ہیں، جیسے کہ میموری بفر _bytes، تو آپ کو اس اسٹرکچر کا ایڈریس ملتا ہے۔ Solidity bytes memory ویلیوز کو 32 بائٹ ورڈ کے طور پر اسٹور کرتی ہے جس میں لمبائی ہوتی ہے، جس کے بعد اصل بائٹس ہوتی ہیں، لہذا بائٹ نمبر _start حاصل کرنے کے لیے ہمیں _bytes+32+_start کا حساب لگانا ہوگا۔
12 return tempUint;3 } // toUint25645 // fourParams() کے لیے فنکشن کے دستخط، بشکریہ6 // https://www.4byte.directory/signatures/?bytes4_signature=0x3edc1e6d7 bytes4 constant FOUR_PARAMS = 0x3edc1e6d;89 // یہ دیکھنے کے لیے کہ ہمیں درست قدریں واپس مل رہی ہیں، بس کچھ مستقل (constant) قدریں10 uint256 constant VAL_A = 0xDEAD60A7;11 uint256 constant VAL_B = 0xBEEF;12 uint256 constant VAL_C = 0x600D;13 uint256 constant VAL_D = 0x600D60A7;سب دکھائیںکچھ کنسٹنٹس (constants) جن کی ہمیں ٹیسٹنگ کے لیے ضرورت ہے۔
1 function testReadParam() public {fourParams() کو کال کریں، جو ایک فنکشن ہے جو readParams استعمال کرتا ہے، تاکہ یہ ٹیسٹ کیا جا سکے کہ ہم پیرامیٹرز کو درست طریقے سے پڑھ سکتے ہیں۔
1 address _cacheAddr = address(cache);2 bool _success;3 bytes memory _callInput;4 bytes memory _callOutput;ہم کیش کا استعمال کرتے ہوئے کسی فنکشن کو کال کرنے کے لیے عام ABI میکانزم استعمال نہیں کر سکتے، اس لیے ہمیں لو لیول (low level) <address>.call() (opens in a new tab) میکانزم استعمال کرنے کی ضرورت ہے۔ وہ میکانزم ان پٹ کے طور پر bytes memory لیتا ہے، اور اسے (نیز ایک بولین ویلیو) آؤٹ پٹ کے طور پر واپس کرتا ہے۔
1 // پہلی کال، کیشے خالی ہے2 _callInput = bytes.concat(3 FOUR_PARAMS,ایک ہی کنٹریکٹ کے لیے کیشڈ فنکشنز (براہ راست ٹرانزیکشنز سے کالز کے لیے) اور نان کیشڈ فنکشنز (دوسرے اسمارٹ کنٹریکٹس سے کالز کے لیے) دونوں کو سپورٹ کرنا مفید ہے۔ ایسا کرنے کے لیے ہمیں درست فنکشن کو کال کرنے کے لیے Solidity میکانزم پر انحصار جاری رکھنے کی ضرورت ہے، بجائے اس کے کہ ہر چیز کو ایک fallback فنکشن (opens in a new tab) میں ڈال دیا جائے۔ ایسا کرنے سے کمپوزایبلٹی (composability) بہت آسان ہو جاتی ہے۔ زیادہ تر معاملات میں فنکشن کی شناخت کے لیے ایک بائٹ کافی ہوگی، اس لیے ہم تین بائٹس (16*3=48 گیس) ضائع کر رہے ہیں۔ تاہم، جب میں یہ لکھ رہا ہوں تو ان 48 گیس کی قیمت 0.07 سینٹ ہے، جو کہ آسان، کم بگ والے کوڈ کی ایک معقول قیمت ہے۔
1 // پہلی قدر، اسے کیشے میں شامل کریں2 cache.INTO_CACHE(),3 bytes32(VAL_A),پہلی ویلیو: ایک فلیگ (flag) جو یہ بتاتا ہے کہ یہ ایک مکمل ویلیو ہے جسے کیش میں لکھنے کی ضرورت ہے، جس کے بعد ویلیو کی 32 بائٹس ہیں۔ باقی تین ویلیوز بھی اسی طرح کی ہیں، سوائے اس کے کہ VAL_B کو کیش میں نہیں لکھا جاتا اور VAL_C تیسرا اور چوتھا دونوں پیرامیٹر ہے۔
1 .2 .3 .4 );5 (_success, _callOutput) = _cacheAddr.call(_callInput);یہ وہ جگہ ہے جہاں ہم اصل میں Cache کنٹریکٹ کو کال کرتے ہیں۔
1 assertEq(_success, true);ہمیں توقع ہے کہ کال کامیاب ہوگی۔
1 assertEq(cache.cacheRead(1), VAL_A);2 assertEq(cache.cacheRead(2), VAL_C);ہم ایک خالی کیش سے شروع کرتے ہیں اور پھر VAL_A اور اس کے بعد VAL_C شامل کرتے ہیں۔ ہمیں توقع ہوگی کہ پہلے کی کی (key) 1 ہوگی، اور دوسرے کی 2 ہوگی۔
1 assertEq(toUint256(_callOutput,0), VAL_A);2 assertEq(toUint256(_callOutput,32), VAL_B);3 assertEq(toUint256(_callOutput,64), VAL_C);4 assertEq(toUint256(_callOutput,96), VAL_C);آؤٹ پٹ چار پیرامیٹرز ہیں۔ یہاں ہم تصدیق کرتے ہیں کہ یہ درست ہے۔
1 // دوسری کال، ہم کیشے استعمال کر سکتے ہیں2 _callInput = bytes.concat(3 FOUR_PARAMS,45 // کیشے میں پہلی قدر6 bytes1(0x01),16 سے کم کیش کیز صرف ایک بائٹ کی ہوتی ہیں۔
1 // دوسری قدر، اسے کیشے میں شامل نہ کریں2 cache.DONT_CACHE(),3 bytes32(VAL_B),45 // تیسری اور چوتھی قدریں، ایک ہی قدر6 bytes1(0x02),7 bytes1(0x02)8 );9 .10 .11 .12 } // testReadParamسب دکھائیںکال کے بعد کے ٹیسٹ پہلی کال کے بعد کے ٹیسٹوں سے بالکل ملتے جلتے ہیں۔
1 function testEncodeVal() public {یہ فنکشن testReadParam سے ملتا جلتا ہے، سوائے اس کے کہ پیرامیٹرز کو واضح طور پر لکھنے کے بجائے ہم encodeVal() استعمال کرتے ہیں۔
1 .2 .3 .4 _callInput = bytes.concat(5 FOUR_PARAMS,6 cache.encodeVal(VAL_A),7 cache.encodeVal(VAL_B),8 cache.encodeVal(VAL_C),9 cache.encodeVal(VAL_D)10 );11 .12 .13 .14 assertEq(_callInput.length, 4+1*4);15 } // testEncodeValسب دکھائیںtestEncodeVal() میں واحد اضافی ٹیسٹ یہ تصدیق کرنا ہے کہ _callInput کی لمبائی درست ہے۔ پہلی کال کے لیے یہ 4+33*4 ہے۔ دوسری کے لیے، جہاں ہر ویلیو پہلے سے ہی کیش میں موجود ہے، یہ 4+1*4 ہے۔
1 // جب کلید ایک بائٹ سے زیادہ ہو تو encodeVal کا ٹیسٹ کریں2 // زیادہ سے زیادہ تین بائٹس کیونکہ کیشے کو چار بائٹس تک بھرنے میں بہت3 // وقت لگتا ہے۔4 function testEncodeValBig() public {5 // کیشے میں متعدد قدریں رکھیں۔6 // چیزوں کو سادہ رکھنے کے لیے، قدر n کے لیے کلید n استعمال کریں۔7 for(uint i=1; i<0x1FFF; i++) {8 cache.cacheWrite(i);9 }سب دکھائیںاوپر دیا گیا testEncodeVal فنکشن کیش میں صرف چار ویلیوز لکھتا ہے، اس لیے فنکشن کا وہ حصہ جو ملٹی بائٹ ویلیوز سے نمٹتا ہے (opens in a new tab) چیک نہیں کیا جاتا۔ لیکن وہ کوڈ پیچیدہ ہے اور اس میں غلطی کا امکان ہے۔
اس فنکشن کا پہلا حصہ ایک لوپ ہے جو 1 سے 0x1FFF تک کی تمام ویلیوز کو ترتیب سے کیش میں لکھتا ہے، تاکہ ہم ان ویلیوز کو انکوڈ کر سکیں اور جان سکیں کہ وہ کہاں جا رہی ہیں۔
1 .2 .3 .45 _callInput = bytes.concat(6 FOUR_PARAMS,7 cache.encodeVal(0x000F), // ایک بائٹ 0x0F8 cache.encodeVal(0x0010), // دو بائٹس 0x10109 cache.encodeVal(0x0100), // دو بائٹس 0x110010 cache.encodeVal(0x1000) // تین بائٹس 0x20100011 );سب دکھائیںایک بائٹ، دو بائٹ، اور تین بائٹ ویلیوز کا ٹیسٹ کریں۔ ہم اس سے آگے ٹیسٹ نہیں کرتے کیونکہ کافی اسٹیک اندراجات (کم از کم 0x10000000، تقریباً ایک چوتھائی ارب) لکھنے میں بہت زیادہ وقت لگے گا۔
1 .2 .3 .4 .5 } // testEncodeValBig678 // ٹیسٹ کریں کہ انتہائی چھوٹے بفر کے ساتھ ہمیں ریورٹ (revert) ملتا ہے9 function testShortCalldata() public {سب دکھائیںٹیسٹ کریں کہ غیر معمولی صورت میں کیا ہوتا ہے جہاں کافی پیرامیٹرز نہیں ہوتے ہیں۔
1 .2 .3 .4 (_success, _callOutput) = _cacheAddr.call(_callInput);5 assertEq(_success, false);6 } // testShortCalldataچونکہ یہ ریورٹ (revert) ہوتا ہے، اس لیے ہمیں جو نتیجہ ملنا چاہیے وہ false ہے۔
1 // Call with cache keys that aren't there2 function testNoCacheKey() public {3 .4 .5 .6 _callInput = bytes.concat(7 FOUR_PARAMS,89 // First value, add it to the cache10 cache.INTO_CACHE(),11 bytes32(VAL_A),1213 // Second value14 bytes1(0x0F),15 bytes2(0x1234),16 bytes11(0xA10102030405060708090A)17 );سب دکھائیںاس فنکشن کو چار بالکل جائز پیرامیٹرز ملتے ہیں، سوائے اس کے کہ کیش خالی ہے اس لیے وہاں پڑھنے کے لیے کوئی ویلیوز نہیں ہیں۔
1 .2 .3 .4 // ٹیسٹ کریں کہ انتہائی لمبے بفر کے ساتھ سب کچھ ٹھیک کام کرتا ہے5 function testLongCalldata() public {6 address _cacheAddr = address(cache);7 bool _success;8 bytes memory _callInput;9 bytes memory _callOutput;1011 // پہلی کال، کیشے خالی ہے12 _callInput = bytes.concat(13 FOUR_PARAMS,1415 // پہلی قدر، اسے کیشے میں شامل کریں16 cache.INTO_CACHE(), bytes32(VAL_A),1718 // دوسری قدر، اسے کیشے میں شامل کریں19 cache.INTO_CACHE(), bytes32(VAL_B),2021 // تیسری قدر، اسے کیشے میں شامل کریں22 cache.INTO_CACHE(), bytes32(VAL_C),2324 // چوتھی قدر، اسے کیشے میں شامل کریں25 cache.INTO_CACHE(), bytes32(VAL_D),2627 // اور "خوش قسمتی" کے لیے ایک اور قدر28 bytes4(0x31112233)29 );سب دکھائیںیہ فنکشن پانچ ویلیوز بھیجتا ہے۔ ہم جانتے ہیں کہ پانچویں ویلیو کو نظر انداز کر دیا جاتا ہے کیونکہ یہ ایک درست کیش انٹری نہیں ہے، جس کی وجہ سے اگر اسے شامل نہ کیا جاتا تو ریورٹ ہو جاتا۔
1 (_success, _callOutput) = _cacheAddr.call(_callInput);2 assertEq(_success, true);3 .4 .5 .6 } // testLongCalldata78} // CacheTest9سب دکھائیںایک نمونہ ایپلی کیشن
Solidity میں ٹیسٹ لکھنا بہت اچھی بات ہے، لیکن آخر کار ایک ڈیپ (dapp) کو مفید ہونے کے لیے چین کے باہر سے آنے والی درخواستوں پر کارروائی کرنے کے قابل ہونا چاہیے۔ یہ مضمون ظاہر کرتا ہے کہ WORM کے ساتھ ڈیپ میں کیشنگ کا استعمال کیسے کیا جائے، جس کا مطلب ہے "Write Once, Read Many" (ایک بار لکھیں، کئی بار پڑھیں)۔ اگر کوئی کی (key) ابھی تک نہیں لکھی گئی ہے، تو آپ اس پر ایک ویلیو لکھ سکتے ہیں۔ اگر کی پہلے سے لکھی ہوئی ہے، تو آپ کو ریورٹ ملتا ہے۔
کنٹریکٹ
یہ کنٹریکٹ ہے (opens in a new tab)۔ یہ زیادہ تر وہی دہراتا ہے جو ہم پہلے ہی Cache اور CacheTest کے ساتھ کر چکے ہیں، اس لیے ہم صرف ان حصوں کا احاطہ کرتے ہیں جو دلچسپ ہیں۔
1import "./Cache.sol";23contract WORM is Cache {Cache کو استعمال کرنے کا سب سے آسان طریقہ اسے اپنے کنٹریکٹ میں وراثت (inherit) میں لینا ہے۔
1 function writeEntryCached() external {2 uint[] memory params = _readParams(2);3 writeEntry(params[0], params[1]);4 } // writeEntryCachedیہ فنکشن اوپر CacheTest میں fourParam سے ملتا جلتا ہے۔ چونکہ ہم ABI تصریحات (specifications) کی پیروی نہیں کرتے ہیں، اس لیے بہتر ہے کہ فنکشن میں کسی بھی پیرامیٹر کا اعلان نہ کیا جائے۔
1 // ہمیں کال کرنا آسان بنائیں2 // writeEntryCached() کے لیے فنکشن کے دستخط، بشکریہ3 // https://www.4byte.directory/signatures/?bytes4_signature=0xe4e4f2d34 bytes4 constant public WRITE_ENTRY_CACHED = 0xe4e4f2d3;وہ بیرونی کوڈ جو writeEntryCached کو کال کرتا ہے اسے worm.writeEntryCached استعمال کرنے کے بجائے دستی طور پر کال ڈیٹا بنانا ہوگا، کیونکہ ہم ABI تصریحات کی پیروی نہیں کرتے ہیں۔ اس کنسٹنٹ ویلیو کا ہونا اسے لکھنا آسان بنا دیتا ہے۔
نوٹ کریں کہ اگرچہ ہم WRITE_ENTRY_CACHED کو ایک اسٹیٹ (state) ویری ایبل کے طور پر بیان کرتے ہیں، اسے بیرونی طور پر پڑھنے کے لیے اس کے لیے گیٹر (getter) فنکشن، worm.WRITE_ENTRY_CACHED() استعمال کرنا ضروری ہے۔
1 function readEntry(uint key) public view2 returns (uint _value, address _writtenBy, uint _writtenAtBlock)ریڈ (read) فنکشن ایک view ہے، اس لیے اسے ٹرانزیکشن کی ضرورت نہیں ہوتی اور اس پر گیس خرچ نہیں ہوتی۔ نتیجے کے طور پر، پیرامیٹر کے لیے کیش استعمال کرنے کا کوئی فائدہ نہیں ہے۔ ویو فنکشنز کے ساتھ معیاری میکانزم استعمال کرنا بہتر ہے جو زیادہ آسان ہے۔
ٹیسٹنگ کوڈ
یہ کنٹریکٹ کے لیے ٹیسٹنگ کوڈ ہے (opens in a new tab)۔ ایک بار پھر، آئیے صرف اس پر نظر ڈالیں جو دلچسپ ہے۔
1 function testWReadWrite() public {2 worm.writeEntry(0xDEAD, 0x60A7);34 vm.expectRevert(bytes("entry already written"));5 worm.writeEntry(0xDEAD, 0xBEEF);یہ (vm.expectRevert) (opens in a new tab) وہ طریقہ ہے جس سے ہم Foundry ٹیسٹ میں یہ بتاتے ہیں کہ اگلی کال ناکام ہونی چاہیے، اور ناکامی کی اطلاع دی گئی وجہ کیا ہے۔ یہ اس وقت لاگو ہوتا ہے جب ہم کال ڈیٹا بنانے اور لو لیول انٹرفیس (<contract>.call(), وغیرہ) کا استعمال کرتے ہوئے کنٹریکٹ کو کال کرنے کے بجائے سنٹیکس <contract>.<function name>() استعمال کرتے ہیں۔
1 function testReadWriteCached() public {2 uint cacheGoat = worm.cacheWrite(0x60A7);یہاں ہم اس حقیقت کا استعمال کرتے ہیں کہ cacheWrite کیش کی (key) واپس کرتا ہے۔ یہ کوئی ایسی چیز نہیں ہے جس کی ہم پروڈکشن میں استعمال کرنے کی توقع کریں گے، کیونکہ cacheWrite اسٹیٹ (state) کو تبدیل کرتا ہے، اور اس لیے اسے صرف ٹرانزیکشن کے دوران کال کیا جا سکتا ہے۔ ٹرانزیکشنز کی ریٹرن ویلیوز نہیں ہوتیں، اگر ان کے نتائج ہوں تو ان نتائج کو ایونٹس کے طور پر خارج (emit) کیا جانا چاہیے۔ لہذا cacheWrite کی ریٹرن ویلیو صرف آن چین کوڈ سے قابل رسائی ہے، اور آن چین کوڈ کو پیرامیٹر کیشنگ کی ضرورت نہیں ہے۔
1 (_success,) = address(worm).call(_callInput);اس طرح ہم Solidity کو بتاتے ہیں کہ اگرچہ <contract address>.call() کی دو ریٹرن ویلیوز ہیں، ہمیں صرف پہلی کی پرواہ ہے۔
1 (_success,) = address(worm).call(_callInput);2 assertEq(_success, false);چونکہ ہم لو لیول <address>.call() فنکشن استعمال کرتے ہیں، اس لیے ہم vm.expectRevert() استعمال نہیں کر سکتے اور ہمیں اس بولین کامیابی کی ویلیو کو دیکھنا ہوگا جو ہمیں کال سے ملتی ہے۔
1 event EntryWritten(uint indexed key, uint indexed value);23 .4 .5 .67 _callInput = bytes.concat(8 worm.WRITE_ENTRY_CACHED(), worm.encodeVal(a), worm.encodeVal(b));9 vm.expectEmit(true, true, false, false);10 emit EntryWritten(a, b);11 (_success,) = address(worm).call(_callInput);سب دکھائیںیہ وہ طریقہ ہے جس سے ہم تصدیق کرتے ہیں کہ کوڈ Foundry میں ایونٹ کو درست طریقے سے خارج (emit) کرتا ہے (opens in a new tab)۔
کلائنٹ
ایک چیز جو آپ کو Solidity ٹیسٹس کے ساتھ نہیں ملتی وہ JavaScript کوڈ ہے جسے آپ کاٹ کر اپنی ایپلی کیشن میں پیسٹ کر سکتے ہیں۔ اس کوڈ کو لکھنے کے لیے میں نے WORM کو Optimism Goerli (opens in a new tab) پر ڈیپلائے کیا، جو Optimism کا (opens in a new tab) نیا ٹیسٹ نیٹ ہے۔ یہ ایڈریس 0xd34335b1d818cee54e3323d3246bd31d94e6a78a (opens in a new tab) پر ہے۔
آپ کلائنٹ کے لیے JavaScript کوڈ یہاں دیکھ سکتے ہیں (opens in a new tab)۔ اسے استعمال کرنے کے لیے:
-
گٹ (git) ریپوزٹری کو کلون کریں:
1git clone https://github.com/qbzzt/20220915-all-you-can-cache.git -
ضروری پیکجز انسٹال کریں:
1cd javascript2yarn -
کنفیگریشن فائل کاپی کریں:
1cp .env.example .env -
اپنی کنفیگریشن کے لیے
.envمیں ترمیم کریں:پیرامیٹر ویلیو MNEMONIC ایک اکاؤنٹ کے لیے نیمونک (mnemonic) جس کے پاس ٹرانزیکشن کی ادائیگی کے لیے کافی ETH ہے۔ آپ Optimism Goerli نیٹ ورک کے لیے مفت ETH یہاں سے حاصل کر سکتے ہیں (opens in a new tab)۔ OPTIMISM_GOERLI_URL Optimism Goerli کا URL۔ پبلک اینڈ پوائنٹ، https://goerli.optimism.io، ریٹ لمیٹڈ (rate limited) ہے لیکن ہماری ضرورت کے لیے کافی ہے۔ -
index.jsچلائیں۔1node index.jsیہ نمونہ ایپلی کیشن پہلے WORM میں ایک انٹری لکھتی ہے، کال ڈیٹا اور Etherscan پر ٹرانزیکشن کا لنک دکھاتی ہے۔ پھر یہ اس انٹری کو واپس پڑھتی ہے، اور اس کی استعمال کردہ کی (key) اور انٹری میں موجود ویلیوز (ویلیو، بلاک نمبر، اور مصنف) دکھاتی ہے۔
کلائنٹ کا زیادہ تر حصہ عام Dapp JavaScript ہے۔ لہذا ایک بار پھر ہم صرف دلچسپ حصوں کا جائزہ لیں گے۔
1.2.3.4const main = async () => {5 const func = await worm.WRITE_ENTRY_CACHED()67 // ہر بار ایک نئی کلید کی ضرورت ہے8 const key = await worm.encodeVal(Number(new Date()))دیے گئے سلاٹ میں صرف ایک بار لکھا جا سکتا ہے، اس لیے ہم ٹائم اسٹیمپ کا استعمال کرتے ہیں تاکہ یہ یقینی بنایا جا سکے کہ ہم سلاٹس کو دوبارہ استعمال نہیں کرتے ہیں۔
1const val = await worm.encodeVal("0x600D")23// ایک اندراج (entry) لکھیں4const calldata = func + key.slice(2) + val.slice(2)Ethers توقع کرتا ہے کہ کال ڈیٹا ایک ہیکس (hex) اسٹرنگ ہوگا، 0x جس کے بعد ہیکسا ڈیسیمل ہندسوں کی جفت تعداد ہوگی۔ چونکہ key اور val دونوں 0x سے شروع ہوتے ہیں، اس لیے ہمیں ان ہیڈرز کو ہٹانے کی ضرورت ہے۔
1const tx = await worm.populateTransaction.writeEntryCached()2tx.data = calldata34sentTx = await wallet.sendTransaction(tx)Solidity ٹیسٹنگ کوڈ کی طرح، ہم کیشڈ فنکشن کو عام طور پر کال نہیں کر سکتے۔ اس کے بجائے، ہمیں لو لیول میکانزم استعمال کرنے کی ضرورت ہے۔
1 .2 .3 .4 // ابھی لکھا گیا اندراج پڑھیں5 const realKey = '0x' + key.slice(4) // FF فلیگ کو ہٹا دیں6 const entryRead = await worm.readEntry(realKey)7 .8 .9 .سب دکھائیںانٹریز پڑھنے کے لیے ہم عام میکانزم استعمال کر سکتے ہیں۔ view فنکشنز کے ساتھ پیرامیٹر کیشنگ استعمال کرنے کی کوئی ضرورت نہیں ہے۔
نتیجہ
اس مضمون میں موجود کوڈ ایک پروف آف کانسیپٹ (proof of concept) ہے، جس کا مقصد خیال کو سمجھنے میں آسان بنانا ہے۔ پروڈکشن کے لیے تیار سسٹم کے لیے آپ کچھ اضافی فعالیت لاگو کرنا چاہیں گے:
-
ان ویلیوز کو ہینڈل کریں جو
uint256نہیں ہیں۔ مثال کے طور پر، اسٹرنگز (strings)۔ -
گلوبل کیش کے بجائے، شاید صارفین اور کیشز کے درمیان میپنگ ہو۔ مختلف صارفین مختلف ویلیوز استعمال کرتے ہیں۔
-
ایڈریسز کے لیے استعمال ہونے والی ویلیوز دیگر مقاصد کے لیے استعمال ہونے والی ویلیوز سے الگ ہوتی ہیں۔ صرف ایڈریسز کے لیے ایک الگ کیش رکھنا سمجھداری کی بات ہو سکتی ہے۔
-
فی الحال، کیش کیز "پہلے آئیں، سب سے چھوٹی کی" الگورتھم پر ہیں۔ پہلی سولہ ویلیوز کو ایک بائٹ کے طور پر بھیجا جا سکتا ہے۔ اگلی 4080 ویلیوز کو دو بائٹس کے طور پر بھیجا جا سکتا ہے۔ اگلی تقریباً دس لاکھ ویلیوز تین بائٹس کی ہیں، وغیرہ۔ ایک پروڈکشن سسٹم کو کیش انٹریز پر استعمال کے کاؤنٹرز رکھنے چاہئیں اور انہیں دوبارہ منظم کرنا چاہیے تاکہ سولہ سب سے عام ویلیوز ایک بائٹ کی ہوں، اگلی 4080 سب سے عام ویلیوز دو بائٹس کی ہوں، وغیرہ۔
تاہم، یہ ممکنہ طور پر ایک خطرناک آپریشن ہے۔ واقعات کی درج ذیل ترتیب کا تصور کریں:
-
Noam Naive اس ایڈریس کو انکوڈ کرنے کے لیے
encodeValکو کال کرتا ہے جس پر وہ ٹوکن بھیجنا چاہتا ہے۔ وہ ایڈریس ایپلی کیشن پر استعمال ہونے والے پہلے ایڈریسز میں سے ایک ہے، اس لیے انکوڈ شدہ ویلیو 0x06 ہے۔ یہ ایکviewفنکشن ہے، ٹرانزیکشن نہیں، اس لیے یہ Noam اور اس کے استعمال کردہ نوڈ کے درمیان ہے، اور کسی اور کو اس کے بارے میں معلوم نہیں ہے۔ -
Owen Owner کیش ری آرڈرنگ (reordering) آپریشن چلاتا ہے۔ بہت کم لوگ دراصل اس ایڈریس کو استعمال کرتے ہیں، اس لیے اب اسے 0x201122 کے طور پر انکوڈ کیا گیا ہے۔ ایک مختلف ویلیو، 1018، کو 0x06 تفویض کیا گیا ہے۔
-
Noam Naive اپنے ٹوکن 0x06 پر بھیجتا ہے۔ وہ ایڈریس
0x0000000000000000000000000de0b6b3a7640000پر جاتے ہیں، اور چونکہ کوئی بھی اس ایڈریس کی پرائیویٹ کی (private key) نہیں جانتا، اس لیے وہ وہیں پھنس جاتے ہیں۔ Noam خوش نہیں ہے۔
اس مسئلے کو حل کرنے کے طریقے موجود ہیں، اور کیش ری آرڈر کے دوران میم پول (mempool) میں موجود ٹرانزیکشنز کے متعلقہ مسئلے کو بھی، لیکن آپ کو اس سے آگاہ ہونا چاہیے۔
-
میں نے یہاں Optimism کے ساتھ کیشنگ کا مظاہرہ کیا، کیونکہ میں Optimism کا ملازم ہوں اور یہ وہ رول اپ ہے جسے میں سب سے بہتر جانتا ہوں۔ لیکن اسے کسی بھی ایسے رول اپ کے ساتھ کام کرنا چاہیے جو اندرونی پروسیسنگ کے لیے کم سے کم لاگت وصول کرتا ہو، تاکہ اس کے مقابلے میں ٹرانزیکشن ڈیٹا کو L1 پر لکھنا بڑا خرچ ہو۔
میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔
صفحہ کی آخری اپ ڈیٹ: 3 مارچ، 2026