ساناز موسوی | 1397.04.29

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم (قسمت دوم)

پایه ‌های solidity

پایه‌ های نوشتن یک کد ساده و راه‌ اندازی آن

اجازه دهید با یک قرارداد هوشمند ابتدایی اتریوم که به زبان solidity نوشته شده است شروع کنیم. می ‌توانیم از کد کوچک بالا ببینیم که سینتکس بسیار ساده ‌ای برای تعریف تابع و بعضی از انواع داده ‌های پایه مانند «unit» (اعداد صحیح بدون علامت)، «string» و همچنین انواع متغیر هایی مانند «address» که شامل روش ‌های «.balance» و «.transfer» می ‌شود، دارد. این روش‌ ها به طور تخمینی موجودی یک آدرس اتریوم را بر می ‌گردانند و به قرارداد هوشمند این اجازه را می‌ دهد که واقعا ether به آن آدرس بفرستد (با واحد های تعریف شده در Wei).

آرایه ‌ها،  enum و عملگر ها، hash data structures «mapping» نامیده می ‌شوند.  برخی واحد های استاندارد و متغیر های از پیش تعریف شده، شامل چیز هایی مانند ارتفاع بلوک (blockheight)، last block timestamp و برخی توابع مفید SHA-hash و توابع دستکاری address/key، می ‌باشد. یک تناقض مهم در مورد solidity هنگام کار با آرایه ‌های چند بعدی ایندکس‌ گذاری آرایه ‌ها، به ترتیب معکوس بسیاری از زبان‌ هاست، به صورتی که:

«یک آرایه با سایز k و عنصر نوع T به صورت T[k] و یک آرایه با اندازه پویا به صورت T[] نوشته می‌ شود. به عنوان یک نمونه، یک آرایه‌ ی متشکل از پنج آرایه ‌ی پویا از unit به صورت unit[][5] قابل استفاده است (توجه کنید که نشانه ‌گذاری در مقایسه با برخی زبان‌ های دیگر به صورت معکوس است). برای دسترسی به unit دوم در سومین آرایه‌ ی پویا، باید از x[2][1] استفاده کنید (اندیس‌ ها مبتنی بر صفر هستند و دسترسی به صورت متضاد از آنچه تعریف شده است کار می‌کند).»

خب، تا به اینجا به قدر کافی با ابزار های پایه‌ ای آشنا شدید.

بیشتر بخوانید: آموزش گام به گام زبان برنامه نویسیSolidity برای ساخت DApp اتریوم (مقدمه)

در ادامه روی یک اپلیکیشن شبکه اجتماعی کار می کنیم؛ اپلیکیشنی که در آن مردم می‌ توانند تعدادی عکس و مقداری اطلاعات در مورد خودشان روی بلاک چین پست کنند. هر کاربر یک آدرس اتریوم و یک «handle» یا نام کاربری مرتبط با حسابش خواهد داشت. به یک سری اطلاعات جمعیت شناختی که مجوز ورود افراد را بررسی کند نیز نیاز خواهیم داشت. همچنین، یک صفحه‌ ی ساده ی جست‌ و جو خواهیم داشت؛ جایی که شما می ‌توانید مستقیما به دنبال کاربر مورد نظرتان بگردید و بررسی کنید که عکسی به پروفایل خود اضافه کرده است یا نه.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

ساخت اپلیکیشن غیرمتمرکز (DApp) بر پایه قرارداد هوشمند اتریوم

اولین گام راه اندازی بستر آزمایش (test-bed) truffle است که امکان کامپایل کردنِ آسانِ قرارداد هوشمند را فراهم می‌ کند. کامپایل و پیاده سازی قرارداد های هوشمند شامل ایجاد معاملات طولانی امضا شده می ‌شود، اما truffle به ما اجازه می ‌دهد تا کامپایل یا پیاده ‌سازی را انجام دهیم و قرارداد هوشمند را با دستوراتی ساده آزمایش کنیم. ابزار های دیگری نیز برای انجام چنین کاری وجود دارند. با این حال این آموزش را با truffle پیش می بریم. دموی این پروژه در گیت هاب برای آموزش به علاقه مندان ایجاد شده است. با دستورات ساده نصب (برنامه تحت محیط debian linux) آغاز می‌کنیم:

tectract@blockgeeks/ethereum-demo-tools> npm i -g truffle
  # if you run into permissions error globally installing node modules, see here

  tectract@blockgeeks/ethereum-demo-tools> mkdir Geekt  # I'm going to call my social application "Geekt"
  tectract@blockgeeks/ethereum-demo-tools> cd Geekt
  tectract@blockgeeks/ethereum-demo-tools/Geekt> truffle init # this creates a few folders and setup files, within the Geekt/ folder
  tectract@blockgeeks/ethereum-demo-tools/Geekt > ls
  contracts  migrations  test  truffle.js                  # we have created three folders, and one new file called truffle.js

  tectract@blockgeeks/ethereum-demo-tools/Geekt > ls contracts/
  ConvertLib.sol  MetaCoin.sol  Migrations.sol             # we have some basic template contracts generated by Truffle

  tectract@blockgeeks/ethereum-demo-tools/Geekt > ls migrations/
  1_initial_migration.js  2_deploy_contracts.js            # a couple fi
آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

بیشتر بخوانید: ترجمه وایت پیپر رمز ارز اتریوم (Ethereum)

یک مثال ساده از قرارداد هوشمند

حال، یک قرارداد با نام Geekt.sol، در پوشه‌ی /Geekt/contracts، که در هنگام فراخوانی «truffle init» ساخته شده، ایجاد می‌کنم. کد کامل قرارداد از اینجا قابل دسترس است، ما فقط به جالب ترین نکات اشاره خواهیم کرد. در خط بالای فایل، نسخه خاص کامپایلر، بعضی از تعریفات ابتدایی سینتکس‌ها و تعریف متغیر ها مشخص شده است.

pragma solidity ^0.4.4;
contract Geekt {

  address GeektAdmin;

  mapping ( bytes32 => notarizedImage) notarizedImages; // this allows to look up notarizedImages by their SHA256notaryHash
  bytes32[] imagesByNotaryHash; // this is like a whitepages of all images, by SHA256notaryHash

  mapping ( address => User ) Users;   // this allows to look up Users by their ethereum address
  address[] usersByAddress;  // this is like a

وقتی یک تابعِ تغییر حالت را در دنیای حقیقی فراخوانی می ‌کنید، باید مشخص نمایید که مایلید چه مقدار Ether برای انجام آن بپردازید. خوشبختانه می‌ توانید تخمین هزینه ‌ی gas cost و gas price را پیشتر درخواست کنید که معمولا خیلی هم دقیق است. چنانچه استخراج‌ کننده ‌های اتریوم تشخیص دهند که کارمزد کافی را پرداخت کرده‌ اید، تراکنش تغییر حالت شما در بلوک بعدی گنجانده می‌ شود. در حقیقت، باید منتظر بمانید تا توابع، وقتی بلوک جدید استخراج شد، بازگردند. خواندن داده خارج از ساختار های «storage» رایگان است و لازم نیست منتظر بلوک بعدی بمانید؛ نود ها فقط داده ‌ها را می ‌خوانند و آن ‌ها را به شما باز می ‌گردانند.

بنابراین، نگاشت کاربران (Users mapping)، storage اصلی شی‌‌ء ماست که اجازه می ‌دهد شی‌ء های User (کاربر) بسازیم و آن‌ها را با استفاده از آدرس ببینیم. نگاشت‌ ها برای ذخیره ‌ی داده و بازیابی سریع آن بسیار کاربردی هستند، اما هیچ راه آسانی برای تکرار نگاشت وجود ندارد. بنابراین همانطور که می‌ بینید یک آرایه ‌ی آدرس usersByAddress ساخته‌ ایم که هر آدرس شناخته شده‌ ای از کاربر را در سیستم ما ذخیره می ‌کند.

از نگاشت notarizedImages استفاده می کنیم تا یک شیء «image» درون storage بسازیم و آنها را با هش SHA256 مرتبط با داده‌ ی image جست و جو کنیم. اندیس‌ های notaryHash سی و دو بایت طول دارند.

همچنین یک آرایه ‌ی ۳۲ بایتی ImagesByNotaryHash داریم که یک لیست از همه ‌ی notrayHash ها است و مانند white-pages به ما اجازه می‌ دهد همه تصاویری را که مورد تایید قرار گرفته‌ اند، تکرار کنیم.

struct notarizedImage {
    string imageURL;
    uint timeStamp;
  }

  struct User {
    string handle;
    bytes32 city;
    bytes32 state;
    bytes32 country;
    bytes32[] myImages;
  }

این ‌ها ساختار های بسیار ابتدایی هستند و همچنین اشیاء «storage» را ارائه می ‌دهند، بنابراین واقعا Ether هزینه می‌ کنند تا «ساخته شوند» و داده‌ ی درون آنها درون بلاک چین اتریوم ذخیره می‌ شود. در حقیقت ما از نگاشت‌ های خارجی و آرایه‌ ها استفاده می ‌کنیم تا مسیر ذخیره users یا images را در حافظه‌ ی بلاک چین داشته باشیم. ساختار notarizedImage به سادگی URL را در تصویر ذخیره می ‌کند و هشدار timestamp ما را از زمان notarize تصویر آگاه می ‌نماید. ساختار User برخی از اطلاعات پایه ی کاربران مثل شهر، ایالت، کشور و همچنین یک آرایه از تصاویری که کاربر به حساب کاربری خود اضافه کرده را ذخیره می ‌کند. شبیه یک ورژن کوچک کاربر به کاربر از شیء white-pages جهانی imagesByNotaryHash.

function registerNewUser(string handle, bytes32 city, bytes32 state, bytes32 country) returns (bool success) {
    address thisNewAddress = msg.sender;
    // don't overwrite existing entries, and make sure handle isn't null
    if(bytes(Users[msg.sender].handle).length == 0 && bytes(handle).length != 0){
      Users[thisNewAddress].handle = handle;
      Users[thisNewAddress].city = city;
      Users[thisNewAddress].state = state;
      Users[thisNewAddress].country = country;
      usersByAddress.push(thisNewAddress);  // adds an entry for this user to the user 'whitepages'
      return true;
    } else {
      return false; // either handle was null, or a user with this handle already existed
    }
  }

در اینجا تابع registerNewUser را می ‌بینید. این تابع شهر، ایالت، کشور را به عنوان متغیر های ورودی در نظر می‌ گیرد و مقدار true یا false را برای موفقیت یا عدم موفقیت بر می‌ گرداند. اما عدم موفقیت به چه معنی است؟ در واقع ما نمی ‌خواهیم که کاربران بیش از اندازه اطلاعات وارد کنند، بنابراین چیزی شبیه به سیستم اولین اقدام (first-claim system) برای دسته‌ ها را به کار می‌ بریم. اگر کاربری با این اطلاعات در حال حاضر موجود باشد، مقدار null را بر می ‌گردانیم، بنابراین یک if شرطی گذاشته ‌ایم تا آن را بررسی کند. همچنین، اجازه ایجاد نام کاربری بدون دسته را به کاربران نمی ‌دهیم. thisNewAddress تابع را فراخوانی می‌ کند. مشاهده می ‌کنید که از تابع خاص msg.sender برای دریافت اطلاعات استفاده می‌ کنیم؛ این آدرسی است که تراکنش «do this function» وقتی کسی تابع قرارداد هوشمند را فراخوانی کند، فرستاده می‌ شود و باید Ether حقیقی برای انجام آن پرداخت شود. بنابراین یک شیء «mapping» به صورت پیش فرض null است. زمانی که این خط را فراخوانی می ‌کنیم:

Users[thisNewAddress].handle = handle;

این یک شیء جدید User در نگاشت User ساخته می‌ شود و اطلاعات (مشابه شهر، ایالت و کشور) را ثبت می‌ کند. همچنین، توجه داشته باشید که آدرس جدید کاربر را، قبل از اینکه true را برگردانیم، به userByAddress global Users “white-pages”  منتقل می ‌کنیم.

 function addImageToUser(string imageURL, bytes32 SHA256notaryHash) returns (bool success) {
    address thisNewAddress = msg.sender;
    if(bytes(Users[thisNewAddress].handle).length != 0){ // make sure this user has created an account first
      if(bytes(imageURL).length != 0){   // ) {  // couldn't get bytes32 null check to work, oh well!
        // prevent users from fighting over sha->image listings in the whitepages, but still allow them to add a personal ref to any sha
        if(bytes(notarizedImages[SHA256notaryHash].imageURL).length == 0) {
          imagesByNotaryHash.push(SHA256notaryHash); // adds entry for this image to our image whitepages
        }
        notarizedImages[SHA256notaryHash].imageURL = imageURL;
        notarizedImages[SHA256notaryHash].timeStamp = block.timestamp; // note that updating an image also updates the timestamp
        Users[thisNewAddress].myImages.push(SHA256notaryHash); // add the image hash to this users .myImages array
        return true;
      } else {
        return false; // either imageURL or SHA256notaryHash was null, couldn't store image
      }
      return true;
    } else {
      return false; // user didn't have an account yet, couldn't store image
    }
  }

تابع addImageToUser کاملا شبیه به تابع registerNewUser است. این تابع شما را توسط آدرس ارسالی از طریق شیء خاصِ msg.sender جست ‌و جو می ‌کند و اینکه کاربر در دسته ‌ای ثبت شده است را بررسی می‌ کند؛ چرا که آدرس قبل از اینکه سعی کنید عکسی را به حساب کاربری خود اضافه کنید وجود داشته است. به کاربران اجازه می ‌دهیم تا notaryHash خود را به پروتکل white-pages جهانی عکس‌ها، در صورتی که از قبل وجود نداشته باشد، اضافه کنند. این کار برای جلوگیری از کشمکش کاربران برای ورود به پروتکل white-pages جهانی است. با این حال آنها می ‌توانند هر ورودی notaryHash را داخل mini-whitepages عکس‌ های خودشان اضافه یا به روز کنند.

function getUsers() constant returns (address[]) { return usersByAddress; }

  function getUser(address userAddress) constant returns (string,bytes32,bytes32,bytes32,bytes32[]) {
    return (Users[userAddress].handle,Users[userAddress].city,Users[userAddress].state,Users[userAddress].country,Users[userAddress].myImages);
  }

  function getAllImages() constant returns (bytes32[]) { return imagesByNotaryHash; }

  function getUserImages(address userAddress) constant returns (bytes32[]) { return Users[userAddress].myImages; }

  function getImage(bytes32 SHA256notaryHash) constant returns (string,uint) {
    return (notarizedImages[SHA256notaryHash].imageURL,notarizedImages[SHA256notaryHash].timeStamp);
  }

در نهایت، توابع جانبی فوق به ما اجازه می دهد به راحتی هر کاربر و یا تصویری را بخوانیم یا یک لیست white-pages کامل از تمام کاربران و عکس‌ ها داشته باشیم. در نظر داشته باشید که این توابع داده ‌هایی ثابت را بر می‌ گردانند، که یعنی آنها فقط قابل خواندن هستند، نه «state-changing». همچنین، رایگان فراخوانی می ‌شوند و فورا اطلاعات را تحویل می ‌دهند و نیازی نیست منتظر استخراج بلوک بعدی بمانید. فقط کافی ست نگاشت جهانی یا آرایه ‌های خود را فراخوانی کنیم، داده های مربوط به ما تحویل داده می‌ شود که شامل آدرس یا تصاویر کاربران توسط notaryHash می ‌باشد.

بیشتر بخوانید: قراداد هوشمند چیست؟

تدوین و تست یک قرارداد هوشمند

حال می‌ خواهیم قرارداد هوشمند را در یک محیط آزمایشی مورد ارزیابی واقعی قرار دهیم. استفاده از دستورات Truffle بسیار راحت است اما در ابتدا به یک «نود اتریوم محلی» برای ارزیابی نیازمندیم. اینجا همان جاییست که Ethereum TestRPC وارد میدان می‌ شود. TestPRC اساسا یک نود جعلی است؛ یک برنامه حاشیه‌ای (slim program) که فقط وانمود می ‌کند یک نود است و در ماشین localhost (میزبان محلی) شما شبیه به یک نود پاسخ می ‌دهد. TestRPC روی پورت 8545 مانند یک نود اتریوم معمولی اجرا می‌ شود و توانایی کامپایل کردن قراداد هوشمند Solidity در کدِ EVM و همچنین اجرا کردن کد را دارد. به علاوه، می ‌توانید هنگام آزمایش، پاسخ ‌های سریع دریافت کنید- به جای اینکه مجبور باشید در شبکه اتریوم واقعی برای استخراج بلوک بعدی منتظر بمانید.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

حال می‌توان دستورات کامپایل یا گسترش تستِ Truffle را به جای یک نود واقعی اتریوم اجرا کرد، اما به خاطر داشته باشید که این کار به اندازه ‌ی Ether واقعی هزینه در بر دارد. همچنین، برای اجرا کردن نود خودتان حتی در صورتی که نیازی به آن نداشته باشید، باز هم زمان اجرا و حافظه صرف می‌ شود. در ادامه چند دستور نصب سریع را ذکر می ‌کنیم:

npm i -g ethereum-testrpc

testrpc -m “sample dog come year spray crawl learn general detect silver jelly pilot”

TestRPC با عبارتِ «seed»ِ مشخص شده راه اندازی می‌ شود و شما باید چنین چیزی را در خروجی ببینید:

EthereumJS TestRPC v3.0.5

Available Accounts
==================
(0) 0x0ac21f1a6fe22241ccd3af85477e5358ac5847c2
(1) 0xb0a36610de0912f2ee794d7f326acc4b3d4bc7bc
...
(9) 0x4c1cc45ef231158947639c1eabec5c5cb187401c

Private Keys
==================
(0) 91e639bd434790e1d4dc4dca95311375007617df501e8c9c250e6a001689f2c7
(1) afaeff0fc68439c4057b09ef1807aaf4e695294db57bd631ce0ddd2e8332eea7
...
(9) dcc51540372fa2cf808efd322c5e158ad5b0dbf330a809c79b540f553c6243d7

HD Wallet
==================
Mnemonic:      sample dog come year spray crawl learn general detect silver jelly pilot
Base HD Path:  m/44'/60'/0'/0/{account_index}

Listening on localhost:8545

با اجرای TestRPC، همچنان که قرارداد هوشمند توسط Truffle یا Web3.js بسط و ارتباط می‌ یابد، شاهد فعالیت‌ هایی در این پنجره خواهید بود. اکنون برای انجام تست آماده هستیم. لازم است file /migrations/2_deploy_contracts.js را به گونه ‌ای اصلاح کنید که نام قرارداد هوشمند شما Truffle را شامل شود تا بداند که باید آن را کامپایل (compile) و آماده کار (deploy) کند. دستور این است:

truffle compile

اگر همه چیز خوب پیش رود، پیام «saving artifact» را خواهید دید که بیانگر عدم وجود هرگونه خطایی است. در صورتی که قرارداد شما دارای خطا های سینتکس یا مسائل دیگر باشد، تفسیر پیغام خطای کامپایلری که خواهید دید احتمالا عجیب و خیلی سخت خواهد بود. اگر خطایی در مورد «stack size» دریافت کردید، احتمالا به این معنی است که متغیر های زیادی را وارد تابع فراخوانی و یا از آن خارج کرده‌اید. یک روش خوب این است که به خاطر داشته باشید حداکثر ۱۶ متغیر ورودی یا خروجی می‌توان به کار برد. همچنین، به یاد داشته باشید که قرارداد های هوشمند Solidarity نمی ‌توانند داده ‌های با ساختار مرسوم را بازگردانند، بنابراین شما در یک محدوده کار می ‌کنید. معمولا من فقط آرایه ‌ها و اشاره‌ گر ها یا آدرس‌ دیگر ساختار ها را در نگاشت ‌های داخلی خود بر می ‌گردانم. اگر پیام خطایی در زمان اجرا در مورد «stack» دریافت کردید، ممکن است به این معنی باشد که کد شما در شرایط بدی است.

truffle migrate

فرمان (بالا) در واقع کارِ راه ‌اندازی تست قرارداد هوشمند شما را در نود TestRPC  انجام می ‌دهد. خروجی بدین صورت است:

Running migration: 1_initial_migration.js
  Deploying Migrations...
  Migrations: 0xd06a1935230c5bae8c7ecf75fbf4f17a04564ed8
Saving successful migration to network...
Saving artifacts...
Running migration: 2_deploy_contracts.js
  Deploying Geekt...
  Geekt: 0xe70ff0fa937a25d5dd4172318fa1593baba5a027
Saving successful migration to network...
Saving artifacts..

قرارداد هوشمند ما (که Geekt نام دارد) زمانی که با موفقیت راه اندازی شود، یک آدرس در بلاک چین اتریوم می ‌دهد. همانطور که می توانید در بالا ببینید، آدرس 0xe70ff0fa937a25d5dd4172318fa1593baba5a027 است. در شبکه اتریوم فعلی، شما  gas پرداخت می ‌کنید تا قراردادتان را اجرایی کنید و آدرس شما هیچ وقت تغییر نمی‌ کند. در TestRPC، اگر این گزینه را خاموش کنید، همه چیز فراموش خواهد شد و هنگامی که دوباره شروع به پشتیبانی TestRPC می‌ کنید، باید دوباره قرارداد خود را راه ‌اندازی کنید. در این صورت، آدرس متفاوتی برای قرارداد هوشمند خود دریافت خواهید کرد. آدرس قرارداد هوشمند شما جاییست که دیگران می توانند تراکنش‌ ها را به وسیله پیام با شما مبادله کنند تا تراکنش‌ های state-changing انجام دهند یا فقط داده ‌ها را به واسطه بلاک چین اتریوم بخوانند. قرارداد های هوشمندی که با سایر این گونه قرارداد ها در store در تعامل هستند، تغییرات یا اطلاعات را از طریق «پیام های» بلاک چین اتریوم که به عنوان سازمان های خودمختار غیر متمرکز یا DAO شناخته می ‌شوند، مبادله می ‌کنند.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

بیشتر بخوانید: راه اندازی توکن ICO بر روی شبکه اتریوم ERC-20 در کمتر از سی دقیقه

حال به  بخش سرگرم کننده می‌ رسیم. اکنون آماده‌ ایم تا آزمایش ‌های اولیه را انجام دهیم و با قرارداد هوشمندمان ارتباط برقرار کنیم. «console» را در Truffle راه ‌اندازی می‌ کنیم و نود  TestRPC localhost را به چالش می‌ کشیم تا مطمئن شویم همه چیز درست کار می ‌کند و می توانیم کاربران و تصاویر را اضافه کرده و آنها را به درستی بازیابی کنیم.

truffle console
> Geekt = Geekt.deployed()
> Geekt.then(function(instance){return JSON.stringify(instance.abi);})
> Geekt.then(function(instance){return instance.registerNewUser("Tectract","Denver","CO","USA");})

> Geekt.then(function(instance){return instance.addImageToUser('www.myimageURL.com','0x6c3e007e281f6948b37c511a11e43c8026d2a16a8a45fed4e83379b66b0ab927');})

> Geekt.then(function(instance){return instance.getUser('0x0ac21f1a6fe22241ccd3af85477e5358ac5847c2');}) > Geekt.then(function(instance){return instance.getUsers();})
> Geekt.then(function(instance){return instance.getImages();})
> Geekt.then(function(instance){return instance.get

یک مفهوم مهم که در اینجا مطرح می شود ABI است؛ یک شیء با فرمت جاوا اسکریپت که نام توابع و ورودی ‌ها یا  خروجی‌ ها را برای تعامل با قرارداد هوشمند شما توصیف می ‌کند. ABI به نحوی شبیه تعریف API (واسط برنامه و برنامه نویس) است، که برای قرارداد هوشمند طراحی شده و به مردم می ‌گوید که چگونه پیام ‌هایشان را شکل دهند.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

تابع MyregisterNewUser() شروع به کار می ‌کند. در پاسخ به فراخوانی تابع registerNewUser() در پنجره console مشاهده می‌ کنیم:

{ tx: '0x1b9f55971871921ccd23a9aa7620620c6c958a893af334087283926d4c6d60b1',
  receipt:
   { transactionHash: '0x1b9f55971871921ccd23a9aa7620620c6c958a893af334087283926d4c6d60b1',
     transactionIndex: 0,
     blockHash: '0x2be4fab68daaf8db199e2a6adea101c0f1ed06f46aba21e8e4c06e752ca3325c',
     blockNumber: 5,
     gasUsed: 145215,
     cumulativeGasUsed: 145215,
     contractAddress: null,
     logs: [] },
  logs: [] }

تابع AddImageToUser() به طور مشابه با موفقیت باز می ‌گردد و حالا می‌توانیم سوابق فردی کاربر یا پرونده های تصویری ثبت شده را از بلاک چین اتریوم بازیابی کنیم.

فراخوانی، تابع getUser() بر می گرداند:

[ 'Tectract',
  '0x44656e7665720000000000000000000000000000000000000000000000000000',
  '0x434f000000000000000000000000000000000000000000000000000000000000',
  '0x5553410000000000000000000000000000000000000000000000000000000000',
  [ '0x6c3e007e281f6948b37c511a11e43c8026d2a16a8a45fed4e83379b66b0ab927' ] ]
and I can grab that image data via the 32-byte SHA256Notary hash string I have associated with it, my getImage() function returns:

[ 'www.myimageURL.com',
  { [String: '1496256315'] s: 1, e: 9, c: [ 14962 ] } ]

همه چیز به خوبی کار می ‌کند، تست در TestRPC و Truffle انجام شده، و حالا آماده هستیم تا به طور حقیقی یک رابط کاربر گرافیکی برای Dapp خودمان بسازیم.

بیشتر بخوانید: راه اندازی توکن ICO بر روی شبکه اتریوم ERC-20 در کمتر از سی دقیقه

طراحی Dapp اتریوم پایه:

طراحی و آزمایش localhost:

تا کنون، قرارداد هوشمند زبان برنامه نویسی Solidity خود را با موفقیت کمپایل و در نود محلی اتریوم testRPC راه اندازی کردیم. در حال حاضر می‌ توانیم به سرعت یک Dapp ساده ایجاد کنیم که به کاربران امکان می‌ دهد از طریق مرورگر وب خود با قرارداد هوشمند ارتباط برقرار کنند. از این پس از برخی برنامه نویسی پایه وب و ماژول ویژه جاوا اسکریپت Web3.js، که به طور خاص برای ارتباط با نود های اتریوم و قرارداد های هوشمند تحت وب ساخته شده اند، استفاده می ‌کنیم.

این نسخه نمایشی (open-source github repositories) به عنوان یک مرجع آماده شده است تا دیگران بتوانند از ان برای ساخت DApp های اتریوم به وسیله ی web3.js استفاده کنند. این نسخه‌ی نمایشی با یک ابزار کاربرد آسان  به نام Create-React-App ساخته شده است که از یک زبان وب داخلی واکنشی Facebook استفاده می‌ کند. ما نمی ‌خواهیم روی هیچ یک از کد های React تمرکز کنیم، فقط به دستورات جاوا اسکریپت web.js نیاز داریم. کد من در CommonJS است، تقریبا مشابه با جاوا اسکریپت ساده-قدیمی ES6.

بنابراین شما باید در برنامه وب خودتان، ماژول-نود web3 را به وسیله ‌ی یک دستور مانند «npm i -S web3» نصب کنید. اگر در حال استفاده از آن هستید، در package.json file شما ذخیره خواهد شد. بنابراین، داخل یک “” تگ یا داخل یک فایل .js، یک خط شبیه این را خواهید داشت که ماژول web3 خودش را بارگذاری می کند:

import Web3 from ‘web3’;

(or, in ES6 javascript)

var Web3 = require(‘web3’);

لازم است بعضی جزئیات را در مورد نود و قرارداد هوشمندمان به Web3 اطلاع دهیم، تا بتواند اتصال برقرار کند.

شما خطوطی شبیه به این خواهید دید:

# this line specificies our localhost node IP:port settings
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

# our smart-contract address, from above
var GeektAddress = '0xe70ff0fa937a25d5dd4172318fa1593baba5a027';

# our smart-contract ABI, as raw javascript object, not quoted string!
var GeektABI = [{"constant":true,"inputs":[],"name":"getUsers","outputs":[{"name":"",   (...)

# this loads the smart-contract into our web3 object
GeektContract = web3.eth.contract(GeektABI).at(GeektAddress);

# this finds the default account listed for the node connection (account 0 from TestRPC above)
defaultAccount = web3.eth.accounts[0];
Here are some lines that demonstrate read-only functions interfacing with our smart-contract, via web3:

# the below function is activated when the page loads, to get the "white-pages" of all known user addresses
GeektContract.getUsers(function(err,usersResult){

# the below function grabs an image as needed, and stores them in browser memory...
GeektContract.getImage(imageHash,function(err,imageRe

در این نسخه ‌ی نمایشی ساده Dapp فقط آدرس‌ تمام کاربر های شناخته شده بارگذاری می ‌شود؛ جزئیات واقعی تصویر کاربر واقعی را بر می دارد و تصویر را به عنوان on-the-fly ثبت می‌ کند. اسکرین شات زیر را ببینید:

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

در این مرحله (تصویر بالا) من TestRPC  مشتری‌ ام را refresh کرده‌ ام و فقط دستور «truffle migrate» را انجام داده‌ ام که قرارداد هوشمند را با نود localhost TestRPC راه‌ اندازی می‌ کند. می‌ بینید که رابط کاربری برای ثبت نام و کلیک دکمه «register» آماده است. وقتی این کلیک انجام می ‌شود تابع registerNewUser() برای قرارداد هوشمند فراخوانی خواهد شد و داده ‌های کاربر (data-user) را به نود بلاک چین اتریوم من (تست localhost) در آدرس قرارداد هوشمندم که در بالا اشاره شد، اضافه می‌ کند. اضافه کردن داده-کاربر به حافظه بلاک چین، هزینه ‌ی gas را در پی دارد و باید محاسبه کنیم چقدر gas باید پرداخت کنیم.

در اینجا کدی داریم که وقتی کلید «Sign Guestbook» را کلیک می کنیم، فراخوانی می شود.

GeektContract.registerNewUser.estimateGas(
outerThis.state.defaultHandle,
outerThis.state.defaultCity,
outerThis.state.defaultState,
outerThis.state.defaultCountry,
{from:defaultAccount},
function(err, result){

...

  var myGasNum = result;
  GeektContract.registerNewUser.sendTransaction(
  outerThis.state.defaultHandle,
  outerThis.state.defaultCity,
  outerThis.state.defaultState,
  outerThis.state.defaultCountry,
  {from:defaultAccount, gas: myGasNum},
  function(err, result){

بنابراین همانطور که می ‌بینید، وقتی یک تراکنش state-changing انجام می ‌دهیم، مجبوریم gas اتریوم  بپردازیم (gas واقعی یا آزمایشگاهی). تابع registerNewUser.estimateGas() را پیشاپیش فرا می ‌خوانیم و سپس وقتی که تابع state-changing را با استفاده از   registerNewUser.sendTransaction() فرا خواندیم از مقدار gas تخمین زده شده  استفاده می‌ کنیم.

وقتی که ما TestRPC را شروع کردیم و تعدادی آدرس‌ کیف پول تستی دریافت کردیم، این حساب‌ ها به حسابی که یک نود کامل را اجرا کردید و دستور getAccounts RPC همراه آن انجام دادید (شبیه یک کیف پول فول نود)، وابسته هستند. TestRPC هنگام راه اندازی حدود ۱۰۰ واحد testnet Ether آزمایشی به هر اکانت می دهد.

وقتی توابع state-changing، مانند registerNewUser() یا addImageToUser() را فرا می‌ خوانیم، در مرحله localhost testing، همراه با نود TestRPC، TestRPC فقط testnet gas را می‌ پردازد و نتیجه تراکنش را فورا برمی گرداند. در یک فول نود localhost، بایستی حساب را قبل از اینکه تراکنش در mainnet موفقیت آمیز باشد، « unlock»کنید. با فرض اینکه gas تخمینی کافی است باید در mainnet منتظر بلوک بعدی بمانید تا تراکنش‌ تان انجام شود. اما همیشه راه ‌های بهتری هم هست.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

اتصال DApp به mainnet اتریوم

من قرار داد هوشمند را در یک mainnet اتریوم نصب و راه اندازی کردم و برنامه نسخه ‌ی نمایشی از اینجا قابل دسترس است. اگر از این سایت بازدید کنید و یک نود TestRPC یا اتریوم کامل که در localhost:8545 برای اجرا نداشته باشید، چیزی شبیه به این پیغام مشاهده خواهید کرد:

Saw connection to network: !

Saw default account: (no web3.eth node link)

آیا این بدین معنی است که شما نیاز به اجرای یک نود کامل اتریوم یا یک نود کامل سرور خصوصی مجازی (VPS) برای اتصال و ارتباط با Mainnet اتریوم دارید؟ تا همین اواخر این مساله درست بود، اما اکنون ما پلاگینِ عالیِ Metamask (برای مرورگر کروم) را داریم که اجازه می دهد از طریق مرورگر خود به Mainnet اتریوم متصل شوید. Metamask اساسا اتصال به یک نود کامل را برای شما فراهم و به صورت رایگان از آن‌ ها پشتیبانی می ‌کند.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

می‌ توانید «injected» اختصاصی web3 از پلاگین metamask که نود را به فول نود اتریوم متصل می‌ کند، در کد من مشاهده کنید. کدی که به طور خودکار به مشتری معمول شما (localhost) سوئیچ می‌ کند، و یا اتصال metamask web3 را مشاهده می ‌کنید:

function loadWeb3() {
  let web3Injected = window.web3;
  if(typeof web3Injected !== 'undefined'){
    console.log("saw injected web3!");
    web3 = new Web3(web3Injected.currentProvider);
  } else {
    console.log("did not see web3 injected!");
    web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
  }
}

در تصویر زیر، لوگوی Metamask Fox در گوشه سمت راست بالا به نمایش درآمده است. من پلاگین  Metamask را نصب کرده و به اتریوم Mainnet متصل شدم. می توانید ببینید که یک جفت کاربر را در دموی Guestbook قرارداد هوشمند «Geekt» اضافه کرده‌ ام. رابط کاربری DApp به شما این امکان را می‌ دهد تا کاربری را انتخاب و تصاویر او را مرور کنید. اگر از یک آدرس که کاربرش هنوز ثبت نشده متصل شوید، صفحه ورود به سیستم مهمان Sign Guestbook را مشاهده خواهید کرد.

من کد های دیگری نیز اضافه کرده‌ام، بنابراین شما فقط می‌توانید در URL تصویری که می‌ خواهید به حساب کاربری خود اضافه کنید را وارد نمایید. سپس SHA256 notary hash به تصویر شما اضافه می ‌شود. همچنین، SHA256 notary hashes تصاویری را که داخل صفحه لود شده است را چک خواهد کرد و یک علامت سبز کوچک یا یک «X» قرمز بر این اساس که آیا تصویر در URL و notary hashes مطابقت دارند یا خیر، به شما می ‌دهد.

برای اضافه کردن تصویر به یک حساب کاربری، پلاگین Metamask اعلام می‌ کند که به پرداخت gas واقعی اتریوم برای تراکنش Ethereum web3 نیاز دارم. پنجره ‌ای کوچک به نمایش در می‌ آید که آیا چنین تراکنشی را تایید می‌کنید؟ من این تراکنش را قبول کردم و یک اتر واقعی به کیف پول Metamask فرستادم که برای این تراکنش کافی بود.

آموزش گام به گام زبان برنامه نویسی Solidity برای ساخت DApp اتریوم

how-to-learn-solidity-2/ Source

سخنان نهایی

این مقاله باید تا حد زیادی به شما کمک کند تا نسخه آزمایشی و کد های مربوط به زبان برنامه نویسی solidity اتریوم را به خوبی آموخته و یک توسعه دهنده DApp شوید. می توانید پلاگین Metamask را نصب کنید، به MAinnet اتریوم متصل شوید و به لینک www.enledger.io/etherereum-demo-tools مراجعه کنید. در آنجا برنامه ی Guestbook / Notary Demo را مشاهده خواهید کرد و حتی اگر بخواهید می توانید guestbook و عکس های notarize را هم امضا کنید.

برای کنترل بیشتر روی قرارداد هوشمند، توانایی های بیشتری برای «صاحب» این قرارداد در نظر می گیرم؛ که بتواند آدرس اولین پرداخت کننده برای راه اندازی قرارداد هوشمند در mainnet اتریوم را، که در اینجا خودم هستم، ببیند. با این کار قصد دارم خیلی سریع به شما نشان دهم که ادمین خاص چه توانایی هایی دارد:

address GeektAdmin;
  function Geekt() payable {  // this is the CONSTRUCTOR (same name as contract) it gets called ONCE only when contract is first deployed
    GeektAdmin = msg.sender;  // just set the admin, so they can remove bad users or images if needed, but nobody else can
  }
  modifier onlyAdmin() {
      if (msg.sender != GeektAdmin)
        throw;
      // Do not forget the "_;"! It will be replaced by the actual function body when the modifier is used.
      _;
  }
  function removeUser(address badUser) onlyAdmin returns (bool success) {
    delete Users[badUser];
    return true;
  }
  function removeImage(bytes32 badImage) onlyAdmin returns (bool success) {
    delete notarizedImages[badImage];
    return true;
  }

تابع سازنده خاص Geekt() را هم نام قرارداد ساختیم. این تابع تنها یکبار، زمان اولین فراخوانی قرارداد روی بلاک چین، فراخوانی می شود. این تابع، آدرسِ msg.sender شخصی که اولین پرداخت برای راه اندازی قرارداد هوشمند را انجام داده، به ادمین نشان می دهد. همچنین، تابع اصلاح کننده خاص onlyAdmin() برای محدود کردن توابع removeUser() و removeImage() به کار می رود و این توابع فقط زمانی فعال می شوند که آدرس msg.sender آدرسِ ادمین باشد.

در این مقاله فقط نوع خاصی از قرارداد هوشمند را پوشش داده ایم. انواع دیگری از قرارداد هوشمند می توانند وجود داشته باشند. امیدواریم این آموزش انگیزه کافی برای یادگیری نوشتن هرگونه قرارداد هوشمند را به شما بدهد. قطعا هنوز چیز های بسیاری برای آموختن وجود دارد.

نکته نهایی دیگر، ساختارِ اتریوم و هزینه واقعی استفاده از قرارداد های هوشمند است. اتریوم شبکه ایست که روی یک بلاک چین عمومی، منفرد و بزرگ اجرا می شود و هرکسی می تواند داده های آن را ارزیابی و ذخیره کند. با این حال، انجام چنین کاری تا حدودی گران است. همانطور که در تصویر فوق می بینید، من ۰.۰۰۴۱۸۲ اتر که معادل ۰.۹۶ دلار است برای ذخیره یک عکس URL، SHA256 notary hash، زماندار و ۱۹۶ بایتی روی Mainnet اتریوم، پرداخت کردم. یعنی، ۵۱۵۹.۰۳ دلار برای یک مگابایت! (ارقام بر اساس زمان نگارش مقاله آورده شده است)

همانطور که پیش تر گفتیم، ایده ای که برای هزینه gas در وایت پیپر اتریوم شرح داده شده این است که در حالت ایده آل هزینه باید تقریبا ثابت باشد. هرچند، هزینه gas در زمان اجرای واقعی به تعداد بلوک ها بستگی دارد و تعداد بلوک ها در آینده ی نزدیک به قیمت بازار اتریوم نمی رسد که باعث می شود gas در شرایط واقعی گران تر باشد. همچنین، هارد فورک ها نشان داده اند که بلاک چین اتریوم واقعا یک زنجیره عمومی است و در زمان وقوع هارد فورک، از لحاظ تئوری ممکن است دیتای شما از بین برود یا قیمت دارایی پایه به شدت کاهش یابد.

ممکن است اتریوم برای چیز هایی که فضای عمومی کمی برای ذخیره سازی روی بلاک چین نیاز دارند، مثلِ دفتر اسناد رسمی آنلاین، مناسب باشد. اما ساختن یک پروژه بلاک چینی برای قرار دادن اطلاعات صنعتی روی بلاک چین اتریوم ممکن است منطقی نباشد، چرا که احتمالا نمی خواهید تمام این اطلاعات را در دسترس عموم قرار دهید و مجبور خواهید شد که برای کاربرد های خاص صنعتی کارمزد تراکنش بپردازید.

با این اوصاف، پیشنهاد می کنم پیش از شروع پروژه بلاک چینی جدید، به دقت پلتفرم مورد نظرتان را ارزیابی کنید. درمیان جدید ترین متد ها جستجو کنید و از کشف کردن نهراسید. به مردم انگیزه بدهید تا نود شما را دانلود و اجرا کنند. بهترین مکمل برنامه نویس این است که مردم از کد در دنیای واقعی استفاده کنند.

نکات سری!

فرض کنید قرارداد هوشمند خود را نوشتید، هزار بار تست می کنید تا مطمئن شوید تمام بخش ها به خوبی کار می کند. حال چطور آن را درون Mainnet  قرار می دهید؟

این کار مشکل است و معمولا نیاز پیدا می کنید که فول نود اتریوم را خودتان اجرا کنید یا پرداختی انجام دهید و کس دیگری این کار را برای شما انجام دهد و به شما اجازه ارزیابی بدهد (مثلا با VPS). برای اولین بار، می توانید قرارداد هوشمند کامل خود را در Mainnet اتریوم، بدون نیاز به فول نود اتریوم خودتان، تست و حتی اجرا کنید. ابزار EthDeployer نرم افزار رایگانیست که به همین منظور ساخته شده است (از اینجا نیز قابل دسترس است). کافیست پلاگین Metamask را نصب کنید و ابتدا به Mainnet اتریوم متصل شوید. به علاوه نیاز دارید مقداری اتر در کیف پول Metamask خود برای پرداخت کارمزد راه اندازی قرارداد هوشمند Mainnet داشته باشید.

این ابزار از Browser-Solc استفاده می کند که فقط یک browserified، ابزار بارگیری minified برای بارگذاری ورژن Solidity کامپایلرِ جاوا اسکریپت، مستقیما در مرورگر سرویس گیرنده، برای تجزیه و تحلیل یا کامپایل یک قرارداد هوشمند on-the-fly، است. این ویژگی باعث می شود این ابزار پرتابل باشد. کد زیر آخرین کامپایلر  Solidity موجود را بارگذاری می کند:

<script src=”./browser-solc.min.js” type=”text/javascript”></script>

browser-solc.min.j جاوا اسکریپ را با صفحه سطح بالای index.html بارگذاری می کنیم که شیء window.BrowserSolc را برای عکس العمل اسکریپت های سطح پایین، می سازد. این برنامه بسیار ساده است و در عرض چند دقیقه روی دستگاه شما نصب می شود.

setupCompiler(){
    var outerThis = this;
    setTimeout(function(){
      // console.debug(window.BrowserSolc);
      window.BrowserSolc.getVersions(function(soljsonSources, soljsonReleases) {
        var compilerVersion = soljsonReleases[_.keys(soljsonReleases)[0]];
        console.log("Browser-solc compiler version : " + compilerVersion);
        window.BrowserSolc.loadVersion(compilerVersion, function(c) {
          compiler = c;
          outerThis.setState({statusMessage:"ready!"},function(){
            console.log("Solc Version Loaded: " + compilerVersion);
          });
        });
      });
    },1000);
  }

تابع setupCompiler پس از یک ثانیه انتظار برای شیء window.BrowserSolc روی صفحه بارگذاری می گردد. سپس توابع داخلی .getVersions() و .loadVersion() و کامپایلر تابعی Solc مستقیما در محیط مشتری اجرا می شوند. برای تکمیل، خطوط مربوط به اجرای واقعی و به کار گیری قرارداد درون یک تابع جاوا اسکریپت را می آورم:

 

compileAndDeploy() {
     ...
     var result = compiler.compile(this.state.contractText, optimize);
     var abi = JSON.parse(result.contracts[_.keys(result.contracts)[0]].interface);
     var bytecode = "0x" + result.contracts[_.keys(result.contracts)[0]].bytecode;
     var myContract = web3.eth.contract(abi);
     ...
       web3.eth.estimateGas({data: bytecode},function(err,gasEstimate){
          ...
          myContract.new({from:web3.eth.accounts[0],data:bytecode,gas:inflatedGasCost},function(err, newContract){
            ...
            thisTxHash: newContract.transactionHash,
            thisAddress: newContract.address

پیش تر با تمام این شیء ها آشنا شده ایم. compiler.compile() را روی متن قرارداد فراخوانی کرده، شیء «نتیجه» قرارداد اجرا شده را از abi و bytecode استخراج می کنیم و به یک تراکنش جدید می فرستیم. تابع .estimateGas() برای انجام تخمین خوب، اینجا قرار داده شده است. وقتی قرارداد با موفقیت اجرا می شود، شناسه تراکنش و آدرس قرارداد جدید را به کاربر نشان می دهیم.اگر هنگام اجرا با پیغام خطا مواجه شدید، حتما لینک شناسه تراکنش را چک کنید. احتمالا قرارداد به طور موفقیت آمیز در حال اجراست؛ گاهی Metamask در انتخاب تراکنش موفق و برداشتن آدرس جدید دچار اشکال می شود.در نهایت، من ۰.۰۲۲۶۸ اتر و معادل ۵.۳۰ دلار در تاریخ ۱ ژوئن ۲۰۱۷ برای راه اندازی قرارداد در Mainnet اتریوم پرداخت کردم. به خاطر آورید که پیش نمایش خیلی ساده اپلیکیشن با تنها دو ساختار داده مد نظرمان بود. بنابراین اصلا مقرون به صرفه نیست. هرچند، شما اغلب تنها یکبار نیاز دارید که قرارداد هوشمندتان را در زنجیره اصلی اجرا کنید.

توجه!

دیدگاه هایی که در این مقاله ارائه شده اند، متعلق به نویسنده می باشند و لزوماً مربوط به Coiniran نمی باشد و نباید به آن نسبت داده شود.

Source