কন্ট্রাক্ট সাইজ লিমিট মোকাবেলা করতে কন্ট্রাক্ট ছোট করা
কেন একটি লিমিট বা সীমা আছে?
২২ নভেম্বর, ২০১৬ (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 অ্যাটাকের সম্ভাবনা তৈরি হয়।
প্রাথমিকভাবে এটি খুব একটা সমস্যা ছিল না কারণ একটি স্বাভাবিক কন্ট্রাক্ট সাইজ লিমিট হলো ব্লক গ্যাস লিমিট। স্পষ্টতই, একটি কন্ট্রাক্টকে এমন একটি লেনদেন-এর মধ্যে ডিপ্লয় করতে হবে যা কন্ট্রাক্টের সমস্ত বাইটকোড ধারণ করে। আপনি যদি একটি ব্লক-এ শুধুমাত্র সেই একটি লেনদেন অন্তর্ভুক্ত করেন, তবে আপনি সেই সমস্ত গ্যাস ব্যবহার করতে পারেন, কিন্তু এটি অসীম নয়। লন্ডন আপগ্রেড-এর পর থেকে, নেটওয়ার্ক-এর চাহিদার ওপর নির্ভর করে ব্লক গ্যাস লিমিট 15M থেকে 30M ইউনিটের মধ্যে পরিবর্তিত হতে পারে।
নিচে আমরা কিছু পদ্ধতি দেখব যা তাদের সম্ভাব্য প্রভাবের ক্রমানুসারে সাজানো হয়েছে। ওজন কমানোর দৃষ্টিকোণ থেকে এটি চিন্তা করুন। কারো লক্ষ্য ওজনে (আমাদের ক্ষেত্রে 24kb) পৌঁছানোর সেরা কৌশল হলো প্রথমে বড় প্রভাব ফেলে এমন পদ্ধতিগুলোতে ফোকাস করা। বেশিরভাগ ক্ষেত্রে শুধুমাত্র আপনার ডায়েট ঠিক করলেই আপনি সেখানে পৌঁছাতে পারবেন, তবে মাঝে মাঝে আপনার আরও কিছুটা বেশি কিছুর প্রয়োজন হতে পারে। তখন আপনি কিছু ব্যায়াম (মাঝারি প্রভাব) বা এমনকি সাপ্লিমেন্ট (ছোট প্রভাব) যোগ করতে পারেন।
বড় প্রভাব
আপনার কন্ট্রাক্টগুলো আলাদা করুন
এটি সর্বদা আপনার প্রথম পদক্ষেপ হওয়া উচিত। আপনি কীভাবে কন্ট্রাক্টটিকে একাধিক ছোট কন্ট্রাক্টে আলাদা করতে পারেন? এটি সাধারণত আপনাকে আপনার কন্ট্রাক্টগুলোর জন্য একটি ভালো আর্কিটেকচার তৈরি করতে বাধ্য করে। কোড পড়ার সুবিধার দৃষ্টিকোণ থেকে ছোট কন্ট্রাক্টগুলো সর্বদা বেশি পছন্দনীয়। কন্ট্রাক্টগুলো ভাগ করার জন্য, নিজেকে জিজ্ঞাসা করুন:
- কোন ফাংশনগুলো একসাথে থাকে? প্রতিটি ফাংশনের সেট তার নিজস্ব কন্ট্রাক্টে রাখা সবচেয়ে ভালো হতে পারে।
- কোন ফাংশনগুলোর জন্য কন্ট্রাক্ট স্টেট পড়ার প্রয়োজন নেই বা শুধুমাত্র স্টেটের একটি নির্দিষ্ট সাবসেট প্রয়োজন?
- আপনি কি স্টোরেজ এবং ফাংশনালিটি আলাদা করতে পারেন?
লাইব্রেরি
স্টোরেজ থেকে ফাংশনালিটি কোড সরিয়ে নেওয়ার একটি সহজ উপায় হলো একটি লাইব্রেরি (opens in a new tab) ব্যবহার করা। লাইব্রেরি ফাংশনগুলোকে ইন্টারনাল (internal) হিসেবে ডিক্লেয়ার করবেন না কারণ সেগুলো কম্পাইলেশনের সময় সরাসরি কন্ট্রাক্টে যোগ করা হবে (opens in a new tab)। তবে আপনি যদি পাবলিক (public) ফাংশন ব্যবহার করেন, তবে সেগুলো আসলে একটি আলাদা লাইব্রেরি কন্ট্রাক্টে থাকবে। লাইব্রেরির ব্যবহার আরও সুবিধাজনক করতে using for (opens in a new tab) ব্যবহার করার কথা বিবেচনা করুন।
প্রক্সি
একটি আরও উন্নত কৌশল হতে পারে একটি প্রক্সি সিস্টেম। লাইব্রেরিগুলো ব্যাকগ্রাউন্ডে DELEGATECALL ব্যবহার করে যা কলিং কন্ট্রাক্টের স্টেট দিয়ে অন্য একটি কন্ট্রাক্টের ফাংশন এক্সিকিউট করে। প্রক্সি সিস্টেম সম্পর্কে আরও জানতে এই ব্লগ পোস্টটি (opens in a new tab) দেখুন। এগুলো আপনাকে আরও ফাংশনালিটি দেয়, যেমন, এগুলো আপগ্রেড করার সুবিধা দেয়, তবে এগুলো অনেক জটিলতাও যোগ করে। আমি শুধুমাত্র কন্ট্রাক্টের সাইজ কমানোর জন্য এগুলো যোগ করব না, যদি না কোনো কারণে এটি আপনার একমাত্র বিকল্প হয়।
মাঝারি প্রভাব
ফাংশনগুলো সরিয়ে ফেলুন
এটি স্পষ্ট হওয়া উচিত। ফাংশনগুলো একটি কন্ট্রাক্টের সাইজ বেশ কিছুটা বাড়িয়ে দেয়।
- এক্সটার্নাল (External): অনেক সময় আমরা সুবিধার জন্য অনেক ভিউ (view) ফাংশন যোগ করি। সাইজ লিমিটে না পৌঁছানো পর্যন্ত এটি পুরোপুরি ঠিক আছে। এরপর আপনি হয়তো একেবারে প্রয়োজনীয় ফাংশনগুলো ছাড়া বাকি সব সরিয়ে ফেলার কথা গুরুত্ব সহকারে ভাবতে পারেন।
- ইন্টারনাল (Internal): আপনি ইন্টারনাল/প্রাইভেট ফাংশনগুলোও সরিয়ে ফেলতে পারেন এবং যতক্ষণ ফাংশনটি শুধুমাত্র একবার কল করা হয় ততক্ষণ কোডটি ইনলাইন (inline) করতে পারেন।
অতিরিক্ত ভেরিয়েবল এড়িয়ে চলুন
1function get(uint id) returns (address,address) {2 MyStruct memory myStruct = myStructs[id];3 return (myStruct.addr1, myStruct.addr2);4}1function get(uint id) returns (address,address) {2 return (myStructs[id].addr1, myStructs[id].addr2);3}এরকম একটি সাধারণ পরিবর্তন 0.28kb-এর পার্থক্য তৈরি করে। সম্ভাবনা আছে যে আপনি আপনার কন্ট্রাক্টগুলোতে এরকম অনেক পরিস্থিতি খুঁজে পেতে পারেন এবং সেগুলো একসাথে মিলে উল্লেখযোগ্য পরিমাণে সাইজ কমাতে পারে।
এরর মেসেজ ছোট করুন
দীর্ঘ রিভার্ট (revert) মেসেজ এবং বিশেষ করে অনেকগুলো ভিন্ন ভিন্ন রিভার্ট মেসেজ কন্ট্রাক্টকে বড় করে তুলতে পারে। এর পরিবর্তে ছোট এরর কোড ব্যবহার করুন এবং আপনার কন্ট্রাক্টে সেগুলো ডিকোড করুন। একটি দীর্ঘ মেসেজ অনেক ছোট হয়ে যেতে পারে:
1require(msg.sender == owner, "Only the owner of this contract can call this function");1require(msg.sender == owner, "OW1");এরর মেসেজের পরিবর্তে কাস্টম এরর ব্যবহার করুন
Solidity 0.8.4 (opens in a new tab)-এ কাস্টম এরর চালু করা হয়েছে। এগুলো আপনার কন্ট্রাক্টের সাইজ কমানোর একটি দুর্দান্ত উপায়, কারণ এগুলো সিলেক্টর হিসেবে ABI-এনকোড করা থাকে (ঠিক যেমন ফাংশনগুলো থাকে)।
1error Unauthorized();2
3if (msg.sender != owner) {4 revert Unauthorized();5}অপ্টিমাইজারে একটি কম রান ভ্যালু বিবেচনা করুন
আপনি অপ্টিমাইজার সেটিংসও পরিবর্তন করতে পারেন। ডিফল্ট ভ্যালু 200-এর অর্থ হলো এটি বাইটকোডকে এমনভাবে অপ্টিমাইজ করার চেষ্টা করছে যেন একটি ফাংশন 200 বার কল করা হয়েছে। আপনি যদি এটি পরিবর্তন করে 1 করেন, তবে আপনি মূলত অপ্টিমাইজারকে প্রতিটি ফাংশন শুধুমাত্র একবার চালানোর জন্য অপ্টিমাইজ করতে বলছেন। শুধুমাত্র একবার চালানোর জন্য অপ্টিমাইজ করা ফাংশন মানে এটি ডিপ্লয়মেন্টের জন্যই অপ্টিমাইজ করা হয়েছে। মনে রাখবেন যে এটি ফাংশনগুলো চালানোর জন্য গ্যাস খরচ বাড়িয়ে দেয়, তাই আপনি হয়তো এটি করতে চাইবেন না।
ছোট প্রভাব
ফাংশনে স্ট্রাক্ট পাস করা এড়িয়ে চলুন
আপনি যদি ABIEncoderV2 (opens in a new tab) ব্যবহার করেন, তবে ফাংশনে স্ট্রাক্ট (struct) পাস না করা সহায়ক হতে পারে। প্যারামিটারটিকে স্ট্রাক্ট হিসেবে পাস করার পরিবর্তে, প্রয়োজনীয় প্যারামিটারগুলো সরাসরি পাস করুন। এই উদাহরণে আমরা আরও 0.1kb বাঁচিয়েছি।
1function get(uint id) returns (address,address) {2 return _get(myStruct);3}4
5function _get(MyStruct memory myStruct) private view returns(address,address) {6 return (myStruct.addr1, myStruct.addr2);7}1function get(uint id) returns(address,address) {2 return _get(myStructs[id].addr1, myStructs[id].addr2);3}4
5function _get(address addr1, address addr2) private view returns(address,address) {6 return (addr1, addr2);7}ফাংশন এবং ভেরিয়েবলের জন্য সঠিক ভিজিবিলিটি ডিক্লেয়ার করুন
- যেসব ফাংশন বা ভেরিয়েবল শুধুমাত্র বাইরে থেকে কল করা হয়? সেগুলোকে
public-এর পরিবর্তেexternalহিসেবে ডিক্লেয়ার করুন। - যেসব ফাংশন বা ভেরিয়েবল শুধুমাত্র কন্ট্রাক্টের ভেতর থেকে কল করা হয়? সেগুলোকে
public-এর পরিবর্তেprivateবাinternalহিসেবে ডিক্লেয়ার করুন।
মডিফায়ারগুলো সরিয়ে ফেলুন
মডিফায়ার (modifier), বিশেষ করে যখন ব্যাপকভাবে ব্যবহার করা হয়, তখন কন্ট্রাক্টের সাইজের ওপর উল্লেখযোগ্য প্রভাব ফেলতে পারে। সেগুলো সরিয়ে ফেলার কথা বিবেচনা করুন এবং এর পরিবর্তে ফাংশন ব্যবহার করুন।
1modifier checkStuff() {}2
3function doSomething() checkStuff {}1function checkStuff() private {}2
3function doSomething() { checkStuff(); }এই টিপসগুলো আপনাকে কন্ট্রাক্টের সাইজ উল্লেখযোগ্যভাবে কমাতে সাহায্য করবে। আবারও বলছি, আমি এর ওপর যথেষ্ট জোর দিতে পারব না যে, সবচেয়ে বড় প্রভাবের জন্য সম্ভব হলে সর্বদা কন্ট্রাক্টগুলো ভাগ করার দিকে ফোকাস করুন।
পেজ সর্বশেষ আপডেট: 3 মার্চ, 2026