چارچوب قراردادهای هوشمند برای اتریوم (Brownie)
Brownie یک چارچوب قوی و آسان برای توسعه قراردادهای هوشمند اتریوم است. موارد استفاده آن عبارت است از:
- استقرار: استقرار بسیاری از قراردادها بر روی بلاکچین و هرگونه تراکنش مورد نیاز برای مقداردهی اولیه یا ادغام آن ها را به وسیله Brownie خودکار می شود.
- تعامل: اسکریپت بنویسید یا از کنسول برای تعامل با قراردادهای خود در شبکه اصلی، یا برای آزمایش سریع در یک محیط محلی استفاده کنید.
- اشکال زدایی: هنگام بازگشت تراکنش اطلاعات دقیقی دریافت کنید تا به شما کمک کند مشکل را سریع تشخیص دهید.
- تست: تست های واحد را در پایتون بنویسید و پوشش تست را بر اساس تحلیل پشته-ردیابی ارزیابی کنید.
اجازه دهید یک قرارداد هوشمند بسیار ساده در Solidity ایجاد کنیم که تنها دو تابع خارجی، getValue و setValue را در معرض نمایش قرار می دهد. این قرارداد هوشمند را در یک فایل به نام smart_contract.sol ذخیره کنید.
pragma solidity ^0.5.11; contract SimpleContract { uint value; function setValue(uint _value) external {value = _value;} function getValue() external view returns(uint){return value;} }
اکنون می خواهیم قرارداد هوشمند بالا را با استفاده از Brownie در بلاکچین مستقر، و با آن تعامل برقرار کنیم:
setup
در اینجا از Python 3.7 و virtualenv برای ایزوله کردن محیط خود استفاده شده است. اگر پایتون نسخه 3.7 را نصب نکرده اید، لطفاً مراحل زیر را دنبال کنید تا نسخه مورد نظر در سیستم شما نصب شود.
sudo add-apt-repository ppa:deadsnakes/ppa sudo add-apt-repository ppa:ethereum/ethereum sudo apt-get update sudo apt install -y python3.7 sudo apt-get install -y python3.7-dev python3-pip virtualenv python-dev solc
ما از Ganache (یک بلاکچین شخصی برای توسعه اتریوم) استفاده خواهیم کرد. برای نصب Ganache، لطفا مراحل زیر را دنبال کنید.
یک محیط مجازی برای پروژه Solidity خود ایجاد کنید. نام آن را TestBrownie گذاشته و سپس Brownie را نصب کنید.
pip install eth-brownie
برای مقداردهی اولیه یک پروژه خالی، با ایجاد یک پوشه جدید شروع کنید. در داخل آن پوشه، دستور زیر را اجرا کنید:
brownie init
ساختار یک پروژه Brownie
هر پروژه Brownie شامل پوشه های زیر است:
-
- قراردادها (contracts/): منابع قرارداد
- رابط ها (interfaces/): منابع واسط
- اسکریپت ها (scripts/): اسکریپت هایی برای استقرار و تعامل
- tests:اسکریپت هایی برای تست پروژه
- brownie-config.yaml : فایل پیکربندی پروژه
پوشه های زیر نیز به صورت داخلی توسط Brownie برای مدیریت پروژه ایجاد و استفاده می شوند. نباید فایل های داخل این پوشه ها را ویرایش یا حذف کرد.
-
- Build/: داده های پروژه، مانند مصنوعات کامپایلر و نتایج تست واحد
- Report/: فایل های گزارش JSON برای استفاده در رابط کاربری گرافیکی
کامپایل کردن
قرارداد هوشمند خود را، smart_contract.sol، در فهرست قراردادها کپی کنید. برای کامپایل همه منابع قرارداد باید به زیرپوشه contract/ بروید و دستور زیر را اجرا کنید:
$ brownie compile
هر بار که کامپایلر اجرا می شود، brownie هش های هر منبع قرارداد را با هش های نسخه های کامپایل شده موجود مقایسه می کند. اگر قراردادی تغییر نکرده باشد مجدد کامپایل نمی شود. اگر می خواهید کل پروژه را مجدد کامپایل کنید، از brownie compile --all
استفاده کنید.
اگر میخواهید قرارداد Solidity را با نسخه دیگری کامپایل کنید، فقط آن را در پراگمای فایل sol. ذکر کنید، و اگر از قبل این نسخه وجود نداشته باشد، به صورت خودکار نصب می شود. نکته قابل توجه این است که می توانید تنظیمات کامپایلر را در brownie-config.yaml انجام دهید.
evm_version: null minify_source: false solc version: 0.6.0 optimize: true runs: 200
- تغییر تنظیمات کامپایلر منجر به کامپایل مجدد کامل پروژه می شود.
- اگر یک نسخه کامپایلر در فایل پیکربندی تنظیم شده باشد، تمام قراردادهای پروژه با استفاده از آن نسخه کامپایل می شوند. نسخه باید به صورت رشته ای و با فرمت x.x ارائه شود.
- اگر نسخه کامپایلر صفر تنظیم شده باشد، brownie به نسخه عملی هر قرارداد نگاه می کند و از آخرین نسخه کامپایلر منطبق که نصب شده است، استفاده می کند.
در مورد evm_version ،Brownie مجموعه قوانین را بر اساس کامپایلر تنظیم می کند.
- Byzantium : Solidity <=0.5.4
- Petersburg : Solidity >=0.5.5 <=0.5.12
- Istanbul : Solidity >=0.5.13, Vyper
همچنین، می توانید نسخه EVM را به صورت دستی تنظیم کنید. گزینه های معتبر بیزانس، کنستانتینوپل، پترزبورگ و استانبول هستند. همچنین می توانید از قوانین کلاسیک اتریوم آتلانتیس و agharta استفاده کنید که قبل از ارسال به کامپایلر به معادل های اتریوم خود تبدیل می شوند.
توجه: Brownie از نسخه های Solidity >=0.4.22، و Vyper از نسخه 0.1.0-b16 پشتیبانی می کند.
پس از کامپایل موفقیت آمیز، Brownie یک فایل SimpleContract.json را در پوشه builds/contract ایجاد می کند.
مستقرکردن قرارداد
اگر brownie-config.yaml را باز کنید، قسمتی برای تنظیمات شبکه دارد. می توانید شبکه های موجود را سفارشی کنید، یا می توانید یک بلوک جدید در زیر شبکه ها ایجاد کنید. ما از رابط کاربری گرافیکی Ganache، که روی پورت 7545 اجرا می شود، استفاده می کنیم.
یک بلوک خصوصی در زیر شبکه ها ایجاد کرده ایم.
network: default: development # the default network that brownie connects to settings: gas_limit: "auto" gas_price: "auto" persist: true reverting_tx_gas_limit: false # if false, reverting tx's will raise without broadcasting networks: # any settings given here will replace the defaults private: host: http://127.0.0.1:7545/ gas_price: 0 persist: false
این فایل را ذخیره کنید. اکنون برای استقرار قرارداد هوشمند کامپایل شده خود، از کنسول Brownie استفاده می کنیم. این کنسول زمانی مفید است که میخواهید مستقیماً با قراردادهای مستقر در یک زنجیره غیر محلی یا برای آزمایش سریع در حین توسعه تعامل داشته باشید. همچنین یک نقطه شروع عالی برای آشنایی با عملکرد Brownie است.
Brownie کنسول بسیار شبیه به یک مفسر معمولی پایتون است. از داخل پوشه پروژه، می توان با تایپ کردن cosnsole به کنسول Brownie متصل شوید.
$ brownie console ##if you want to use a specific network entioned in your config file, use $ brownie console --network private ##this will connect your brownie console with your ganache private ##network. Brownie v1.5.1 - Python development framework for Ethereum Project is the active project. Brownie environment is ready.
بیایید اندکی با کنسول Brownie کار کنیم:
می توانید حساب های موجود در خروجی را با حساب های قابل مشاهده در GUI Ganache بررسی کنید.
>>> from brownie import accounts >>> accounts [<Account '0xd5373B59FDF14e9F1f5cc858eCAE0e6662FfedC1'>, <Account '0x533D7aa17435Ee227FdC30BA94fF8a509532D776'>, <Account '0x40E29Ca2269785f2F53d7E41006FC83eb3df4D0a'>, <Account '0x537f82751024Ed22F9f943e32A30e47B5A1Da6Df'>, <Account '0x28556574cfE7331738FadEE2fb6eC2630A9C142b'>, <Account '0xfe96534827B237951d4Ab4018Ff8c014B8918521'>, <Account '0xF32D5DfeCf6e1F6Fb2b29DF29Aba8fBd6bCB9D9E'>, <Account '0x88fdA9f0938A1fB5C5C902aeA8Bf28dE76d5467f'>, <Account '0xf6FAdCF3e805E61B1051A610a38365c74310ecCc'>, <Account '0x42904B9429E8d0599b90d6E24dF578684c0E03d3'>]
هر حساب منفرد توسط یک شیء به نام Account نشان داده می شود که می تواند اقداماتی مانند درخواست موجودی یا ارسال ETH انجام دهد.
>>> accounts[0] <Account '0xd5373B59FDF14e9F1f5cc858eCAE0e6662FfedC1'> >>> dir(accounts[0]) [address, balance, deploy, estimate_gas, nonce, transfer] >>> accounts[1].balance() 100000000000000000000 >>> accounts[0].transfer(accounts[1], "10 ether") Transaction sent: 0xb9738009af0a8b721bca854572ce21622ebfeb2aca5d89eccfc55dfd42a5 d202 Gas price: 0.0 gwei Gas limit: 21000 Transaction confirmed - Block: 1 Gas used: 21000 (100.00%) <Transaction '0xb9738009af0a8b721bca854572ce21622ebfeb2aca5d89eccfc55dfd42a 5d202'> >>> accounts[1].balance() 110000000000000000000
هر قرارداد و کتابخانه قابل استقرار دارای یک کلاس ContractContainer است که برای استقرار قراردادهای جدید و دسترسی به قراردادهای موجود استفاده می شود. تمام قراردادهایی که کامپایل شده اند به عنوان متغیری به همین نام در دسترس خواهند بود.
>>> type(SimpleContract) <class 'brownie.network.contract.ContractContainer'> >>> SimpleContract [] >>> SimpleContract.abi [ { 'constant': True, 'inputs': [], 'name': "getValue", 'outputs': [ { 'internalType': "uint256", 'name': "", 'type': "uint256" } ], 'payable': False, 'stateMutability': "view", 'type': "function" }, { 'constant': False, 'inputs': [ { 'internalType': "uint256", 'name': "_value", 'type': "uint256" } ], 'name': "setValue", 'outputs': [], 'payable': False, 'stateMutability': "nonpayable", 'type': "function" } ] >>> accounts[0] <Account '0xd5373B59FDF14e9F1f5cc858eCAE0e6662FfedC1'>
برای استقرار قرارداد با حساب صفر خواهیم داشت:
>>> accounts[0].deploy(SimpleContract) Transaction sent: 0xf0681bafbeae7e36de5a944fe247b6a8e08289906b602e986a5a8ca2c39d 0df3 Gas price: 0.0 gwei Gas limit: 100127 SimpleContract.constructor confirmed - Block: 2 Gas used: 100127 (100.00%) SimpleContract deployed at: 0x531afb27345047755741513b4dC70AD35e6F986b <SimpleContract Contract '0x531afb27345047755741513b4dC70AD35e6F986b'>
اگر SimpleContract را در کنسول تایپ کنید، می توانید لیستی از نمونه های مستقر شده از SmartContract را مشاهده کنید که یک شیء ContractContainer است.
>>> SimpleContract [<SimpleContract Contract '0x531afb27345047755741513b4dC70AD35e6F986b'>]
Interaction
متدهای های موجود در این قرارداد را ببینید:
>>> SimpleContract.signatures { 'getValue': "0x20965255", 'setValue': "0x55241077" }
بیایید با تنظیم یک متغیر در قرارداد هوشمند خود شروع کنیم.
>>> tx = SimpleContract[0].setValue(10000) Transaction sent: 0x8efb92f53c3995294597b13b81c2b067e1cece2c29333accde7e9e7f61ee 1acc Gas price: 0.0 gwei Gas limit: 41717 SimpleContract.setValue confirmed - Block: 4 Gas used: 41717 (100.00%)
هر تراکنش یک شیء TransactionReceipt را باز می گرداند. این شیء شامل تمام اطلاعات مرتبط در مورد تراکنش و همچنین روش های مختلف برای کمک به اشکال زدایی در صورت بازگشت آن است.
برای دریافت اطلاعات قابل خواندن توسط انسان در مورد یک تراکنش، از دستور ()TransactionReceipt.info استفاده کنید.
>>> tx.info() Transaction was Mined --------------------- Tx Hash: 0x8efb92f53c3995294597b13b81c2b067e1cece2c29333accde7e9e7f61ee 1acc From: 0xd5373B59FDF14e9F1f5cc858eCAE0e6662FfedC1 To: 0x531afb27345047755741513b4dC70AD35e6F986b Value: 0 Function: SimpleContract.setValue Block: 4 Gas Used: 41717 / 41717 (100.0%)
برای بررسی نیز از دستور زیر استفاده خواهیم کرد:
>>> SimpleContract[0].getValue() 10000