مرکزی مواد پر جائیں

کنٹریکٹ کے سائز کی حد سے نمٹنے کے لیے کنٹریکٹس کو چھوٹا کرنا

Solidity
سمارٹ کنٹریکٹس
سٹوریج
درمیانی
مارکس واس
۲۶ جون، ۲۰۲۰
8 منٹ کا مطالعہ
صفحہ میں ترمیم کریں (opens in a new tab)

حد کیوں مقرر ہے؟

22 نومبر 2016 (opens in a new tab) کو Spurious Dragon ہارڈ فورک نے EIP-170 (opens in a new tab) متعارف کرایا جس نے سمارٹ کنٹریکٹ کے سائز کی حد 24.576 kb مقرر کی۔ ایک Solidity ڈیولپر کے طور پر آپ کے لیے اس کا مطلب یہ ہے کہ جب آپ اپنے کنٹریکٹ میں زیادہ سے زیادہ فنکشنلٹی شامل کرتے ہیں، تو کسی مقام پر آپ اس حد تک پہنچ جائیں گے اور تعیناتی کے وقت یہ ایرر دیکھیں گے:

Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.

یہ حد ڈینائل آف سروس (DOS) حملوں کو روکنے کے لیے متعارف کرائی گئی تھی۔ کسی بھی کنٹریکٹ کو کال کرنا گیس کے لحاظ سے نسبتاً سستا ہے۔ تاہم، ایتھیریم نوڈز کے لیے کنٹریکٹ کال کا اثر کال کیے گئے کنٹریکٹ کوڈ کے سائز کے لحاظ سے غیر متناسب طور پر بڑھ جاتا ہے (ڈسک سے کوڈ پڑھنا، کوڈ کی پری پروسیسنگ، مرکل ثبوت میں ڈیٹا شامل کرنا)۔ جب بھی آپ کے پاس ایسی صورتحال ہوتی ہے جہاں حملہ آور کو دوسروں کے لیے بہت زیادہ کام پیدا کرنے کے لیے کم وسائل کی ضرورت ہوتی ہے، تو وہاں DOS حملوں کا خطرہ پیدا ہو جاتا ہے۔

شروع میں یہ کم مسئلہ تھا کیونکہ کنٹریکٹ کے سائز کی ایک قدرتی حد بلاک گیس کی حد ہے۔ ظاہر ہے، ایک کنٹریکٹ کو ایک ایسی ٹرانزیکشن کے اندر تعینات کیا جانا چاہیے جس میں کنٹریکٹ کا تمام بائٹ کوڈ موجود ہو۔ اگر آپ ایک بلاک میں صرف وہی ایک ٹرانزیکشن شامل کرتے ہیں، تو آپ وہ تمام گیس استعمال کر سکتے ہیں، لیکن یہ لامحدود نہیں ہے۔ London Upgrade کے بعد سے، نیٹ ورک کی طلب کے لحاظ سے بلاک گیس کی حد 15M اور 30M یونٹس کے درمیان تبدیل ہونے کے قابل ہو گئی ہے۔

ذیل میں ہم کچھ طریقوں کا جائزہ لیں گے جنہیں ان کے ممکنہ اثرات کے لحاظ سے ترتیب دیا گیا ہے۔ اسے وزن کم کرنے کے حوالے سے سوچیں۔ کسی کے لیے اپنے ہدف کے وزن (ہمارے معاملے میں 24kb) تک پہنچنے کی بہترین حکمت عملی یہ ہے کہ پہلے بڑے اثر والے طریقوں پر توجہ مرکوز کی جائے۔ زیادہ تر معاملات میں صرف اپنی خوراک کو ٹھیک کرنے سے آپ وہاں پہنچ جائیں گے، لیکن بعض اوقات آپ کو تھوڑی مزید ضرورت ہوتی ہے۔ پھر آپ کچھ ورزش (درمیانہ اثر) یا سپلیمنٹس (چھوٹا اثر) بھی شامل کر سکتے ہیں۔

بڑا اثر

اپنے کنٹریکٹس کو الگ کریں

یہ ہمیشہ آپ کا پہلا طریقہ ہونا چاہیے۔ آپ کنٹریکٹ کو متعدد چھوٹے کنٹریکٹس میں کیسے الگ کر سکتے ہیں؟ یہ عام طور پر آپ کو اپنے کنٹریکٹس کے لیے ایک اچھا آرکیٹیکچر بنانے پر مجبور کرتا ہے۔ کوڈ کو پڑھنے میں آسانی کے نقطہ نظر سے چھوٹے کنٹریکٹس کو ہمیشہ ترجیح دی جاتی ہے۔ کنٹریکٹس کو تقسیم کرنے کے لیے، اپنے آپ سے پوچھیں:

  • کون سے فنکشنز ایک ساتھ تعلق رکھتے ہیں؟ فنکشنز کا ہر سیٹ اپنے الگ کنٹریکٹ میں بہترین ہو سکتا ہے۔
  • کن فنکشنز کو کنٹریکٹ کی حالت یا حالت کے صرف ایک مخصوص ذیلی سیٹ کو پڑھنے کی ضرورت نہیں ہے؟
  • کیا آپ سٹوریج اور فنکشنلٹی کو تقسیم کر سکتے ہیں؟

لائبریریاں

فنکشنلٹی کوڈ کو سٹوریج سے دور لے جانے کا ایک آسان طریقہ لائبریری (opens in a new tab) کا استعمال ہے۔ لائبریری فنکشنز کو انٹرنل کے طور پر ڈکلیئر نہ کریں کیونکہ وہ کمپائلیشن کے دوران براہ راست کنٹریکٹ میں شامل (opens in a new tab) ہو جائیں گے۔ لیکن اگر آپ پبلک فنکشنز استعمال کرتے ہیں، تو وہ درحقیقت ایک الگ لائبریری کنٹریکٹ میں ہوں گے۔ لائبریریوں کے استعمال کو مزید آسان بنانے کے لیے using for (opens in a new tab) کے استعمال پر غور کریں۔

پراکسیز

ایک زیادہ جدید حکمت عملی پراکسی سسٹم ہوگی۔ لائبریریاں پس منظر میں DELEGATECALL کا استعمال کرتی ہیں جو کال کرنے والے کنٹریکٹ کی حالت کے ساتھ کسی دوسرے کنٹریکٹ کے فنکشن کو آسانی سے انجام دیتا ہے۔ پراکسی سسٹمز کے بارے میں مزید جاننے کے لیے یہ بلاگ پوسٹ (opens in a new tab) دیکھیں۔ وہ آپ کو مزید فنکشنلٹی دیتے ہیں، مثلاً، وہ اپ گریڈ ایبلٹی کو فعال کرتے ہیں، لیکن وہ بہت زیادہ پیچیدگی بھی بڑھاتے ہیں۔ میں انہیں صرف کنٹریکٹ کے سائز کو کم کرنے کے لیے شامل نہیں کروں گا جب تک کہ کسی بھی وجہ سے یہ آپ کا واحد آپشن نہ ہو۔

درمیانہ اثر

فنکشنز کو ہٹائیں

یہ واضح ہونا چاہیے۔ فنکشنز کنٹریکٹ کے سائز میں کافی حد تک اضافہ کرتے ہیں۔

  • ایکسٹرنل: اکثر اوقات ہم سہولت کی وجوہات کی بنا پر بہت سے ویو فنکشنز شامل کرتے ہیں۔ یہ بالکل ٹھیک ہے جب تک کہ آپ سائز کی حد تک نہ پہنچ جائیں۔ پھر آپ کو واقعی صرف انتہائی ضروری فنکشنز کے علاوہ باقی سب کو ہٹانے کے بارے میں سوچنا چاہیے۔
  • انٹرنل: آپ انٹرنل/پرائیویٹ فنکشنز کو بھی ہٹا سکتے ہیں اور کوڈ کو آسانی سے ان لائن کر سکتے ہیں جب تک کہ فنکشن کو صرف ایک بار کال کیا جائے۔

اضافی متغیرات (variables) سے گریز کریں

function get(uint id) returns (address,address) {
    MyStruct memory myStruct = myStructs[id];
    return (myStruct.addr1, myStruct.addr2);
}
function get(uint id) returns (address,address) {
    return (myStructs[id].addr1, myStructs[id].addr2);
}

اس جیسی ایک سادہ تبدیلی 0.28kb کا فرق پیدا کرتی ہے۔ امکان ہے کہ آپ کو اپنے کنٹریکٹس میں ایسی بہت سی صورتحال مل سکتی ہیں اور وہ واقعی مل کر ایک نمایاں مقدار بن سکتی ہیں۔

ایرر پیغام کو مختصر کریں

طویل ریورٹ پیغامات اور خاص طور پر بہت سے مختلف ریورٹ پیغامات کنٹریکٹ کو بہت بڑا کر سکتے ہیں۔ اس کے بجائے مختصر ایرر کوڈز استعمال کریں اور انہیں اپنے کنٹریکٹ میں ڈی کوڈ کریں۔ ایک طویل پیغام بہت مختصر ہو سکتا ہے:

require(msg.sender == owner, "Only the owner of this contract can call this function");
require(msg.sender == owner, "OW1");

ایرر پیغامات کے بجائے کسٹم ایررز کا استعمال کریں

کسٹم ایررز کو Solidity 0.8.4 (opens in a new tab) میں متعارف کرایا گیا ہے۔ یہ آپ کے کنٹریکٹس کے سائز کو کم کرنے کا ایک بہترین طریقہ ہیں، کیونکہ وہ سلیکٹرز کے طور پر ABI-انکوڈڈ ہوتے ہیں (بالکل فنکشنز کی طرح)۔

error Unauthorized();

if (msg.sender != owner) {
    revert Unauthorized();
}

آپٹیمائزر میں کم رن ویلیو پر غور کریں

آپ آپٹیمائزر کی سیٹنگز کو بھی تبدیل کر سکتے ہیں۔ 200 کی ڈیفالٹ ویلیو کا مطلب یہ ہے کہ یہ بائٹ کوڈ کو اس طرح آپٹیمائز کرنے کی کوشش کر رہا ہے جیسے کسی فنکشن کو 200 بار کال کیا گیا ہو۔ اگر آپ اسے 1 میں تبدیل کرتے ہیں، تو آپ بنیادی طور پر آپٹیمائزر کو بتاتے ہیں کہ وہ ہر فنکشن کو صرف ایک بار چلانے کے کیس کے لیے آپٹیمائز کرے۔ صرف ایک بار چلانے کے لیے آپٹیمائز کیے گئے فنکشن کا مطلب یہ ہے کہ اسے خود تعیناتی کے لیے آپٹیمائز کیا گیا ہے۔ آگاہ رہیں کہ یہ فنکشنز کو چلانے کے لیے گیس کی لاگت کو بڑھاتا ہے، لہذا ہو سکتا ہے آپ ایسا نہ کرنا چاہیں۔

چھوٹا اثر

فنکشنز میں سٹرکٹس (structs) پاس کرنے سے گریز کریں

اگر آپ ABIEncoderV2 (opens in a new tab) استعمال کر رہے ہیں، تو یہ فنکشن میں سٹرکٹس پاس نہ کرنے میں مدد کر سکتا ہے۔ پیرامیٹر کو سٹرکٹ کے طور پر پاس کرنے کے بجائے، مطلوبہ پیرامیٹرز کو براہ راست پاس کریں۔ اس مثال میں ہم نے مزید 0.1kb کی بچت کی۔

function get(uint id) returns (address,address) {
    return _get(myStruct);
}

function _get(MyStruct memory myStruct) private view returns(address,address) {
    return (myStruct.addr1, myStruct.addr2);
}
function get(uint id) returns(address,address) {
    return _get(myStructs[id].addr1, myStructs[id].addr2);
}

function _get(address addr1, address addr2) private view returns(address,address) {
    return (addr1, addr2);
}

فنکشنز اور متغیرات کے لیے درست وزیبلٹی ڈکلیئر کریں

  • وہ فنکشنز یا متغیرات جو صرف باہر سے کال کیے جاتے ہیں؟ انہیں public کے بجائے external کے طور پر ڈکلیئر کریں۔
  • وہ فنکشنز یا متغیرات جو صرف کنٹریکٹ کے اندر سے کال کیے جاتے ہیں؟ انہیں public کے بجائے private یا internal کے طور پر ڈکلیئر کریں۔

موڈیفائرز (modifiers) کو ہٹائیں

موڈیفائرز، خاص طور پر جب کثرت سے استعمال کیے جائیں، کنٹریکٹ کے سائز پر نمایاں اثر ڈال سکتے ہیں۔ انہیں ہٹانے پر غور کریں اور اس کے بجائے فنکشنز استعمال کریں۔

modifier checkStuff() {}

function doSomething() checkStuff {}
function checkStuff() private {}

function doSomething() { checkStuff(); }

ان تجاویز سے آپ کو کنٹریکٹ کے سائز کو نمایاں طور پر کم کرنے میں مدد ملنی چاہیے۔ ایک بار پھر، میں اس بات پر جتنا بھی زور دوں کم ہے، سب سے بڑے اثر کے لیے اگر ممکن ہو تو ہمیشہ کنٹریکٹس کو تقسیم کرنے پر توجہ دیں۔

صفحہ کی آخری اپ ڈیٹ: ۳ اپریل، ۲۰۲۶