فهرست مطالب۷ بخش
  1. سالیدیتی به زبان ساده
  2. قرارداد سالیدیتی با یک برنامه معمولی چه فرقی دارد؟
  3. اولین قرارداد سالیدیتی: یک قلک ساده
  4. قلک را در ریمیکس اجرا کنید
  5. کامپایل و آزمایش در مرورگر
  6. انتشار روی شبکه آزمایشی Sepolia
  7. وقتی پروژه جدی شد: هاردهت یا فاندری؟
  8. اشتباه‌های امنیتی رایج در سالیدیتی
  9. حمله ورود مجدد (Reentrancy)
  10. کنترل دسترسی
  11. سه دام دیگر برای تازه‌کارها
  12. کد منتشرشده وصله نمی‌شود
  13. چک‌لیست پیش از انتشار روی شبکه اصلی
  14. مسیر یادگیری سالیدیتی

سالیدیتی (Solidity) زبان برنامه‌نویسی‌ای است که قراردادهای هوشمند اتریوم را معمولاً با آن می‌نویسند؛ همان برنامه‌هایی که توکن‌ها، صرافی‌های غیرمتمرکز و خیلی از ابزارهای دیفای روی آن‌ها سوارند. ظاهرش برای کسی که با جاوااسکریپت یا ++C کار کرده آشناست، ولی چند چیز اینجا از اساس فرق دارد: کدی که منتشر کنید دیگر روی همان آدرس عوض نمی‌شود، همه می‌توانند آن را ببینند و یک اشتباه کوچک ممکن است پول واقعی را به باد بدهد.

اینجا با یک قرارداد کوچک جلو می‌رویم: قلکی که هر کسی می‌تواند در آن اتر بریزد و فقط صاحبش می‌تواند خالی‌اش کند. کدش را خط‌به‌خط می‌خوانیم، در ریمیکس (Remix) اجرا و روی شبکه آزمایشی منتشرش می‌کنیم و بعد سراغ اشتباه‌های امنیتی معروف سالیدیتی می‌رویم. اگر هنوز نمی‌دانید قرارداد هوشمند دقیقاً چیست، اول قرارداد هوشمند به زبان ساده را بخوانید.

سالیدیتی به زبان ساده

مستندات رسمی سالیدیتی آن را زبانی سطح بالا و شیءگرا معرفی می‌کند که برای ماشین مجازی اتریوم (EVM) طراحی شده و از ++C، پایتون و جاوااسکریپت الهام گرفته است. سه ویژگی‌اش را همین اول بدانید:

  • نوع هر متغیر از قبل معلوم است. موقع تعریف هر متغیر می‌گویید عدد است، آدرس است، متن است یا ساختاری که خودتان ساخته‌اید. اگر جای عدد آدرس بگذارید، کامپایلر همان موقع خطا می‌دهد، خیلی پیش از آنکه پولی در قرارداد بنشیند.
  • هر برنامه یک «قرارداد» است. قرارداد (کلمه contract در کد) شبیه کلاس در زبان‌های دیگر است: داده و تابع‌های خودش را دارد، می‌تواند از قرارداد دیگری ارث ببرد و از کتابخانه‌ها استفاده کند.
  • پول جزو خود زبان است. قرارداد خودش می‌تواند اتر بگیرد، نگه دارد و بفرستد و برای این کار درگاه پرداختی لازم ندارد.

ماشین مجازی اتریوم کد سالیدیتی را مستقیم نمی‌فهمد. کامپایلر سالیدیتی (solc) اول آن را به بایت‌کد تبدیل می‌کند؛ بعد همین بایت‌کد با یک تراکنش روی شبکه می‌نشیند و آدرس خودش را می‌گیرد. جزئیات این مرحله را در ماشین مجازی اتریوم (EVM) چیست گفته‌ایم.

چون خیلی از شبکه‌ها همان EVM را اجرا می‌کنند، قراردادی که برای اتریوم نوشته‌اید معمولاً با تغییر کمی روی BNB Smart Chain، پالیگان و شبکه‌های لایه دوم مثل آربیتروم و آپتیمیزم هم اجرا می‌شود. ماشین مجازی ترون هم بایت‌کدی را که کامپایلر سالیدیتی می‌سازد اجرا می‌کند. پس با یاد گرفتن یک زبان، درِ چند شبکه به رویتان باز می‌شود.

سالیدیتی تنها زبان قرارداد هوشمند هم نیست. در صفحه زبان‌های قرارداد هوشمند ethereum.org، سالیدیتی و وایپر (Vyper) دو زبانی معرفی شده‌اند که از همه فعال‌ترند و هنوز پشتیبانی می‌شوند. فرقشان را در سؤال‌های آخر مطلب گفته‌ایم.

شماره نسخه سالیدیتی هنوز با صفر شروع می‌شود و به گفته مستنداتش، همین صفر نشان می‌دهد زبان هنوز تند تغییر می‌کند. آخرین نسخه، ۰٫۸٫۳۸، روز ۳۰ مهر ۱۴۰۵ منتشر شد. نتیجه عملی‌اش ساده است: برای انتشار واقعی آخرین نسخه کامپایلر را انتخاب کنید، چون طبق همان مستندات، جز در موارد استثنایی فقط آخرین نسخه وصله امنیتی می‌گیرد.

قرارداد سالیدیتی با یک برنامه معمولی چه فرقی دارد؟

اگر قبلاً سایت یا اپ نوشته‌اید، بیشتر عادت‌هایتان اینجا هم به کار می‌آید. ولی چند فرق اساسی هست که اگر جدی‌شان نگیرید، اولین قراردادتان را خراب می‌کنند:


برنامه معمولی (مثلاً یک سایت)

قرارداد سالیدیتی

کجا اجرا می‌شود

روی سرور شما

روی همه گره‌های شبکه، با قواعد EVM

رفع باگ

نسخه تازه را روی سرور می‌گذارید

کد روی همان آدرس دیگر عوض نمی‌شود

هزینه اجرا

هزینه سرور با خودتان است

هر عملیات گس مصرف می‌کند و فرستنده تراکنش کارمزدش را می‌دهد

دسترسی به اینترنت

هر API که بخواهید

فقط داده‌های خود زنجیره؛ داده بیرونی با اوراکل می‌آید

داده خصوصی

روی سرور پنهان می‌ماند

همه‌چیز روی زنجیره دیدنی است، حتی متغیر private

پول

از درگاه پرداخت رد می‌شود

اتر مستقیم به خود قرارداد می‌رسد

دو ردیف این جدول بیشتر از بقیه دردسر می‌سازد. اولی تغییرناپذیری است: به گفته ethereum.org، قراردادهای اتریوم از اساس طوری طراحی شده‌اند که بعد از انتشار نشود منطقشان را عوض کرد. پس اینجا تست پیش از انتشار شوخی‌بردار نیست.

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

اولین قرارداد سالیدیتی: یک قلک ساده

قلک مثال خوبی است، چون همه اجزای اصلی یک قرارداد را دارد: داده‌ای که روی زنجیره می‌ماند، تابعی که پول می‌گیرد، تابعی که پول می‌فرستد و شرطی که نمی‌گذارد هر کسی پول را بردارد. توضیح‌های داخل کد (خط‌هایی که با // شروع می‌شوند) را فارسی نوشته‌ایم؛ کامپایلر کاری به آن‌ها ندارد.

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// قلک ساده: همه می‌توانند پول بریزند، فقط صاحب قلک برمی‌دارد
contract PiggyBank {
    // آدرس کسی که قرارداد را منتشر کرده است
    address public owner;

    // بعد از هر واریز، این رویداد در بلاک‌چین ثبت می‌شود
    event Deposited(address from, uint256 amount);

    // فقط یک بار اجرا می‌شود: موقع انتشار قرارداد
    constructor() {
        owner = msg.sender;
    }

    // هر کسی می‌تواند اتر به قلک بفرستد
    function deposit() external payable {
        require(msg.value > 0, "Send some ETH");
        emit Deposited(msg.sender, msg.value);
    }

    // فقط صاحب قلک می‌تواند کل موجودی را بردارد
    function withdraw() external {
        require(msg.sender == owner, "Not the owner");
        uint256 amount = address(this).balance;
        (bool ok, ) = owner.call{value: amount}("");
        require(ok, "Transfer failed");
    }
}

حالا کد را تکه‌به‌تکه بخوانیم:

  1. SPDX-License-Identifier مجوز انتشار کد را مشخص می‌کند. اجرای قرارداد به آن بستگی ندارد، ولی اگر نباشد، کامپایلر هشدار می‌دهد.
  2. pragma solidity ^0.8.24 به کامپایلر می‌گوید این کد با نسخه ۰٫۸٫۲۴ و نسخه‌های بعدی سری ۰٫۸ کامپایل می‌شود. کامپایلر قدیمی‌تر یا نسخه ۰٫۹ آن را قبول نمی‌کند.
  3. contract PiggyBank قرارداد را تعریف می‌کند و هر چیزی بین دو آکولادش مال همین قرارداد است.
  4. address public owner یک متغیر حالت است؛ یعنی مقدارش روی زنجیره ذخیره می‌ماند و با تمام شدن تراکنش پاک نمی‌شود. کلمه public باعث می‌شود سالیدیتی خودش تابعی برای خواندن این متغیر بسازد تا هر کسی ببیند صاحب قلک کیست.
  5. event Deposited یک رویداد است. هر بار که کسی پول می‌ریزد، ردش در لاگ تراکنش ثبت می‌شود و کیف پول‌ها، سایت‌ها و اکسپلوررهای بلاک‌چین می‌توانند آن را بخوانند.
  6. constructor فقط یک بار اجرا می‌شود، همان لحظه‌ای که قرارداد منتشر می‌شود. منظور از msg.sender آدرسی است که تراکنش فعلی را فرستاده؛ اینجا یعنی کسی که قرارداد را منتشر می‌کند و همین آدم صاحب قلک می‌شود.
  7. deposit کلمه payable را دارد؛ یعنی می‌شود موقع صدا زدنش اتر هم فرستاد. مقدار اتری که همراه تراکنش آمده در msg.value است. خط require شرط را چک می‌کند و اگر کسی صفر بفرستد، تراکنش با پیام «Send some ETH» برمی‌گردد. در آخر هم emit رویداد را ثبت می‌کند.
  8. withdraw اول با require مطمئن می‌شود که فرستنده همان صاحب قلک است. بعد کل موجودی قرارداد، یعنی address(this).balance، را با call برایش می‌فرستد. اگر این ارسال انجام نشود، require آخر همه‌چیز را به حالت قبل برمی‌گرداند.

درباره require یک نکته را از همین حالا به خاطر بسپارید: وقتی شرطش برقرار نباشد، هر تغییری که تراکنش داده بود لغو می‌شود، انگار هیچ اتفاقی نیفتاده. ولی کارمزد کاری که تا آن لحظه انجام شده از فرستنده کم می‌شود؛ پس روی شبکه اصلی، تراکنش ناموفق هم خرج دارد.

قلک را در ریمیکس اجرا کنید

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

نمودار پنج مرحله انتشار قرارداد سالیدیتی: نوشتن کد، کامپایل، آزمایش در Remix VM، شبکه آزمایشی Sepolia و شبکه اصلی اتریوم، ۶ آبان ۱۴۰۵

کامپایل و آزمایش در مرورگر

آدرس remix.ethereum.org را باز کنید؛ ریمیکس حالا خودبه‌خود به نشانی تازه‌اش app.remix.live می‌رود. در تصویر زیر همین قلک را در ریمیکس می‌بینید: شماره ۱ نسخه کامپایلر را نشان می‌دهد، شماره ۲ تیک سبزی است که یعنی کد بی‌خطا کامپایل شده و شماره ۳ قراردادی است که برای انتشار آماده است.

محیط ریمیکس با کد قرارداد قلک به زبان سالیدیتی که با کامپایلر ۰٫۸٫۳۴ بی‌خطا کامپایل شده است، ۶ آبان ۱۴۰۵
  1. در بخش فایل‌ها (File Explorer) یک فایل تازه به اسم PiggyBank.sol بسازید و کد بالا را در آن بچسبانید.
  2. به زبانه Solidity Compiler بروید. وقتی ما روز ۶ آبان ۱۴۰۵ امتحان کردیم، ریمیکس خودش نسخه ۰٫۸٫۳۴ را انتخاب کرد که با pragma کد ما جور است. برای انتشار واقعی، آخرین نسخه را از همین فهرست انتخاب کنید.
  3. دکمه Compile را بزنید. اگر خطایی نباشد، روی آیکن کامپایلر تیک سبز می‌بینید.
  4. به زبانه Deploy & run transactions بروید و از فهرست Environment گزینه Remix VM را انتخاب کنید. این گزینه یک بلاک‌چین شبیه‌سازی‌شده داخل خود مرورگر است و طبق مستندات ریمیکس ۱۰ حساب آزمایشی دارد که در هر کدام ۱۰۰ اتر بی‌ارزش هست.
  5. دکمه Deploy را بزنید. قلک زیر Deployed Contracts ظاهر می‌شود و برای هر تابعش یک دکمه دارد: deposit، withdraw و owner.
  6. در خانه Value عدد ۱ را بنویسید، واحد را روی Ether بگذارید و deposit را بزنید. حالا یک اتر آزمایشی در قلک است.
  7. از فهرست Account حساب دیگری را انتخاب کنید و withdraw را بزنید. تراکنش با پیام «Not the owner» برمی‌گردد. حالا با حساب اول دوباره امتحان کنید تا این بار اتر به حسابتان برگردد.

همین تمرین کوچک مهم‌ترین درس سالیدیتی را نشان می‌دهد: قرارداد فقط کاری را می‌کند که در کد نوشته‌اید. اگر خط require اول withdraw را پاک کنید، هر حسابی می‌تواند قلک را خالی کند و قرارداد هم هیچ اعتراضی نمی‌کند.

انتشار روی شبکه آزمایشی Sepolia

بلاک‌چین Remix VM فقط داخل مرورگر خودتان وجود دارد. قدم بعد شبکه آزمایشی (تست‌نت) است؛ شبکه‌ای که مثل اتریوم کار می‌کند، ولی اترش ارزشی ندارد. صفحه شبکه‌های ethereum.org شبکه Sepolia را شبکه آزمایشی پیش‌فرض برای ساختن اپلیکیشن می‌داند. شبکه آزمایشی دیگری هم به اسم Hoodi هست که برای آزمایش اعتبارسنج‌ها و ارتقاهای خود شبکه است.

  1. یک کیف پول مرورگری مثل متامسک نصب کنید؛ مراحلش را در آموزش نصب متامسک قدم‌به‌قدم گفته‌ایم. برای آزمایش یک حساب جدا بسازید؛ خود ethereum.org هم توصیه می‌کند حسابی را که در شبکه اصلی دارید در شبکه آزمایشی به کار نبرید و برعکس.
  2. شبکه Sepolia را در کیف پول انتخاب کنید و از یک فاست (faucet) اتر آزمایشی بگیرید. فاست سایتی است که آدرستان را می‌گیرد و رایگان برایتان اتر آزمایشی می‌فرستد؛ فهرست چند فاست در همان صفحه ethereum.org هست. اتر آزمایشی را هیچ‌وقت نخرید، چون قرار نیست ارزشی داشته باشد.
  3. در ریمیکس، از فهرست Environment گزینه Browser Extension را انتخاب کنید و متامسک را وصل کنید. به گفته مستندات ریمیکس، راه معمول انتشار روی شبکه آزمایشی یا اصلی با حساب خودتان همین است.
  4. دکمه Deploy را بزنید و تراکنش را در متامسک تأیید کنید. بعد آدرس قرارداد را در یک اکسپلورر Sepolia مثل sepolia.etherscan.io جست‌وجو کنید؛ تراکنش ساخت قرارداد و بعدها هر واریز را همان‌جا می‌بینید.

وقتی پروژه جدی شد: هاردهت یا فاندری؟

ریمیکس برای یاد گرفتن و آزمایش سریع عالی است، ولی پروژه واقعی چیزهایی می‌خواهد که در مرورگر سخت جور می‌شود: صدها تست خودکار، اسکریپت انتشار، گیت و کار تیمی. اینجاست که معمولاً سراغ یکی از این دو ابزار می‌روند.

هاردهت (Hardhat) محیط توسعه‌ای روی Node.js است که با آن قرارداد را می‌نویسند، تست و دیباگ می‌کنند و منتشر می‌کنند. طبق مستندات خودش، نسخه ۳ هاردهت به Node.js نسخه ۲۲٫۱۳ یا جدیدتر نیاز دارد و تست‌ها را می‌شود هم با تایپ‌اسکریپت نوشت و هم با خود سالیدیتی. اگر سایت یا اپ کنار قرارداد را هم با جاوااسکریپت می‌نویسید، با هاردهت لازم نیست زبان و ابزارتان را عوض کنید.

فاندری (Foundry) مجموعه‌ای از چهار ابزار خط فرمان است. ابزار forge قرارداد را می‌سازد، تست و دیباگ می‌کند، منتشر می‌کند و کدش را در اکسپلورر تأیید می‌کند. با cast قراردادها را صدا می‌زنید، تراکنش می‌فرستید و از زنجیره داده می‌خوانید. ابزار anvil یک گره محلی اتریوم راه می‌اندازد که می‌تواند وضعیت یک شبکه واقعی را هم کپی کند و با chisel می‌شود سالیدیتی را خط‌به‌خط اجرا کرد.

در فاندری تست‌ها را هم با خود سالیدیتی می‌نویسید. همین forge می‌تواند یک تست را با ورودی‌های تصادفی بارها اجرا کند؛ در نمونه‌ای که مستندات فاندری آورده، یک تست ۲۵۶ بار با عددهای مختلف اجرا شده است.


ریمیکس

هاردهت

فاندری

کجا کار می‌کند

در مرورگر، بدون نصب

روی کامپیوتر، با Node.js

روی کامپیوتر، در خط فرمان

تست‌ها

بیشتر دستی، با دکمه‌ها

تایپ‌اسکریپت یا سالیدیتی

سالیدیتی

مناسب چه کسی

تازه‌کار و آزمایش سریع

کسی که با جاوااسکریپت و تایپ‌اسکریپت راحت است

کسی که می‌خواهد همه‌چیز را در سالیدیتی نگه دارد

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

اشتباه‌های امنیتی رایج در سالیدیتی

به برآورد صفحه امنیت قراردادهای هوشمند در ethereum.org، پولی که به خاطر ایراد امنیتی قراردادها دزدیده شده یا از دست رفته، به‌راحتی از یک میلیارد دلار بیشتر است. در این ماجراها چند اشتباه بارها تکرار شده که هر کسی سالیدیتی می‌نویسد باید بشناسدشان.

حمله ورود مجدد (Reentrancy)

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

هک معروف The DAO در سال ۱۳۹۵ از همین نوع بود و حدود ۳٫۶ میلیون اتر از آن بیرون رفت؛ داستانش را در اتریوم چیست گفته‌ایم. حالا دو نسخه از یک تابع برداشت را ببینید که برخلاف قلک، در آن هر کاربر موجودی جدای خودش را دارد:

Solidity
// نسخه آسیب‌پذیر: اول پول را می‌فرستد، بعد موجودی را صفر می‌کند
function withdraw() external {
    uint256 amount = balances[msg.sender];
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "Transfer failed");
    balances[msg.sender] = 0;
}

// نسخه درست: اول موجودی را صفر می‌کند، بعد پول را می‌فرستد
function withdraw() external {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0;
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "Transfer failed");
}

تنها فرق این دو، جای یک خط است. مستندات سالیدیتی اسم این ترتیب درست را الگوی «بررسی، اثر، تعامل» (Checks-Effects-Interactions) گذاشته است: اول شرط‌ها را چک کنید، بعد وضعیت قرارداد را به‌روز کنید و فقط در آخر سراغ آدرس یا قرارداد دیگری بروید.

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

قلک ما این مشکل را ندارد، چون فقط صاحبش برداشت می‌کند و هر بار کل موجودی را می‌گیرد. ولی همین که چند نفر هر کدام موجودی جدا داشته باشند، ترتیب خط‌ها حیاتی می‌شود.

کنترل دسترسی

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

الگوی رایج این کار Ownable است: یک آدرس صاحب قرارداد می‌شود و تابع‌های حساس با یک modifier (شرطی که پیش از اجرای تابع چک می‌شود) به اسم onlyOwner قفل می‌شوند. کتابخانه OpenZeppelin نسخه آماده این الگو را دارد و برای پروژه‌های جدی‌تر کنترل نقش‌محور (AccessControl) را پیشنهاد می‌کند که هر کار را به نقش جدایی می‌سپارد.

صفحه امنیت ethereum.org نکته دیگری هم دارد: اگر همه اختیار دست یک آدرس باشد، کافی است کلید خصوصی همان یک حساب لو برود تا کل قرارداد از دست برود. برای همین پروژه‌های جدی این اختیار را به کیف پول چندامضایی می‌دهند؛ مثلاً حسابی که هر کارش تأیید ۳ نفر از ۵ نفر را می‌خواهد.

سه دام دیگر برای تازه‌کارها

  • استفاده از tx.origin برای شناختن صاحب قرارداد. مستندات سالیدیتی می‌گوید هرگز از tx.origin برای احراز هویت استفاده نکنید. منظور از tx.origin حسابی است که تراکنش را از اول امضا کرده و فرستاده، حتی اگر در این میان چند قرارداد دیگر هم صدا زده شده باشند. اگر صاحب قرارداد فریب بخورد و با یک قرارداد مخرب کار کند، آن قرارداد می‌تواند از طرف او برداشت کند. به جایش همیشه msg.sender را چک کنید.
  • سرریز عدد (Overflow). سرریز یعنی نتیجه یک محاسبه از بزرگ‌ترین یا کوچک‌ترین عددی که نوع متغیر جا دارد بیرون بزند. سالیدیتی در نسخه‌های فعلی خودش جلوی این اتفاق را می‌گیرد و تراکنش را برمی‌گرداند، ولی داخل بلوک unchecked این محافظت خاموش است. مستندات هشدار می‌دهد که حتی برگشتن تراکنش هم ممکن است قرارداد را در یک وضعیت گیر بیندازد؛ پس با require اندازه ورودی‌ها را محدود کنید.
  • خیال اینکه private یعنی مخفی. طبق مستندات سالیدیتی، هر چیزی که در قرارداد هست، حتی متغیری که private تعریف شده، برای همه دیدنی است. کلمه private فقط جلوی دسترسی قراردادهای دیگر را می‌گیرد، نه چشم آدم‌ها را. رمز، کلید یا جواب یک معما را هیچ‌وقت در قرارداد نگذارید.

کد منتشرشده وصله نمی‌شود

اگر بعد از انتشار باگی پیدا کنید، به گفته ethereum.org کد روی آن آدرس را دیگر نمی‌شود درست کرد. الگوهایی مثل پراکسی (proxy) راهی برای ارتقای قرارداد باز می‌کنند، ولی همین امکان اختیار تازه‌ای به مدیر قرارداد می‌دهد که خودش باید محافظت شود. ممیزی (audit) هم کمک بزرگی است، ولی به گفته همان منبع، همه باگ‌ها را پیدا نمی‌کند و جای کد تمیز و تست کافی را نمی‌گیرد.

چک‌لیست پیش از انتشار روی شبکه اصلی

پیش از انتشار روی شبکه اصلی، این فهرست را یکی‌یکی تیک بزنید:

  1. کامپایلر روی آخرین نسخه منتشرشده سالیدیتی تنظیم شده است.
  2. هر تابع حساس شرط دسترسی دارد و برایش تستی نوشته‌اید که آن را با یک حساب غریبه هم امتحان می‌کند.
  3. در هر تابعی که پول یا توکن می‌فرستد، وضعیت قرارداد پیش از ارسال به‌روز می‌شود.
  4. هیچ جا از tx.origin برای احراز هویت استفاده نکرده‌اید و هیچ داده محرمانه‌ای در قرارداد نیست.
  5. کد را با ابزارهای تحلیل خودکار مثل Slither، Mythril یا Aderyn بررسی کرده‌اید.
  6. قرارداد را روی Sepolia منتشر کرده‌اید و همه تابع‌ها را با چند حساب مختلف صدا زده‌اید.
  7. کلید مدیریت قرارداد در کیف پول چندامضایی یا دست‌کم کیف پول سخت‌افزاری است، نه در فایلی روی کامپیوتر.
  8. اگر قرار است پول دیگران در قرارداد بنشیند، ممیزی مستقل و برنامه پاداش باگ (bug bounty) را جدی گرفته‌اید.
  9. برنامه دارید که بعد از انتشار، کد منبع را در اکسپلورر تأیید (verify) کنید تا همه بتوانند آن را بخوانند.

مسیر یادگیری سالیدیتی

اگر از صفر شروع می‌کنید، این ترتیب کمتر سردرگمتان می‌کند:

  1. مفهوم‌ها را محکم کنید. بدانید قرارداد هوشمند چیست، EVM چطور کد را اجرا می‌کند و چرا هر کاری روی زنجیره گس می‌خواهد.
  2. یک زبان معمولی بلد باشید. به گفته ethereum.org، آشنایی با جاوااسکریپت یا پایتون فهم زبان‌های قرارداد هوشمند را راحت‌تر می‌کند. اگر هیچ زبانی بلد نیستید، اول پایه برنامه‌نویسی را با یکی از این دو یاد بگیرید.
  3. مستندات و مثال بخوانید. از مستندات رسمی سالیدیتی شروع کنید و کنارش سایت Solidity by Example را ببینید که ethereum.org هم معرفی‌اش کرده و هر مفهوم را با یک قرارداد کوتاه نشان می‌دهد.
  4. در ریمیکس چند قرارداد کوچک بنویسید. بعد از قلک، یک رأی‌گیری ساده یا یک قرارداد امانی بنویسید و هر کدام را با چند حساب امتحان کنید.
  5. کد واقعی بخوانید. قرارداد ERC-20 در OpenZeppelin را باز کنید یا کد تأییدشده یک توکن معروف را در اکسپلورر بخوانید؛ در مطلب قرارداد هوشمند قرارداد تتر را با هم باز کرده‌ایم.
  6. هاردهت یا فاندری را انتخاب کنید و تست بنویسید. برای هر تابع، هم حالتی را تست کنید که باید درست کار کند و هم حالت‌هایی را که باید رد شوند.
  7. امنیت را جداگانه یاد بگیرید. بخش ملاحظات امنیتی مستندات سالیدیتی و صفحه امنیت ethereum.org را بخوانید و گزارش هک‌های گذشته را دنبال کنید.
  8. روی شبکه آزمایشی منتشر کنید و فقط وقتی چک‌لیست بالا کامل شد، سراغ شبکه اصلی بروید.

برای انتشار روی شبکه اصلی، روی همان شبکه‌ای که انتخاب کرده‌اید ETH واقعی برای کارمزد لازم دارید. می‌توانید اتریوم (ETH) را در سواپ‌ولت بخرید و به کیف پول خودتان برداشت کنید. موقع برداشت دقت کنید شبکه‌ای که برای ETH نشان داده می‌شود همان شبکه‌ای باشد که قرارداد را رویش منتشر می‌کنید؛ ETH روی آربیتروم و ETH روی شبکه اصلی اتریوم دو موجودی جدا هستند.

کارمزد برداشت را در صفحه کارمزدها ببینید. برای واریز تومان و خرید با آن احراز هویت سطح متوسط لازم است، ولی واریز، تبدیل و برداشت رمزارز بدون احراز هویت هم انجام می‌شود.

سؤال‌های رایج درباره سالیدیتی

یاد گرفتن سالیدیتی سخت است؟

نوشتن خود کد برای کسی که جاوااسکریپت یا ++C بلد است زود دستش می‌آید. سختی کار جای دیگری است: باید مثل مهاجم فکر کنید و پیش از او راهی را پیدا کنید که با آن می‌شود قرارداد را دور زد.

سالیدیتی بهتر است یا وایپر (Vyper)؟

کد وایپر شبیه پایتون است و طبق ethereum.org، این زبان عمداً امکانات کمتری دارد (مثلاً ارث‌بری ندارد) تا کدش امن‌تر و بررسی‌اش راحت‌تر باشد. سالیدیتی امکانات بیشتری دارد. انتخاب بینشان بیشتر به سلیقه و پروژه برمی‌گردد و ریمیکس هر دو را پشتیبانی می‌کند؛ پس می‌توانید هر دو را امتحان کنید.

با سالیدیتی می‌شود برای سولانا یا TON هم قرارداد نوشت؟

نه. سالیدیتی برای EVM ساخته شده و روی اتریوم، شبکه‌های سازگار با EVM و ترون کار می‌کند. قرارداد سولانا را معمولاً با راست (Rust) می‌نویسند و قرارداد TON را با Tolk یا FunC.

انتشار یک قرارداد چقدر خرج دارد؟

روی شبکه آزمایشی خرج واقعی ندارد، چون اتر آزمایشی رایگان است. روی شبکه اصلی، انتشار خودش یک تراکنش است و کارمزدش به حجم کد و شلوغی شبکه بستگی دارد؛ این کارمزد روی شبکه‌های لایه دوم معمولاً خیلی کمتر از خود اتریوم است.

برای ساختن توکن هم باید سالیدیتی بلد باشم؟

هر توکن ERC-20 خودش یک قرارداد هوشمند است که معمولاً با سالیدیتی نوشته می‌شود. OpenZeppelin پیاده‌سازی آماده‌اش را دارد و لازم نیست همه‌چیز را از صفر بنویسید. ولی برای اینکه بفهمید دقیقاً چه چیزی منتشر می‌کنید و چه اختیاری دست چه کسی می‌ماند، پایه سالیدیتی لازم است.

می‌شود نوشتن قرارداد را به هوش مصنوعی سپرد؟

هوش مصنوعی برای نوشتن پیش‌نویس و توضیح دادن کد کمک خوبی است، ولی مسئولیت کدی که منتشر می‌کنید با خودتان است. کد تولیدشده را مثل کد یک غریبه بخوانید، برایش تست بنویسید و از همان چک‌لیست بالا ردش کنید.