মূল কন্টেন্টে যান

স্মার্ট কন্ট্রাক্ট টেস্ট করতে কীভাবে একিডনা (Echidna) ব্যবহার করবেন

Solidity
স্মার্ট কন্ট্রাক্ট
নিরাপত্তা
টেস্টিং
ফাজিং
উন্নত
ট্রেইলঅফবিটস
10 এপ্রিল, 2020
13 মিনিট পড়ার সময়
পৃষ্ঠা সম্পাদনা করুন (opens in a new tab)

ইনস্টলেশন

Docker-এর মাধ্যমে বা প্রি-কম্পাইল করা বাইনারি ব্যবহার করে একিডনা ইনস্টল করা যেতে পারে।

Docker-এর মাধ্যমে একিডনা

docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox

শেষ কমান্ডটি একটি Docker-এ eth-security-toolbox চালায় যার আপনার বর্তমান ডিরেক্টরিতে অ্যাক্সেস রয়েছে। আপনি আপনার হোস্ট থেকে ফাইলগুলো পরিবর্তন করতে পারেন এবং Docker থেকে ফাইলগুলোতে টুলগুলো চালাতে পারেন

Docker-এর ভিতরে, রান করুন:

solc-select 0.5.11
cd /home/training

বাইনারি

https://github.com/crytic/echidna/releases/tag/v1.4.0.0 (opens in a new tab)

প্রপার্টি-ভিত্তিক ফাজিং-এর পরিচিতি

একিডনা হলো একটি প্রপার্টি-ভিত্তিক ফাজার, যা আমরা আমাদের আগের ব্লগপোস্টগুলোতে বর্ণনা করেছি (1 (opens in a new tab), 2 (opens in a new tab), 3 (opens in a new tab))।

ফাজিং

ফাজিং (opens in a new tab) নিরাপত্তা কমিউনিটিতে একটি সুপরিচিত কৌশল। প্রোগ্রামে বাগ খুঁজে বের করার জন্য কমবেশি র‍্যান্ডম ইনপুট তৈরি করা এর কাজ। প্রথাগত সফটওয়্যারের জন্য ফাজারগুলো (যেমন AFL (opens in a new tab) বা LibFuzzer (opens in a new tab)) বাগ খোঁজার ক্ষেত্রে কার্যকর টুল হিসেবে পরিচিত।

ইনপুটগুলোর সম্পূর্ণ র‍্যান্ডম জেনারেশনের বাইরেও, ভালো ইনপুট তৈরি করার জন্য অনেক কৌশল এবং পদ্ধতি রয়েছে, যার মধ্যে অন্তর্ভুক্ত:

  • প্রতিটি এক্সিকিউশন থেকে ফিডব্যাক নেওয়া এবং এটি ব্যবহার করে জেনারেশনকে গাইড করা। উদাহরণস্বরূপ, যদি একটি নতুন তৈরি করা ইনপুট একটি নতুন পাথ ডিসকভারি করতে সাহায্য করে, তবে এর কাছাকাছি নতুন ইনপুট তৈরি করা যৌক্তিক হতে পারে।
  • কাঠামোগত সীমাবদ্ধতা মেনে ইনপুট তৈরি করা। উদাহরণস্বরূপ, যদি আপনার ইনপুটে চেকসামসহ একটি হেডার থাকে, তবে ফাজারকে চেকসাম যাচাই করে এমন ইনপুট তৈরি করতে দেওয়া যৌক্তিক হবে।
  • নতুন ইনপুট তৈরি করতে পরিচিত ইনপুট ব্যবহার করা: যদি আপনার কাছে বৈধ ইনপুটের একটি বড় ডেটাসেটে অ্যাক্সেস থাকে, তবে আপনার ফাজার একেবারে শুরু থেকে জেনারেশন শুরু করার পরিবর্তে সেগুলো থেকে নতুন ইনপুট তৈরি করতে পারে। এগুলোকে সাধারণত সিডস (seeds) বলা হয়।

প্রপার্টি-ভিত্তিক ফাজিং

একিডনা ফাজারের একটি নির্দিষ্ট পরিবারের অন্তর্গত: প্রপার্টি-ভিত্তিক ফাজিং যা QuickCheck (opens in a new tab) দ্বারা ব্যাপকভাবে অনুপ্রাণিত। ক্লাসিক ফাজারের বিপরীতে যা ক্র্যাশ খুঁজে বের করার চেষ্টা করে, একিডনা ব্যবহারকারী-নির্ধারিত ইনভ্যারিয়েন্টগুলো (invariants) ভাঙার চেষ্টা করবে।

স্মার্ট কন্ট্রাক্টগুলোতে, ইনভ্যারিয়েন্টগুলো হলো Solidity ফাংশন, যা কন্ট্রাক্ট পৌঁছাতে পারে এমন যেকোনো ভুল বা অবৈধ স্টেট উপস্থাপন করতে পারে, যার মধ্যে রয়েছে:

  • ভুল অ্যাক্সেস কন্ট্রোল: আক্রমণকারী কন্ট্রাক্টের মালিক হয়ে গেছে।
  • ভুল স্টেট মেশিন: কন্ট্রাক্ট পজ (pause) থাকা অবস্থায় টোকেন ট্রান্সফার করা যেতে পারে।
  • ভুল পাটিগণিত: ব্যবহারকারী তার ব্যালেন্স আন্ডারফ্লো করতে পারে এবং আনলিমিটেড ফ্রি টোকেন পেতে পারে।

একিডনা দিয়ে একটি প্রপার্টি টেস্ট করা

আমরা দেখব কীভাবে একিডনা দিয়ে একটি স্মার্ট কন্ট্রাক্ট টেস্ট করতে হয়। লক্ষ্য হলো নিচের স্মার্ট কন্ট্রাক্টটি token.sol (opens in a new tab):

আমরা ধরে নেব যে এই টোকেনটির নিচের প্রপার্টিগুলো থাকতে হবে:

  • যে কারো কাছে সর্বোচ্চ 1000 টোকেন থাকতে পারে
  • টোকেনটি ট্রান্সফার করা যাবে না (এটি কোনো ERC-20 টোকেন নয়)

একটি প্রপার্টি লেখা

একিডনা প্রপার্টিগুলো হলো Solidity ফাংশন। একটি প্রপার্টিতে অবশ্যই:

  • কোনো আর্গুমেন্ট থাকা যাবে না
  • সফল হলে true রিটার্ন করতে হবে
  • এর নাম echidna দিয়ে শুরু হতে হবে

একিডনা যা করবে:

  • প্রপার্টি টেস্ট করার জন্য স্বয়ংক্রিয়ভাবে আরবিট্রারি ট্রানজ্যাকশন তৈরি করবে।
  • কোনো ট্রানজ্যাকশন প্রপার্টিকে false রিটার্ন করালে বা এরর থ্রো করলে তা রিপোর্ট করবে।
  • প্রপার্টি কল করার সময় সাইড-ইফেক্ট বাতিল করবে (অর্থাৎ, যদি প্রপার্টি কোনো স্টেট ভেরিয়েবল পরিবর্তন করে, তবে টেস্টের পরে তা বাতিল করা হয়)

নিচের প্রপার্টিটি চেক করে যে কলারের কাছে 1000-এর বেশি টোকেন নেই:

function echidna_balance_under_1000() public view returns(bool){
      return balances[msg.sender] <= 1000;
}

আপনার কন্ট্রাক্টকে আপনার প্রপার্টিগুলো থেকে আলাদা করতে ইনহেরিটেন্স ব্যবহার করুন:

contract TestToken is Token{
      function echidna_balance_under_1000() public view returns(bool){
            return balances[msg.sender] <= 1000;
      }
  }

token.sol (opens in a new tab) প্রপার্টিটি ইমপ্লিমেন্ট করে এবং টোকেন থেকে ইনহেরিট করে।

একটি কন্ট্রাক্ট ইনিশিয়েট করা

একিডনার আর্গুমেন্ট ছাড়া একটি কনস্ট্রাক্টর প্রয়োজন। যদি আপনার কন্ট্রাক্টের একটি নির্দিষ্ট ইনিশিয়ালাইজেশন প্রয়োজন হয়, তবে আপনাকে এটি কনস্ট্রাক্টরে করতে হবে।

একিডনায় কিছু নির্দিষ্ট ঠিকানা রয়েছে:

  • 0x00a329c0648769A73afAc7F9381E08FB43dBEA72 যা কনস্ট্রাক্টরকে কল করে।
  • 0x10000, 0x20000, এবং 0x00a329C0648769a73afAC7F9381e08fb43DBEA70 যা র‍্যান্ডমভাবে অন্যান্য ফাংশনগুলোকে কল করে।

আমাদের বর্তমান উদাহরণে কোনো নির্দিষ্ট ইনিশিয়ালাইজেশনের প্রয়োজন নেই, যার ফলে আমাদের কনস্ট্রাক্টর খালি।

একিডনা রান করা

একিডনা এর মাধ্যমে চালু করা হয়:

echidna-test contract.sol

যদি contract.sol-এ একাধিক কন্ট্রাক্ট থাকে, তবে আপনি টার্গেট নির্দিষ্ট করতে পারেন:

echidna-test contract.sol --contract MyContract

সারসংক্ষেপ: একটি প্রপার্টি টেস্ট করা

নিচের অংশটি আমাদের উদাহরণে একিডনা রান করার সারসংক্ষেপ দেয়:

contract TestToken is Token{
    constructor() public {}
        function echidna_balance_under_1000() public view returns(bool){
          return balances[msg.sender] <= 1000;
        }
  }

একিডনা খুঁজে পেয়েছে যে backdoor কল করা হলে প্রপার্টিটি লঙ্ঘিত হয়।

ফাজিং ক্যাম্পেইনের সময় কল করার জন্য ফাংশন ফিল্টার করা

আমরা দেখব কীভাবে ফাজ করার জন্য ফাংশনগুলো ফিল্টার করতে হয়। লক্ষ্য হলো নিচের স্মার্ট কন্ট্রাক্টটি:

এই ছোট উদাহরণটি একটি স্টেট ভেরিয়েবল পরিবর্তন করার জন্য একিডনাকে ট্রানজ্যাকশনের একটি নির্দিষ্ট সিকোয়েন্স খুঁজে বের করতে বাধ্য করে। এটি একটি ফাজারের জন্য কঠিন (এর জন্য ম্যান্টিকোর (opens in a new tab)-এর মতো একটি সিম্বলিক এক্সিকিউশন টুল ব্যবহার করার পরামর্শ দেওয়া হয়)। এটি যাচাই করতে আমরা একিডনা রান করতে পারি:

echidna-test multi.sol
...
echidna_state4: passed! 🎉
Seed: -3684648582249875403

ফাংশন ফিল্টার করা

এই কন্ট্রাক্টটি টেস্ট করার জন্য সঠিক সিকোয়েন্স খুঁজে পেতে একিডনার সমস্যা হয় কারণ দুটি রিসেট ফাংশন (reset1 এবং reset2) সমস্ত স্টেট ভেরিয়েবলকে false-এ সেট করবে। তবে, আমরা রিসেট ফাংশনটিকে ব্ল্যাকলিস্ট করতে বা শুধুমাত্র f, g, h এবং i ফাংশনগুলোকে হোয়াইটলিস্ট করতে একটি বিশেষ একিডনা ফিচার ব্যবহার করতে পারি।

ফাংশনগুলোকে ব্ল্যাকলিস্ট করতে, আমরা এই কনফিগারেশন ফাইলটি ব্যবহার করতে পারি:

filterBlacklist: true
filterFunctions: ["reset1", "reset2"]

ফাংশন ফিল্টার করার আরেকটি পদ্ধতি হলো হোয়াইটলিস্ট করা ফাংশনগুলোর তালিকা করা। এটি করার জন্য, আমরা এই কনফিগারেশন ফাইলটি ব্যবহার করতে পারি:

filterBlacklist: false
filterFunctions: ["f", "g", "h", "i"]
  • filterBlacklist ডিফল্টভাবে true থাকে।
  • ফিল্টারিং শুধুমাত্র নাম দ্বারা (প্যারামিটার ছাড়া) করা হবে। যদি আপনার f() এবং f(uint256) থাকে, তবে "f" ফিল্টারটি উভয় ফাংশনের সাথেই মিলবে।

একিডনা রান করা

একটি কনফিগারেশন ফাইল blacklist.yaml দিয়ে একিডনা রান করতে:

echidna-test multi.sol --config blacklist.yaml
...
echidna_state4: failed!💥
  Call sequence:
    f(12)
    g(8)
    h(42)
    i()

একিডনা প্রায় সাথে সাথেই প্রপার্টিটিকে মিথ্যা প্রমাণ করার জন্য ট্রানজ্যাকশনের সিকোয়েন্স খুঁজে পাবে।

সারসংক্ষেপ: ফাংশন ফিল্টার করা

একিডনা ফাজিং ক্যাম্পেইনের সময় কল করার জন্য ফাংশনগুলোকে ব্ল্যাকলিস্ট বা হোয়াইটলিস্ট করতে পারে এটি ব্যবহার করে:

filterBlacklist: true
filterFunctions: ["f1", "f2", "f3"]
echidna-test contract.sol --config config.yaml
...

filterBlacklist বুলিয়ানের মান অনুযায়ী, একিডনা f1, f2 এবং f3-কে ব্ল্যাকলিস্ট করে অথবা শুধুমাত্র এগুলোকে কল করে একটি ফাজিং ক্যাম্পেইন শুরু করে।

একিডনা দিয়ে কীভাবে Solidity-এর অ্যাসার্ট টেস্ট করবেন

এই সংক্ষিপ্ত টিউটোরিয়ালে, আমরা দেখাব কীভাবে কন্ট্রাক্টগুলোতে অ্যাসার্শন চেকিং টেস্ট করতে একিডনা ব্যবহার করতে হয়। ধরা যাক আমাদের কাছে এরকম একটি কন্ট্রাক্ট আছে:

একটি অ্যাসার্শন লেখা

আমরা নিশ্চিত করতে চাই যে এর পার্থক্য রিটার্ন করার পরে tmp যেন counter-এর চেয়ে কম বা সমান হয়। আমরা একটি একিডনা প্রপার্টি লিখতে পারতাম, কিন্তু আমাদের tmp মানটি কোথাও সংরক্ষণ করতে হবে। এর পরিবর্তে, আমরা এরকম একটি অ্যাসার্শন ব্যবহার করতে পারি:

একিডনা রান করা

অ্যাসার্শন ফেইলিওর টেস্টিং সক্ষম করতে, একটি একিডনা কনফিগারেশন ফাইল (opens in a new tab) config.yaml তৈরি করুন:

checkAsserts: true

যখন আমরা একিডনায় এই কন্ট্রাক্টটি রান করি, তখন আমরা প্রত্যাশিত ফলাফল পাই:

যেমনটি আপনি দেখতে পাচ্ছেন, একিডনা inc ফাংশনে কিছু অ্যাসার্শন ফেইলিওর রিপোর্ট করে। প্রতি ফাংশনে একাধিক অ্যাসার্শন যোগ করা সম্ভব, তবে একিডনা বলতে পারে না কোন অ্যাসার্শনটি ব্যর্থ হয়েছে।

কখন এবং কীভাবে অ্যাসার্শন ব্যবহার করবেন

অ্যাসার্শনগুলো এক্সপ্লিসিট প্রপার্টিগুলোর বিকল্প হিসেবে ব্যবহার করা যেতে পারে, বিশেষ করে যদি চেক করার শর্তগুলো সরাসরি কোনো অপারেশন f-এর সঠিক ব্যবহারের সাথে সম্পর্কিত হয়। কিছু কোডের পরে অ্যাসার্শন যোগ করা নিশ্চিত করবে যে এটি এক্সিকিউট হওয়ার পরপরই চেকটি ঘটবে:

function f(..) public {
    // কিছু জটিল কোড
    ...
    assert (condition);
    ...
}

বিপরীতে, একটি এক্সপ্লিসিট একিডনা প্রপার্টি ব্যবহার করলে র‍্যান্ডমভাবে ট্রানজ্যাকশন এক্সিকিউট হবে এবং এটি ঠিক কখন চেক করা হবে তা নিশ্চিত করার কোনো সহজ উপায় নেই। তারপরও এই ওয়ার্কঅ্যারাউন্ডটি করা সম্ভব:

function echidna_assert_after_f() public returns (bool) {
    f(..);
    return(condition);
}

তবে, কিছু সমস্যা রয়েছে:

  • যদি f-কে internal বা external হিসেবে ডিক্লেয়ার করা হয় তবে এটি ব্যর্থ হয়।
  • f কল করার জন্য কোন আর্গুমেন্টগুলো ব্যবহার করা উচিত তা অস্পষ্ট।
  • যদি f রিভার্ট করে, তবে প্রপার্টিটি ব্যর্থ হবে।

সাধারণভাবে, আমরা অ্যাসার্শন কীভাবে ব্যবহার করতে হয় সে সম্পর্কে জন রেগেহরের সুপারিশ (opens in a new tab) অনুসরণ করার পরামর্শ দিই:

  • অ্যাসার্শন চেকিংয়ের সময় কোনো সাইড ইফেক্ট জোর করে প্রয়োগ করবেন না। উদাহরণস্বরূপ: assert(ChangeStateAndReturn() == 1)
  • সুস্পষ্ট স্টেটমেন্টগুলো অ্যাসার্ট করবেন না। উদাহরণস্বরূপ assert(var >= 0) যেখানে var-কে uint হিসেবে ডিক্লেয়ার করা হয়েছে।

সবশেষে, অনুগ্রহ করে assert-এর পরিবর্তে require ব্যবহার করবেন না, কারণ একিডনা এটি শনাক্ত করতে পারবে না (তবে কন্ট্রাক্টটি যেভাবেই হোক রিভার্ট করবে)।

সারসংক্ষেপ: অ্যাসার্শন চেকিং

নিচের অংশটি আমাদের উদাহরণে একিডনা রান করার সারসংক্ষেপ দেয়:

একিডনা খুঁজে পেয়েছে যে inc-এর অ্যাসার্শনটি ব্যর্থ হতে পারে যদি এই ফাংশনটি বড় আর্গুমেন্টসহ একাধিকবার কল করা হয়।

একটি একিডনা কর্পাস সংগ্রহ এবং পরিবর্তন করা

আমরা দেখব কীভাবে একিডনার সাথে ট্রানজ্যাকশনের একটি কর্পাস সংগ্রহ এবং ব্যবহার করতে হয়। লক্ষ্য হলো নিচের স্মার্ট কন্ট্রাক্টটি magic.sol (opens in a new tab):

এই ছোট উদাহরণটি একটি স্টেট ভেরিয়েবল পরিবর্তন করার জন্য নির্দিষ্ট মান খুঁজে বের করতে একিডনাকে বাধ্য করে। এটি একটি ফাজারের জন্য কঠিন (এর জন্য ম্যান্টিকোর (opens in a new tab)-এর মতো একটি সিম্বলিক এক্সিকিউশন টুল ব্যবহার করার পরামর্শ দেওয়া হয়)। এটি যাচাই করতে আমরা একিডনা রান করতে পারি:

echidna-test magic.sol
...

echidna_magic_values: passed! 🎉

Seed: 2221503356319272685

তবে, এই ফাজিং ক্যাম্পেইনটি রান করার সময় আমরা কর্পাস সংগ্রহ করতে একিডনা ব্যবহার করতে পারি।

একটি কর্পাস সংগ্রহ করা

কর্পাস সংগ্রহ সক্ষম করতে, একটি কর্পাস ডিরেক্টরি তৈরি করুন:

mkdir corpus-magic

এবং একটি একিডনা কনফিগারেশন ফাইল (opens in a new tab) config.yaml:

coverage: true
corpusDir: "corpus-magic"

এখন আমরা আমাদের টুলটি রান করতে পারি এবং সংগৃহীত কর্পাস চেক করতে পারি:

echidna-test magic.sol --config config.yaml

একিডনা এখনও সঠিক ম্যাজিক ভ্যালুগুলো খুঁজে পাচ্ছে না, তবে আমরা এর সংগৃহীত কর্পাসটি দেখতে পারি। উদাহরণস্বরূপ, এই ফাইলগুলোর মধ্যে একটি ছিল:

স্পষ্টতই, এই ইনপুটটি আমাদের প্রপার্টিতে ফেইলিওর ট্রিগার করবে না। তবে, পরবর্তী ধাপে, আমরা দেখব কীভাবে এর জন্য এটিকে পরিবর্তন করতে হয়।

একটি কর্পাস সিডিং করা

magic ফাংশনটি ডিল করার জন্য একিডনার কিছু সাহায্য প্রয়োজন। আমরা এর জন্য উপযুক্ত প্যারামিটার ব্যবহার করতে ইনপুটটি কপি এবং পরিবর্তন করতে যাচ্ছি:

cp corpus/2712688662897926208.txt corpus/new.txt

আমরা magic(42,129,333,0) কল করার জন্য new.txt পরিবর্তন করব। এখন, আমরা একিডনা পুনরায় রান করতে পারি:

এবার, এটি খুঁজে পেয়েছে যে প্রপার্টিটি সাথে সাথেই লঙ্ঘিত হয়েছে।

উচ্চ গ্যাস খরচসহ ট্রানজ্যাকশনগুলো খুঁজে বের করা

আমরা দেখব কীভাবে একিডনা দিয়ে উচ্চ গ্যাস খরচসহ ট্রানজ্যাকশনগুলো খুঁজে বের করতে হয়। লক্ষ্য হলো নিচের স্মার্ট কন্ট্রাক্টটি:

এখানে expensive-এর একটি বড় গ্যাস খরচ থাকতে পারে।

বর্তমানে, একিডনার সবসময় টেস্ট করার জন্য একটি প্রপার্টি প্রয়োজন: এখানে echidna_test সবসময় true রিটার্ন করে। এটি যাচাই করতে আমরা একিডনা রান করতে পারি:

echidna-test gas.sol
...
echidna_test: passed! 🎉

Seed: 2320549945714142710

গ্যাস খরচ পরিমাপ করা

একিডনার সাথে গ্যাস খরচ পরিমাপ সক্ষম করতে, একটি কনফিগারেশন ফাইল config.yaml তৈরি করুন:

estimateGas: true

এই উদাহরণে, ফলাফলগুলো সহজে বোঝার জন্য আমরা ট্রানজ্যাকশন সিকোয়েন্সের আকারও কমিয়ে দেব:

seqLen: 2
estimateGas: true

একিডনা রান করা

কনফিগারেশন ফাইল তৈরি হয়ে গেলে, আমরা এভাবে একিডনা রান করতে পারি:

  • দেখানো গ্যাসটি HEVM (opens in a new tab) দ্বারা প্রদত্ত একটি অনুমান।

গ্যাস-হ্রাসকারী কলগুলো ফিল্টার করা

উপরের ফাজিং ক্যাম্পেইনের সময় কল করার জন্য ফাংশন ফিল্টার করা টিউটোরিয়ালটি দেখায় কীভাবে আপনার টেস্টিং থেকে কিছু ফাংশন সরিয়ে ফেলতে হয়।
একটি সঠিক গ্যাস অনুমান পাওয়ার জন্য এটি অত্যন্ত গুরুত্বপূর্ণ হতে পারে। নিচের উদাহরণটি বিবেচনা করুন:

যদি একিডনা সমস্ত ফাংশন কল করতে পারে, তবে এটি সহজে উচ্চ গ্যাস খরচসহ ট্রানজ্যাকশনগুলো খুঁজে পাবে না:

এর কারণ হলো খরচটি addrs-এর আকারের ওপর নির্ভর করে এবং র‍্যান্ডম কলগুলো অ্যারেকে প্রায় খালি রাখার প্রবণতা দেখায়। তবে, pop এবং clear ব্ল্যাকলিস্ট করলে আমরা অনেক ভালো ফলাফল পাই:

filterBlacklist: true
filterFunctions: ["pop", "clear"]
echidna-test pushpop.sol --config config.yaml
...
push used a maximum of 40839 gas
...
check used a maximum of 1484472 gas

সারসংক্ষেপ: উচ্চ গ্যাস খরচসহ ট্রানজ্যাকশনগুলো খুঁজে বের করা

একিডনা estimateGas কনফিগারেশন অপশন ব্যবহার করে উচ্চ গ্যাস খরচসহ ট্রানজ্যাকশনগুলো খুঁজে পেতে পারে:

estimateGas: true
echidna-test contract.sol --config config.yaml
...

ফাজিং ক্যাম্পেইন শেষ হওয়ার পর, একিডনা প্রতিটি ফাংশনের জন্য সর্বোচ্চ গ্যাস খরচসহ একটি সিকোয়েন্স রিপোর্ট করবে।

পেজ সর্বশেষ আপডেট করা হয়েছে: 3 মার্চ, 2026