اہم مواد پر جائیں

اسمارٹ کنٹریکٹس میں بگز تلاش کرنے کے لیے Manticore کا استعمال کیسے کریں

solidity
اسمارٹ معاہدات
سیکورٹی
testing
رسمی تصدیق
ترقی
Trailofbits
13 جنوری، 2020
14 منٹ کی پڑھائی

اس ٹیوٹوریل کا مقصد یہ دکھانا ہے کہ اسمارٹ کنٹریکٹس میں خود بخود بگز تلاش کرنے کے لیے Manticore کا استعمال کیسے کیا جائے۔

انسٹالیشن

Manticore کے لیے >= python 3.6 درکار ہے۔ اسے pip کے ذریعے یا docker کا استعمال کرکے انسٹال کیا جاسکتا ہے۔

docker کے ذریعے Manticore

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

آخری کمانڈ eth-security-toolbox کو ایک docker میں چلاتا ہے جس کی آپ کی موجودہ ڈائریکٹری تک رسائی ہے۔ آپ اپنے ہوسٹ سے فائلیں تبدیل کر سکتے ہیں، اور docker سے فائلوں پر ٹولز چلا سکتے ہیں

docker کے اندر، چلائیں:

solc-select 0.5.11
cd /home/trufflecon/

pip کے ذریعے Manticore

pip3 install --user manticore

solc 0.5.11 تجویز کیا جاتا ہے۔

ایک اسکرپٹ چلانا

python 3 کے ساتھ ایک python اسکرپٹ چلانے کے لیے:

python3 script.py

ڈائنامک سمبولک ایگزیکیوشن کا تعارف

مختصر طور پر ڈائنامک سمبولک ایگزیکیوشن

ڈائنامک سمبولک ایگزیکیوشن (DSE) پروگرام کے تجزیے کی ایک تکنیک ہے جو اعلیٰ درجے کی سیمانٹک بیداری کے ساتھ اسٹیٹ اسپیس کو تلاش کرتی ہے۔ یہ تکنیک "پروگرام پاتھس" کی دریافت پر مبنی ہے، جسے path predicates کہلانے والے ریاضیاتی فارمولوں کے طور پر پیش کیا جاتا ہے۔ تصوراتی طور پر، یہ تکنیک دو مراحل میں پاتھ پریڈیکیٹس پر کام کرتی ہے:

  1. یہ پروگرام کے ان پٹ پر پابندیوں کا استعمال کرتے ہوئے تعمیر کیے جاتے ہیں۔
  2. یہ ایسے پروگرام ان پٹس پیدا کرنے کے لیے استعمال ہوتے ہیں جو متعلقہ پاتھس کو ایگزیکیوٹ کرنے کا سبب بنیں گے۔

یہ نقطہ نظر اس لحاظ سے کوئی غلط مثبت پیدا نہیں کرتا ہے کہ تمام شناخت شدہ پروگرام اسٹیٹس کو ٹھوس ایگزیکیوشن کے دوران ٹرگر کیا جا سکتا ہے۔ مثال کے طور پر، اگر تجزیہ میں کوئی انٹیجر اوور فلو ملتا ہے، تو اس کے دوبارہ پیدا کیے جانے کی ضمانت ہے۔

پاتھ پریڈیکیٹ کی مثال

DSE کیسے کام کرتا ہے اس کی بصیرت حاصل کرنے کے لیے، درج ذیل مثال پر غور کریں:

1function f(uint a){
2
3 if (a == 65) {
4 // A bug is present
5 }
6
7}

چونکہ f() میں دو پاتھس ہیں، اس لیے ایک DSE دو مختلف پاتھ پریڈیکیٹس بنائے گا:

  • پاتھ 1: a == 65
  • پاتھ 2: Not (a == 65)

ہر پاتھ پریڈیکیٹ ایک ریاضیاتی فارمولا ہے جسے SMT solveropens in a new tab نامی ایک نام نہاد کو دیا جا سکتا ہے، جو مساوات کو حل کرنے کی کوشش کرے گا۔ پاتھ 1 کے لیے، سولور کہے گا کہ پاتھ کو a = 65 کے ساتھ تلاش کیا جا سکتا ہے۔ پاتھ 2 کے لیے، سولور a کو 65 کے علاوہ کوئی بھی قدر دے سکتا ہے، مثال کے طور پر a = 0۔

خصوصیات کی تصدیق کرنا

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

درج ذیل مثال پر غور کریں:

1function unsafe_add(uint a, uint b) returns(uint c){
2 c = a + b; // no overflow protection
3 return c;
4}

یہاں فنکشن میں تلاش کرنے کے لیے صرف ایک پاتھ ہے:

  • پاتھ 1: c = a + b

Manticore کا استعمال کرتے ہوئے، آپ اوور فلو کی جانچ کر سکتے ہیں، اور پاتھ پریڈیکیٹ میں پابندیاں شامل کر سکتے ہیں:

  • c = a + b AND (c < a OR c < b)

اگر a اور b کی ایسی ویلیویشن تلاش کرنا ممکن ہے جس کے لیے مذکورہ بالا پاتھ پریڈیکیٹ قابل عمل ہے، تو اس کا مطلب ہے کہ آپ کو ایک اوور فلو مل گیا ہے۔ مثال کے طور پر سولور ان پٹ a = 10 , b = MAXUINT256 پیدا کر سکتا ہے۔

اگر آپ ایک فکسڈ ورژن پر غور کریں:

1function safe_add(uint a, uint b) returns(uint c){
2 c = a + b;
3 require(c>=a);
4 require(c>=b);
5 return c;
6}

اوور فلو چیک کے ساتھ متعلقہ فارمولا ہوگا:

  • c = a + b AND (c >= a) AND (c=>b) AND (c < a OR c < b)

اس فارمولے کو حل نہیں کیا جا سکتا؛ دوسرے لفظوں میں یہ ایک ثبوت ہے کہ safe_add میں، c ہمیشہ بڑھے گا۔

DSE اس طرح ایک طاقتور ٹول ہے، جو آپ کے کوڈ پر من مانی پابندیوں کی تصدیق کر سکتا ہے۔

Manticore کے تحت چلانا

ہم دیکھیں گے کہ Manticore API کے ساتھ ایک اسمارٹ کنٹریکٹ کو کیسے تلاش کیا جائے۔ ہدف درج ذیل اسمارٹ کنٹریکٹ example.solopens in a new tab ہے:

1pragma solidity >=0.4.24 <0.6.0;
2
3contract Simple {
4 function f(uint a) payable public{
5 if (a == 65) {
6 revert();
7 }
8 }
9}
سب دکھائیں

ایک اسٹینڈ الون ایکسپلوریشن چلائیں

آپ درج ذیل کمانڈ کے ذریعے Manticore کو براہ راست اسمارٹ کنٹریکٹ پر چلا سکتے ہیں (پروجیکٹ ایک Solidity فائل، یا ایک پروجیکٹ ڈائریکٹری ہو سکتا ہے):

$ manticore project

آپ کو اس طرح کے ٹیسٹ کیسز کا آؤٹ پٹ ملے گا (ترتیب بدل سکتی ہے):

1...
2... m.c.manticore:INFO: Generated testcase No. 0 - STOP
3... m.c.manticore:INFO: Generated testcase No. 1 - REVERT
4... m.c.manticore:INFO: Generated testcase No. 2 - RETURN
5... m.c.manticore:INFO: Generated testcase No. 3 - REVERT
6... m.c.manticore:INFO: Generated testcase No. 4 - STOP
7... m.c.manticore:INFO: Generated testcase No. 5 - REVERT
8... m.c.manticore:INFO: Generated testcase No. 6 - REVERT
9... m.c.manticore:INFO: Results in /home/ethsec/workshops/Automated Smart Contracts Audit - TruffleCon 2018/manticore/examples/mcore_t6vi6ij3
10...
سب دکھائیں

اضافی معلومات کے بغیر، Manticore نئے سمبولک ٹرانزیکشنز کے ساتھ کنٹریکٹ کو اس وقت تک تلاش کرے گا جب تک کہ وہ کنٹریکٹ پر نئے پاتھس کو تلاش نہ کر لے۔ Manticore ایک ناکام ٹرانزیکشن کے بعد (مثال کے طور پر: ایک revert کے بعد) نئے ٹرانزیکشنز نہیں چلاتا ہے۔

Manticore معلومات کو ایک mcore_* ڈائریکٹری میں آؤٹ پٹ کرے گا۔ دیگر چیزوں کے علاوہ، آپ کو اس ڈائریکٹری میں ملے گا:

  • global.summary: کوریج اور کمپائلر وارننگز
  • test_XXXXX.summary: کوریج، آخری ہدایت، فی ٹیسٹ کیس اکاؤنٹ بیلنس
  • test_XXXXX.tx: فی ٹیسٹ کیس ٹرانزیکشنز کی تفصیلی فہرست

یہاں Manticore کو 7 ٹیسٹ کیسز ملے ہیں، جو اس کے مطابق ہیں (فائل نام کی ترتیب بدل سکتی ہے):

ٹرانزیکشن 0ٹرانزیکشن 1ٹرانزیکشن 2نتیجہ
test_00000000.txکنٹریکٹ کی تخلیقf(!=65)f(!=65)STOP
test_00000001.txکنٹریکٹ کی تخلیقفال بیک فنکشنREVERT
test_00000002.txکنٹریکٹ کی تخلیقRETURN
test_00000003.txکنٹریکٹ کی تخلیقf(65)REVERT
test_00000004.txکنٹریکٹ کی تخلیقf(!=65)STOP
test_00000005.txکنٹریکٹ کی تخلیقf(!=65)f(65)REVERT
test_00000006.txکنٹریکٹ کی تخلیقf(!=65)فال بیک فنکشنREVERT

ایکسپلوریشن کا خلاصہ f(!=65) ظاہر کرتا ہے کہ f کو 65 سے مختلف کسی بھی قدر کے ساتھ کال کیا گیا ہے۔

جیسا کہ آپ دیکھ سکتے ہیں، Manticore ہر کامیاب یا واپس کیے گئے ٹرانزیکشن کے لیے ایک منفرد ٹیسٹ کیس تیار کرتا ہے۔

اگر آپ تیز کوڈ ایکسپلوریشن چاہتے ہیں تو --quick-mode فلیگ کا استعمال کریں (یہ بگ ڈیٹیکٹرز، گیس کمپیوٹیشن، ... کو غیر فعال کر دیتا ہے)

API کے ذریعے ایک اسمارٹ کنٹریکٹ میں ہیرا پھیری کرنا

یہ سیکشن Manticore Python API کے ذریعے ایک اسمارٹ کنٹریکٹ میں ہیرا پھیری کرنے کے طریقے کی تفصیلات بیان کرتا ہے۔ آپ python ایکسٹینشن *.py کے ساتھ نئی فائل بنا سکتے ہیں اور اس فائل میں API کمانڈز (جن کی بنیادی باتیں ذیل میں بیان کی جائیں گی) کو شامل کرکے ضروری کوڈ لکھ سکتے ہیں اور پھر اسے $ python3 *.py کمانڈ کے ساتھ چلا سکتے ہیں۔ آپ ذیل میں دی گئی کمانڈز کو براہ راست python کنسول میں بھی ایگزیکیوٹ کر سکتے ہیں، کنسول چلانے کے لیے $ python3 کمانڈ کا استعمال کریں۔

اکاؤنٹس بنانا

سب سے پہلے آپ کو درج ذیل کمانڈز کے ساتھ ایک نئی بلاک چین شروع کرنی چاہیے:

1from manticore.ethereum import ManticoreEVM
2
3m = ManticoreEVM()

ایک غیر کنٹریکٹ اکاؤنٹ m.create_accountopens in a new tab کا استعمال کرتے ہوئے بنایا جاتا ہے:

1user_account = m.create_account(balance=1000)

ایک Solidity کنٹریکٹ m.solidity_create_contractopens in a new tab کا استعمال کرتے ہوئے تعینات کیا جا سکتا ہے:

1source_code = '''
2pragma solidity >=0.4.24 <0.6.0;
3contract Simple {
4 function f(uint a) payable public{
5 if (a == 65) {
6 revert();
7 }
8 }
9}
10'''
11# Initiate the contract
12contract_account = m.solidity_create_contract(source_code, owner=user_account)
سب دکھائیں

خلاصہ

ٹرانزیکشنز کو ایگزیکیوٹ کرنا

Manticore دو قسم کے ٹرانزیکشن کو سپورٹ کرتا ہے:

  • را ٹرانزیکشن: تمام فنکشنز کو تلاش کیا جاتا ہے
  • نامزد ٹرانزیکشن: صرف ایک فنکشن کو تلاش کیا جاتا ہے

را ٹرانزیکشن

ایک را ٹرانزیکشن m.transactionopens in a new tab کا استعمال کرتے ہوئے ایگزیکیوٹ کیا جاتا ہے:

1m.transaction(caller=user_account,
2 address=contract_account,
3 data=data,
4 value=value)

ٹرانزیکشن کا کالر، ایڈریس، ڈیٹا، یا ویلیو یا تو ٹھوس یا سمبولک ہو سکتا ہے:

مثال کے طور پر:

1symbolic_value = m.make_symbolic_value()
2symbolic_data = m.make_symbolic_buffer(320)
3m.transaction(caller=user_account,
4 address=contract_address,
5 data=symbolic_data,
6 value=symbolic_value)

اگر ڈیٹا سمبولک ہے، تو Manticore ٹرانزیکشن ایگزیکیوشن کے دوران کنٹریکٹ کے تمام فنکشنز کو تلاش کرے گا۔ فنکشن کا انتخاب کیسے کام کرتا ہے، یہ سمجھنے کے لیے Hands on the Ethernaut CTFopens in a new tab مضمون میں فال بیک فنکشن کی وضاحت دیکھنا مددگار ہوگا۔

نامزد ٹرانزیکشن

فنکشنز کو ان کے نام کے ذریعے ایگزیکیوٹ کیا جا سکتا ہے۔ f(uint var) کو ایک سمبولک ویلیو کے ساتھ، user_account سے، اور 0 ایتھر کے ساتھ ایگزیکیوٹ کرنے کے لیے، استعمال کریں:

1symbolic_var = m.make_symbolic_value()
2contract_account.f(symbolic_var, caller=user_account, value=0)

اگر ٹرانزیکشن کی value کی وضاحت نہیں کی گئی ہے، تو یہ بطور ڈیفالٹ 0 ہے۔

خلاصہ

  • ایک ٹرانزیکشن کے آرگومنٹس ٹھوس یا سمبولک ہو سکتے ہیں
  • ایک را ٹرانزیکشن تمام فنکشنز کو تلاش کرے گا
  • فنکشن کو ان کے نام سے کال کیا جا سکتا ہے

ورک اسپیس

m.workspace وہ ڈائریکٹری ہے جو تمام تیار کردہ فائلوں کے لیے آؤٹ پٹ ڈائریکٹری کے طور پر استعمال ہوتی ہے:

1print("Results are in {}".format(m.workspace))

ایکسپلوریشن کو ختم کریں

ایکسپلوریشن کو روکنے کے لیے m.finalize()opens in a new tab کا استعمال کریں۔ ایک بار یہ طریقہ کال ہو جانے کے بعد مزید کوئی ٹرانزیکشن نہیں بھیجا جانا چاہیے اور Manticore ہر تلاش کیے گئے پاتھ کے لیے ٹیسٹ کیسز تیار کرتا ہے۔

خلاصہ: Manticore کے تحت چلانا

پچھلے تمام مراحل کو ایک ساتھ رکھنے پر، ہم حاصل کرتے ہیں:

1from manticore.ethereum import ManticoreEVM
2
3m = ManticoreEVM()
4
5with open('example.sol') as f:
6 source_code = f.read()
7
8user_account = m.create_account(balance=1000)
9contract_account = m.solidity_create_contract(source_code, owner=user_account)
10
11symbolic_var = m.make_symbolic_value()
12contract_account.f(symbolic_var)
13
14print("Results are in {}".format(m.workspace))
15m.finalize() # stop the exploration
سب دکھائیں

اوپر دیا گیا تمام کوڈ آپ example_run.pyopens in a new tab میں تلاش کر سکتے ہیں

تھروئنگ پاتھس حاصل کرنا

اب ہم f() میں استثناء پیدا کرنے والے پاتھس کے لیے مخصوص ان پٹس تیار کریں گے۔ ہدف اب بھی درج ذیل اسمارٹ کنٹریکٹ example.solopens in a new tab ہے:

1pragma solidity >=0.4.24 <0.6.0;
2contract Simple {
3 function f(uint a) payable public{
4 if (a == 65) {
5 revert();
6 }
7 }
8}

اسٹیٹ کی معلومات کا استعمال

ہر ایگزیکیوٹ ہونے والے پاتھ کی بلاک چین کی اپنی اسٹیٹ ہوتی ہے۔ ایک اسٹیٹ یا تو تیار ہوتی ہے یا اسے ختم کر دیا جاتا ہے، جس کا مطلب ہے کہ یہ ایک THROW یا REVERT ہدایت تک پہنچ جاتی ہے:

1for state in m.all_states:
2 # do something with state

آپ اسٹیٹ کی معلومات تک رسائی حاصل کر سکتے ہیں۔ مثال کے طور پر:

  • state.platform.get_balance(account.address): اکاؤنٹ کا بیلنس
  • state.platform.transactions: ٹرانزیکشنز کی فہرست
  • state.platform.transactions[-1].return_data: آخری ٹرانزیکشن کے ذریعے واپس کیا گیا ڈیٹا

آخری ٹرانزیکشن کے ذریعے واپس کیا گیا ڈیٹا ایک ایرے ہے، جسے ABI.deserialize کے ساتھ ایک ویلیو میں تبدیل کیا جا سکتا ہے، مثال کے طور پر:

1data = state.platform.transactions[0].return_data
2data = ABI.deserialize("uint", data)

ٹیسٹ کیس کیسے تیار کریں

ٹیسٹ کیس تیار کرنے کے لیے m.generate_testcase(state, name)opens in a new tab کا استعمال کریں:

1m.generate_testcase(state, 'BugFound')

خلاصہ

  • آپ m.all_states کے ساتھ اسٹیٹ پر اعادہ کر سکتے ہیں
  • state.platform.get_balance(account.address) اکاؤنٹ کا بیلنس واپس کرتا ہے
  • state.platform.transactions ٹرانزیکشنز کی فہرست واپس کرتا ہے
  • transaction.return_data واپس کیا گیا ڈیٹا ہے
  • m.generate_testcase(state, name) اسٹیٹ کے لیے ان پٹس تیار کرتا ہے

خلاصہ: تھروئنگ پاتھ حاصل کرنا

1from manticore.ethereum import ManticoreEVM
2
3m = ManticoreEVM()
4
5with open('example.sol') as f:
6 source_code = f.read()
7
8user_account = m.create_account(balance=1000)
9contract_account = m.solidity_create_contract(source_code, owner=user_account)
10
11symbolic_var = m.make_symbolic_value()
12contract_account.f(symbolic_var)
13
14## Check if an execution ends with a REVERT or INVALID
15for state in m.terminated_states:
16 last_tx = state.platform.transactions[-1]
17 if last_tx.result in ['REVERT', 'INVALID']:
18 print('Throw found {}'.format(m.workspace))
19 m.generate_testcase(state, 'ThrowFound')
سب دکھائیں

اوپر دیا گیا تمام کوڈ آپ example_run.pyopens in a new tab میں تلاش کر سکتے ہیں

نوٹ کریں کہ ہم ایک بہت آسان اسکرپٹ تیار کر سکتے تھے، کیونکہ terminated_state کے ذریعے واپس کی گئی تمام اسٹیٹس کے نتیجے میں REVERT یا INVALID ہوتا ہے: اس مثال کا مقصد صرف یہ دکھانا تھا کہ API میں ہیرا پھیری کیسے کی جائے۔

پابندیاں شامل کرنا

ہم دیکھیں گے کہ ایکسپلوریشن کو کیسے محدود کیا جائے۔ ہم یہ فرض کریں گے کہ f() کی دستاویزات میں کہا گیا ہے کہ فنکشن کو کبھی بھی a == 65 کے ساتھ کال نہیں کیا جاتا، لہذا a == 65 والا کوئی بھی بگ حقیقی بگ نہیں ہے۔ ہدف اب بھی درج ذیل اسمارٹ کنٹریکٹ example.solopens in a new tab ہے:

1pragma solidity >=0.4.24 <0.6.0;
2contract Simple {
3 function f(uint a) payable public{
4 if (a == 65) {
5 revert();
6 }
7 }
8}

آپریٹرز

Operatorsopens in a new tab ماڈیول پابندیوں کی ہیرا پھیری میں سہولت فراہم کرتا ہے، دیگر چیزوں کے علاوہ یہ فراہم کرتا ہے:

  • Operators.AND,
  • Operators.OR,
  • Operators.UGT (غیر دستخط شدہ سے بڑا),
  • Operators.UGE (غیر دستخط شدہ سے بڑا یا برابر)،
  • Operators.ULT (غیر دستخط شدہ سے کم)،
  • Operators.ULE (غیر دستخط شدہ سے کم یا برابر)۔

ماڈیول کو امپورٹ کرنے کے لیے درج ذیل کا استعمال کریں:

1from manticore.core.smtlib import Operators

Operators.CONCAT ایک ایرے کو ایک ویلیو سے جوڑنے کے لیے استعمال کیا جاتا ہے۔ مثال کے طور پر، ایک ٹرانزیکشن کے return_data کو ایک ویلیو میں تبدیل کرنے کی ضرورت ہے تاکہ اسے دوسری ویلیو کے خلاف چیک کیا جا سکے:

1last_return = Operators.CONCAT(256, *last_return)

پابندیاں

آپ پابندیوں کا استعمال عالمی سطح پر یا کسی مخصوص اسٹیٹ کے لیے کر سکتے ہیں۔

عالمی پابندی

عالمی پابندی شامل کرنے کے لیے m.constrain(constraint) کا استعمال کریں۔ مثال کے طور پر، آپ ایک سمبولک ایڈریس سے ایک کنٹریکٹ کو کال کر سکتے ہیں، اور اس ایڈریس کو مخصوص ویلیوز تک محدود کر سکتے ہیں:

1symbolic_address = m.make_symbolic_value()
2m.constraint(Operators.OR(symbolic == 0x41, symbolic_address == 0x42))
3m.transaction(caller=user_account,
4 address=contract_account,
5 data=m.make_symbolic_buffer(320),
6 value=0)

اسٹیٹ کی پابندی

کسی مخصوص اسٹیٹ میں پابندی شامل کرنے کے لیے state.constrain(constraint)opens in a new tab کا استعمال کریں۔ اس کا استعمال اسٹیٹ کو اس کی ایکسپلوریشن کے بعد محدود کرنے کے لیے کیا جا سکتا ہے تاکہ اس پر کچھ خصوصیت کی جانچ کی جا سکے۔

پابندی کی جانچ کرنا

یہ جاننے کے لیے کہ کیا کوئی پابندی اب بھی قابل عمل ہے، solver.check(state.constraints) کا استعمال کریں۔ مثال کے طور پر، درج ذیل symbolic_value کو 65 سے مختلف ہونے پر مجبور کرے گا اور یہ جانچے گا کہ کیا اسٹیٹ اب بھی قابل عمل ہے:

1state.constrain(symbolic_var != 65)
2if solver.check(state.constraints):
3 # state is feasible

خلاصہ: پابندیاں شامل کرنا

پچھلے کوڈ میں پابندی شامل کرنے پر، ہم حاصل کرتے ہیں:

1from manticore.ethereum import ManticoreEVM
2from manticore.core.smtlib.solver import Z3Solver
3
4solver = Z3Solver.instance()
5
6m = ManticoreEVM()
7
8with open("example.sol") as f:
9 source_code = f.read()
10
11user_account = m.create_account(balance=1000)
12contract_account = m.solidity_create_contract(source_code, owner=user_account)
13
14symbolic_var = m.make_symbolic_value()
15contract_account.f(symbolic_var)
16
17no_bug_found = True
18
19## Check if an execution ends with a REVERT or INVALID
20for state in m.terminated_states:
21 last_tx = state.platform.transactions[-1]
22 if last_tx.result in ['REVERT', 'INVALID']:
23 # we do not consider the path were a == 65
24 condition = symbolic_var != 65
25 if m.generate_testcase(state, name="BugFound", only_if=condition):
26 print(f'Bug found, results are in {m.workspace}')
27 no_bug_found = False
28
29if no_bug_found:
30 print(f'No bug found')
سب دکھائیں

اوپر دیا گیا تمام کوڈ آپ example_run.pyopens in a new tab میں تلاش کر سکتے ہیں

صفحہ کی آخری تازہ کاری: 26 اپریل، 2024

کیا یہ ٹیوٹوریل کارآمد تھا؟