#قراردادهایهوشمند
Explore tagged Tumblr posts
Text
راهنمای جامع پیاده سازی قراردادهای هوشمند با اتریوم
این راهنما به دو بخش تقسیم میشود: نحوه شروع ایجاد قراردادهای هوشمند روی اتریوم و نکاتی در خصوص امنیت قرارداد هوشمند. پس در این مقاله میتوانید شروع کردن کار با قراردادهای هوشمند اتریوم را یاد بگیرید.
۰- مفاهیم پایه ای
در این راهنما فرض بر این شده است که سطح مقدماتی از مفاهیم فنی نحوه کارارزهای دیجیتال و بلاک چین را میدانید. در غیر این صورت به شما پیشنهاد میکنیم که کتاب مهارت در بیت کوین از آندریاس آنتونوپولوس را مطالعه کنید. قبل از ادامه این آموزش باید بدانید که کلید خصوصی و عمومی چیست، چرا بلاک چین به ماینرها نیاز دارد، چطور میتوان به اجماع غیرمتمرکز رسید، تراکنش چیست و هم چنین مفهوم قرارداد هوشمند و اسکریپت نویسی تراکنش را بدانید. دو مفهوم مهم دیگری که قبل از کار با اتریوم باید بدانید، گس و ماشین مجازی اتریوم میباشند. اتریوم به عنوان پلتفرم برای ایجاد قراردادهای هوشمند طراحی شده است. منشا اتریوم در واقع به انتقاد ویتالیک بوترین از بیت کوین در خصوص محدودیت بسیار زیاد آن در قراردادهای هوشمند برمیگردد. ماشین مجازی اتریوم (EVM) جایی در اتریوم است که قراردادهای هوشمند در آنجا اجرا میشوند. ماشین مجازی اتریوم در خصوص اسکریپت نویسی نسبت به بیت کوین، زبان کاملتر و گسترده تری میباشد. در واقع یک زبان برنامه نویسی تورینگ کامل است. ماشین مجازی اتریوم همچون یک رایانه توزیع شده جهانی میباشد که تمام قراردادهای هوشمند در آن اجرا میشود. با در نظر گرفتن این موضوع که قراردادهای هوشمند در EVM اجرا میشوند، باید مکانیزمی برای محدود کردن منابع مورد استفاده هر قرارداد وجود داشته باشد. هر عملیاتی که در EVM اجرا میشود در واقع به طور همزمان توسط هر نود شبکه اجرا میشود. به این دلیل است که گس وجود دارد. کد قرارداد تراکنش اتریوم میتواند خواندن و نوشتن اطلاعات را فعال کند، محاسبات سنگین نظیر استفاده از کدهای اولیه رمزنگاری، ارسال پیام به قراردادهای دیگر و غیره را انجام دهد. هر کدام از این عملیات تا هزینه ای دارد که بر حسب گس محاسبه میشود و هر واحد گس مصرف شده توسط تراکنش باید به صورت اتر پرداخت شود، از اینرو قیمت گس بر اساس اتر متفاوت میباشد. این هزینه از حساب اتریوم ارسال کننده تراکنش کم میشود. تراکنش ها هم چنین پارامتر محدودیت گس دارند که مشخص میکند تراکنش تا چه میزان گس میتواند مصرف کند. این پارامتر یک اقدام پیشگیرانه در مقابل اشتباهات برنامه نویسی است که میتواند موجودی یک حساب را خالی کند.
۱- تنظیم محیط مورد نظر
با دانستن مفاهیم پایه ای، اکنون به سراغ کدها میرویم. برای شروع توسعه برنامه های غیرمتمرکز اتریوم، به کلاینتی برای اتصال به شبکه نیاز دارید. این کلاینت مانند پنجره ای به شبکه توزیع شده عمل خواهد کرد و چشم اندازی از بلاک چین را ارائه میدهد که در آن، تمام شرایط EVM فراهم است. کلاینت های مختلفی وجود دارند که با این پروتکل هماهنگ هستند و محبوب ترین آن، گث میباشد که بازبان برنامه نویسی گو (Go) اجرا میشود. هرچند گث اصلا مورد پسند توسعه دهندگان نمیباشد. بهترین گزینه میتواند testrpc node باشد. مطمئن باشید که این کلاینت، زمان بسیار زیادی را برای شما صرفه جویی خواهد کرد. آن را نصب و اجرا کنید. ممکن است بر اساس نحوه نصب، به افزودن sudo نیاز داشته باشید. $ npm install -g ethereumjs-testrpc $ testrpc باید testrpc را در ترمینال جدیدی اجرا کنید و هنگام توسعه باید فعال بماند. هربار که testrpc را اجرا میکنید، ۱۰ آدرس جدید با سرمایه آزمایشی شبیه سازی شده تولید میکند. این سرمایه واقعی نیست بدون نگرانی از دست دادن آن میتوانید هر چیزی را امتحان کنید. محبوب ترین زبان برنامه نویسی برای نوشتن قراردادهای هوشمند اتریوم، سالیدیتی میباشد، در نتیجه از آن استفاده میکنیم. هم چنین از چارچوب توسعه ترافل استفاده میکنیم که به ایجاد کردن، کامپایل، توسعه و آزمایش قرارداد هوشمند کمک میکند. توسعه را شروع میکنیم. مجددا بر اساس نحوه نصب، ممکن است به افزودن sudo نیاز داشته باشید. زبان برنامه نویسی سالیدیتی (Solidity) چیست کاربرد Modifierها در Solidity بهترین زبان های برنامه نویسی برای توسعه بلاک چین # First, let's install truffle $ npm install -g truffle # let's setup our project $ mkdir solidity-experiments $ cd solidity-experiments/ $ truffle init ترافل ��مام فایل های مورد نیاز برای این مثال را ایجاد خواهد کرد. این فایل ها شامل قراردادها برای توکن آزمایشی ما به اسم متاکوین (Metacoin) میباشد. شما باید بتوانید قراردادهای این مثال را با اجرای truffle compile، کامپایل کنید. سپس برای اجرای قراردادها در شبکه شبیه سازی شده، باید truffle migrate را اجرا کنید. $ truffle compile Compiling ConvertLib.sol... Compiling MetaCoin.sol... Compiling Migrations.sol... Writing artifacts to ./build/contracts$ truffle migrate Using network 'development'.Running migration: 1_initial_migration.js Deploying Migrations... ... 0x686ed32f73afdf4a84298642c60e2002a6d0d736a5478cc8cb22a655ac018a67 Migrations: 0xa7edbac1156f98907a24d18df8104b5b1bd7027c Saving successful migration to network... ... 0xe3bf1e50d2262d9ffb015091e5f2974c8ebe0d6fd0df97a7dbcde8a0e51c694a Saving artifacts... Running migration: 2_deploy_contracts.js Deploying ConvertLib... ... 0x2e0e6718f01d0da6da2ada13d6e4ad662c5a20e784e04c404e9d4ef1d392bdae ConvertLib: 0xf4388ce4d4ce8a443228d65ecfa5149205db049f Linking ConvertLib to MetaCoin Deploying MetaCoin... ... 0xb03a3cde0672a2bd4dda6c01dd31641d95bd680c4e21162b3370ed6db7a5620d MetaCoin: 0x4fc68713f7ac86bb84ac1ef1a09881a9b8d4100f Saving successful migration to network... ... 0xb9a2245c27ff1c6506c0bc6349caf86a31bc9f700388defe04566b6d237b54b6 Saving artifacts... نکته ای برای کاربران سیستم عامل OS X: گاهی اوقات ترافل با فایل های DS_Store. اشتباه گرفته میشود. اگر پیام خطایی دریافت کردید که به یکی از این فایل ها اشاره میکند، پیام را حذف کنید.
۲- نوشتن اولین قرارداد هوشمند اتریوم
در این آموزش، قرارداد هوشمند ساده ای با الگوریتم گواه اثبات وجود خواهیم نوشت. ایده این کار، ایجاد دفتر ثبت دیجیتالی است که هش های اسناد را به عنوان اثبات وجود آنها ذخیره کند. برای شروع از عبارت truffle create contract استفاده کنید. $ truffle create contract ProofOfExistence1 اکنون در ویرایشگر متن مورد نظر خود، فایلی با اسم contracts/ProofOfExistence1.sol ایجاد کنید و این نسخه اولیه کد را در آن وارد کنید: pragma solidity ^0.4.15;// Proof of Existence contract, version 1 contract ProofOfExistence1 { // state bytes32 public proof; // calculate and store the proof for a document // *transactional function* function notarize(string document) { proof = proofFor(document); } // helper function to get a document's sha256 // *read-only function* function proofFor(string document) constant returns (bytes32) { return sha256(document); } } با مورد ساده و اشتباه شروع و سپس به سمت راهکارهای بهتر حرکت خواهیم کرد. این تعریف قرارداد سالیدیتی است که در بین زبان های برنامه نویسی دیگر همانند کلاس درس است. قراردادها دارای وضعیت و توابع میباشند. دانستن تفاوت بین دو نوع تابع در قرارداد بسیار مهم است: توابع فقط خواندنی (ثابت): توابعی که هیچ تغییری در وضعیت موجود ایجاد نمیکنند. این توابع فقط وضعیت را میخوانند، محاسبات را انجام میدهند و مقادیر را برمیگردانند. از آنجایی که هر نود میتواند این توابع را اجرا کند، هیچ هزینه گس ندارد و با کلمه کلیدی constant مشخص میشود. توابع تراکنشی: توابعی که در وضعیت قرارداد تغییر ایجاد میکنند یا سرمایه را انتقال میدهند. از آنجایی که این تغییرات باید در بلاک چین ثبت شود، اجرای تابع تراکنشی به ارسال تراکنش به شبکه و خرج کردن گس نیاز دارد. قرارداد ما یکی از هرکدام از توابع را دارد. ما مشاهده خواهیم کرد که کدام تابع مورد استفاده ما، چه تغییری در تعامل با قرارداد هوشمند ایجاد میکند. نسخه ساده قرارداد، در هر لحظه فقط یک اثبات ذخیره میکند و از اطلاعات ۳۲ بایتی استفاده میکند که هم اندازه هش sha256 میباشد. تابع تراکنش، ذخیره سازی هش سند در اثبات متغیر وضعیت قرارداد هوشمند ما را امکان پذیر میسازد. متغیر مورد نظر، عمومی میباشد و اگر ا��ن سند ثبت شده باشد، این تنها روشی است که کاربر قرارداد باید آن را تایید کند. این کار را به زودی انجام میدهیم، اما در ابتدا باید… باید ProofOfExistence1 را در شبکه اجرا کنیم. این بار باید فایل انتقال (migrations/2_deploy_contracts.js) را ویرایش کنید تا ترافل بتواند قرارداد جدید را اجرا کند. اطلاعات این فایل را به صورت زیر تغییر دهید: var ProofOfExistence1 = artifacts.require("./ProofOfExistence1.sol"); module.exports = function(deployer) { deployer.deploy(ProofOfExistence1); }; برای اجرای مجدد این انتقال، باید از ریست فلگ استفاده کنید تا از اجرا شدن مجدد آن مطمئن شوید. truffle migrate --reset
۳- تعامل با قرارداد هوشمند
اکنون که قرارداد ما اجرا شده است با آن بازی میکنیم. میتوانیم از طریق تماس، به آن پیام ارسال کنیم و وضعیت عمومی آن را بخوانیم. برای این کار از کنسول ترافل استفاده خواهیم کرد: $ truffle console // get the deployed version of our contract truffle(default)> var poe = ProofOfExistence1.at(ProofOfExistence1.address)// and print its address truffle(default)> poe.address 0x3d3bce79cccc331e9e095e8985def13651a86004// let's register our first "document" truffle(default)> poe.notarize('An amazing idea') { tx: '0x18ac...cb1a', receipt: { transactionHash: '0x18ac...cb1a', ... }, logs: }// let's now get the proof for that document truffle(default)> poe.proofFor('An amazing idea') 0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7// To check if the contract's state was correctly changed: truffle(default)> poe.proof() 0xa3287ff8d1abde95498962c4e1dd2f50a9f75bd8810bd591a64a387b93580ee7 // The hash matches the one we previously calculated اولین کاری که میکنیم دستیابی به نسخه ا�� از قرارداد اجرا شده و ذخیره آن در متغیری به اسم poe میباشد. سپس به تابع تراکنشی، ثبت شده یا notarize میگوییم که شامل تغییر وضعیت میباشد. وقتی ما تابع تراکنشی را فرا میخواستم در واقع تصویری را دریافت میکنیم که بیانگر هدف تراکنش است و نه خود تابع. به یاد داشته باشید که برای تغییر در وضعیت EVM باید گس خرج کنیم و تراکنش را به شبکه ارسال کنیم. به همین دلیل است که اطلاعات تراکنش را به عنوان نتیجه این تصویر دریافتند میکنیم. این اطلاعات به تراکنشی که باعث تغییر. وضعیت شده است اشاره میکند. در این مورد، ما علاقه ای به ID تراکنش نداریم پس تصویر را حذف میکنیم. هنگام نوشتن برنامه واقعی، ما در صدد ذخیره آن خواهیم بود تا نتیجه تراکنش را بررسی کنیم و خطاهای آن را متوجه شویم. سپس تابع فقط خواندنی proofFor را فرا میخوانیم. به خاطر داشته باشید که توابع فقط خواندنی خود را با کلمه کلیدی constant علامت گذاری کنید، در غیر این صورت ترافل سعی در ایجاد تراکنش برای اجرای آنها خواهد کرد. این روشی است که به ترافل بگوییم که ما با بلاک چین تعامل نمیکنیم بلکه از روی آن میخوانیم. با استفاده از این تابع فقط خواندنی، ما به هش sha256 سند دست مییابیم. اکنون باید این موضوع را در مقابل وضعیت قرارداد هوشمند قرار دهیم. برای اینکه بررسی کنیم وضعیت به طور صحیح تغییر کرده است، باید متغیر وضعیت عمومی proof را بخوانیم. برای به دست آوردن مقدار متغیر وضعیت عمومی میتوانیم تابعی با اسم مشابه را فرا بخوانیم که تصویری از مقدار خود را برمیگرداند. در مثال ما، هش خروجی یکسان است بنابراین همه موارد طبق انتظار کار میکند. همانطور که مشاهده میکنید، به نظر میرسد نسخه اول قرارداد هوشمند گواه اثبات وجود به درستی کار میکند. این قرارداد فقط برای ثبت یک سند در هر بار مناسب است. پس نسخه بهتری ایجاد میکنیم.
۴- تکرار کد قرارداد
قرارداد را برای پشتیبانی از ثبت چندین سند، تغییر میدهیم. نسخه ای از فایل اصلی به نام contracts/ProofOfExistence2.sol را کپی کنید و این تغییرات را روی آن اعمال کنید. تغییرات اصلی عبارتند از: تغییر متغیر proof به طیف ۳۲ بایتی و تغییر اسم آن به proofs، خصوصی کردن آن و افزودن تابعی برای بررسی این موضوع که این سن. قبلاً ثبت شده است یا خیر. pragma solidity ^0.4.15;// Proof of Existence contract, version 2 contract ProofOfExistence2 { // state bytes32 private proofs; // store a proof of existence in the contract state // *transactional function* function storeProof(bytes32 proof) { proofs.push(proof); }// calculate and store the proof for a document // *transactional function* function notarize(string document) { bytes32 proof = proofFor(document); storeProof(proof); }// helper function to get a document's sha256 // *read-only function* function proofFor(string document) constant returns (bytes32) { return sha256(document); }// check if a document has been notarized // *read-only function* function checkDocument(string document) constant returns (bool) { bytes32 proof = proofFor(document); return hasProof(proof); } // returns true if proof is stored // *read-only function* function hasProof(bytes32 proof) constant returns (bool) { for (uint256 i = 0; i if (proofs == proof) { return true; } } return false; } } با توابع جدید تعامل ایجاد میکنیم. (به یاد داشته باشید که migrations/2_deploy_contracts.js را آپدیت کنید تا شامل قرارداد جدید باشد و truffle migrate –reset را اجرا کنید) // deploy contracts truffle(default)> migrate --reset// Get the new version of the contract truffle(default)> var poe = ProofOfExistence2.at(ProofOfExistence2.address)// let's check for some new document, and it shouldn't be there. truffle(default)> poe.checkDocument('hello') false// let's now add that document to the proof store truffle(default)> poe.notarize('hello') { tx: '0x1d2d...413f', receipt: { ... }, logs: }// let's now check again if the document has been notarized! truffle(default)> poe.checkDocument('hello') true // success!// we can also store other documents and they are recorded too truffle(default)> poe.notarize('some other document'); truffle(default)> poe.checkDocument('some other document') true این نسخه بهتر از نسخه اول است اما همچنان مشکلاتی دارد. به خاطر داشته باشید که هربار که بخواهیم بررسی کنیم آیا سند ثبت شده است یا خیر باید تمام اثبات های موجود را تکرار کنیم. این امر باعث میشود که قراردادهای هوشمند در هر بررسی گس بیشتری مصرف کند زیرا سندهای بیشتری افزوده شده است. ساختار بهتر برای ذخیره سازی اثبات ها، ایجاد یک نقشه است. خوشبختانه سالیدیتی از نقشه ها پشتیبانی میکند و به آنها مپینگ گفته میشود. نکته دیگری که در این نسخه بهبود خواهیم داد، حذف تمام نظرات اضافه است که علامت توابع تراکنشی یا فقط خواندنی دارند. احتمالا تا الان متوجه آن شده اید. نسخه پایانی به شرح زیر است که درک آن بسیار آسان است زیرا نسخه های قبلی را پشت سر گذاشته اید: pragma solidity ^0.4.15;// Proof of Existence contract, version 3 contract ProofOfExistence3 { mapping (bytes32 => bool) private proofs; // store a proof of existence in the contract state function storeProof(bytes32 proof) { proofs = true; } // calculate and store the proof for a document function notarize(string document) { var proof = proofFor(document); storeProof(proof); } // helper function to get a document's sha256 function proofFor(string document) constant returns (bytes32) { return sha256(document); } // check if a document has been notarized function checkDocument(string document) constant returns (bool) { var proof = proofFor(document); return hasProof(proof); } // returns true if proof is stored function hasProof(bytes32 proof) constant returns(bool) { return proofs; } } این نسخه به نظر خوب میآید و دقیقا همانند نسخه دوم عمل میکند. برای امتحان کردن آن به یاد داشته باشید که فایل انتقال را آپدیت کنید و مجددا truffle migrate –reset را اجرا کنید.
۵- اجرا در شبکه آزمایشی واقعی
پس از آنکه قرارداد خود را با استفاده از testrpc در شبکه شبیه سازی شده آزمایش کردید، آماده هستید که آن را در شبکه واقعی امتحان کنید. برای انجام این کار، به شبکه آزمایشی واقعی یا کلاینت اتریوم نیاز داریم. حین توسعه، باید نودها را در حالت شبکه آزمایشی اجرا کنید تا بتوانید بدون خطر از دست دادن سرمایه واقعی، همه موارد را امتحان کنید. حالت شبکه آزمایشی (که در اتریوم به اسم مدرن شناخته میشود) اساسا مشابه با شبکه اتریوم واقعی است، اما توکن اتر در شبکه آزمایشی هیچ ارزشی ندارد. تنبلی را کنار بگذارید و به یاد داشته باشید که در شبکه آزمایشی اگر به دلیل خطای برنامه نویسی اتر واقعی از دست بدهید دیگر جای پشیمانی وجود ندارد. گث را در حالت شبکه آزمایشی و با فعال بودن سرور RPC اجرا کنید: geth --testnet --rpc console 2>> geth.log این امر، کنسولی را باز میکند که میتوانید در آن، فرمان های مقدماتی برای کنترل نود یا کلاینت خود تایپ کنید. نود شما شروع به دانلود بلاک چین شبکه آزمایشی خواهد کرد و با بررسی eth.blockNumber میتوانید میزان پیشرفت دانلود را بررسی کنید. هنگامی که بلاک چین در حال دانلود شدن است، هم چنان میتوانید فرمان ها را اجرا کنید. برای مثال در ادامه یک حساب ایجاد میکنیم: > personal.newAccount() Passphrase: Repeat passphrase: "0xa88614166227d83c93f4c50be37150b9500d51fc" چند کوین ارسال میکنیم و موجودی را بررسی میکنیم. برای بررسی موجودی، دستور زیر را اجرا کنید: > eth.getBalance(eth.accounts) 0 پس از آن هیچ موجودی نشان داده نخواهد شد زیرا نود شما هنوز با سایر شبکه سینک یا هماهنگ نشده است. هنگامی که منتظر سینک شدن هستید، موجودی خود را در مرورگر بلاک شبکه آزمایشی بررسی کنید. در اینجا میتوانید بالاترین شماره بلاک فعلی در شبکه آزمایشی را مشاهده کنید. در نتیجه با ترکیب آن با eth.blockNumber میتوانید متوجه شوید که چه زمانی نود شما به طور کامل سینک شده یا خواهد شد. پس از سینک شدن نود شما، آماده اجرای قراردادها در شبکه آزمایشی و با استفاده از ترافل میشوید. ابتدا، حساب اصلی گث خود را باز کنید تا ترافل بتواند از آن استفاده کند. هم چنین مطمئن شوید که حساب شما موجودی داشته باشد، در غیر این صورت نخواهید توانست قرارداد جدیدی به شبکه اضافه کنید. در گث دستور زیر را اجرا کنید: > personal.unlockAccount(eth.accounts, "mypassword", 24*3600) true > eth.getBalance(eth.accounts) 1000000000000000000 اگر یکی از این دو دستور کار نکرد، مراحل فوق را بررسی کنید و اطمینان حاصل کنید که به درستی آنها را انجام داده باشید. اکنون دستور زیر را اجرا کنید: $ truffle migrate --reset به خاطر داشته باشید که این بار بیشتر طول خواهد کشید، زیرا به جای اتصال به شبکه شبیه سازی شده توسط testrpc، در حال اتصال به شبکه واقعی میباشیم. پس از اتصال، میتوانید با استفاده از رویکرد مشابه با قبل، با قرارداد تعامل ایجاد کنید. نسخه اجرا شده شبکه آزمایشی ProofOfExistence3 را میتوان در آدرس 0xcaf216d1975f75ab3fed520e1e3325dac3e79e05 پیدا کرد. جزییات نحوه اجرا در شبکه اصلی را به خوانندگان میسپریم. باید پس از آزمایش های فراوان قرارداد خود در شبکه های آزمایشی و شبیه سازی شده، این کار را انجام دهید. به یاد داشته باشید که هر خطا در برنامه نویسی میتواند منجر به ضرر مالی در شبکه اصلی شود. فراهم کردن امنیت قرارداد هوشمند در اتریوم سخت است قراردادهای هوشمند، کدهای رایانه ای هستند که مشخص میکند چه مقدار پول انتقال یابد. این آموزش بدون بیان نکاتی در خصوص امنیت قرارداد هوشمند، ناقص است. در ادامه به بیان چند مورد از این نکات خواهیم پرداخت. بعضی از مسائلی که باید متوجه آنها باشید و از آنها اجتناب کنید عبارتند از: خوانش مجدد (reentrancy): پیام های خارجی را اجرا نکنید. در غیر این صورت اطمینان حاصل کنید که آخرین مرحله کار شما باشد.ارسال نشدن: هنگام ارسال پول، کد شما باید همواره برای این موضوع آماده باشد که ممکن است ارسال انجام نشود.چرخه ها یا لوپ ها میتواند محدودیت گس را از بین ببرد: هنگام لوپینگ در متغیرهای وضعیت مراقب باشید، زیرا میتوانند بزرگ شوند و از محدودیت مصرف گس تجاوز کنند.فراخوانی محدودیت عمق استک: از بازگشت استفاده نکنید و توجه کنید که اگر به سقف عمق استک برسید، هر فراخوانی میتواند با شکست مواجه شود.وابستگی به سابقه زمانی: در بخش های بسیار مهم کد از سابقه زمانی استفاده نکنید زیرا ماینرها میتوانند آن را دستکاری کنند و تغییر دهند. این موارد فقط نمونه هایی از شرایط غیرمنتظره ای است که میتواند منجر به سرقت یا از بین رفتن سرمایه در قرارداد هوشمند شود. اصل بر این است که اگر قرارداد هوشمند مینویسید، در واقع کدی مینویسید که پول واقعی را مدیریت میکند. باید بسیار مراقب باشید. کدهای آزمایشی بنویسید و آنها را به دقت بررسی و حسابرسی کنید. بهترین روش برای اجتناب از مشکلات امنیتی، درک کامل از زبان برنامه نویسی است. پیشنهاد میکنیم که موارد ثبت شده سالیدیتی را مطالعه کنید. ما برای امنیت قابل قبول در قراردادهای هوشمند به ابزار بهتر نیاز داریم. منبع: میهن بلاکچین Short link : https://arzmonitor.com/?p=5025 Read the full article
#اتریوم#بلاکچین#زبانبرنامهنویسی#زبانبرنامهنویسیبلاکچین#قراردادهوشمند#قراردادهایهوشمند#قراردادهایهوشمنداتریوم
0 notes