آموزش سالیدیتی مقدماتی - آشنایی با سالیدیتی

سالیدیتی چیست؟

سالیدیتی یا Solidity یک زبان برنامه نویسی است که برای توسعه ی قراردادهای هوشمند در بلاکچین ها، به ویژه در بلاکچین اتریوم، استفاده می شود. این زبان برنامه نویسی به طور اصلی برای توسعه قراردادهای هوشمند که قابلیت اجرای کد بر روی بلاکچین را دارند، طراحی شده است. قراردادهای هوشمند توسط Solidity قابل تعریف و اجرا هستند و معمولاً برای انجام توابعی مانند معاملات مالی، اجرای قراردادهای بین دو طرف بدون نیاز به واسطه، یا تولید اعتبار از طریق اجرای قراردادها بکار می روند.

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

جلسات دوره

۱
سالیدیتی چیست؟

سالیدیتی یا Solidity یک زبان برنامه نویسی است که برای توسعه ی قراردادهای هوشمند در بلاکچین ها، به ویژه در بلاکچین اتریوم، استفاده می شود. این زبان برنامه نویسی به طور اصلی برای توسعه قراردادهای هوشمند که قابلیت اجرای کد بر روی بلاکچین را دارند، طراحی شده است. قراردادهای هوشمند توسط Solidity قابل تعریف و اجرا هستند و معمولاً برای انجام توابعی مانند معاملات مالی، اجرای قراردادهای بین دو طرف بدون نیاز به واسطه، یا تولید اعتبار از طریق اجرای قراردادها بکار می روند.

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

۲
آشنایی با Remix

ریمیکس یا Remix یکی از محیط های توسعه ی متن باز و رایگان برای توسعه ی قراردادهای هوشمند بر روی بلاکچین اتریوم است. این محیط توسعه، ابزاری قدرتمند برای توسعه، آزمایش، و استقرار قراردادهای هوشمند با استفاده از زبان برنامه نویسی Solidity است. ریمیکس امکانات بسیاری برای توسعه دهندگان فراهم می کند که اجازه می دهد قراردادهای هوشمند را به راحتی بسازند، آزمایش کنند و در نهایت آن ها را بر روی بلاکچین اتریوم استقرار دهند.

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

۳
متغییر ها در سالیدیتی

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

انواع متغیرها در سالیدیتی

سالیدیتی از چندین نوع متغیر پشتیبانی می کند که هر کدام برای مقاصد خاصی استفاده می شوند. انواع متغیرها در سالیدیتی شامل موارد زیر است:

  • متغیرهای حالت (State Variables): این متغیرها در سطح قرارداد تعریف می شوند و داده ها را در بلاکچین ذخیره می کنند. این متغیرها پایدار هستند و پس از پایان اجرای تابع نیز مقدار خود را حفظ می کنند.

  • متغیرهای محلی (Local Variables): این متغیرها در داخل توابع تعریف می شوند و تنها در طول اجرای همان تابع قابل دسترسی هستند. پس از پایان تابع، این متغیرها از بین می روند.

  • متغیرهای سراسری (Global Variables): این متغیرها اطلاعاتی درباره بلاکچین و تراکنش ها را فراهم می کنند، مانند msg.sender و block.timestamp.

تعریف و استفاده از متغیرها

برای تعریف متغیرها در سالیدیتی، ابتدا نوع داده را مشخص کرده و سپس نام متغیر را تعیین می کنید. مثال:

uint public myNumber;
address private owner;
string public name;

 

در این مثال، uint نوع داده صحیح بدون علامت، address نوع داده آدرس اتریوم، و string نوع داده رشته ای را نشان می دهد. کلمات کلیدی public و private سطح دسترسی به متغیر را تعیین می کنند.

سطوح دسترسی متغیرها

  • Public: متغیرهایی که با کلمه کلیدی public تعریف می شوند، به صورت خودکار یک تابع getter برای آن ها ایجاد می شود و از خارج قرارداد قابل دسترسی هستند.

  • Private: این متغیرها تنها در داخل قرارداد قابل دسترسی هستند و از خارج قابل مشاهده نیستند.

  • Internal: این متغیرها در داخل قرارداد و قراردادهای مشتق شده قابل دسترسی هستند.

  • External: این سطح دسترسی برای متغیرها قابل استفاده نیست و تنها برای توابع به کار می رود.

 

نکات مهم در استفاده از متغیرها در سالیدیتی

  • بهینه سازی گس (Gas): استفاده بهینه از متغیرها می تواند به کاهش هزینه های گس منجر شود. برای مثال، متغیرهای محلی هزینه کمتری نسبت به متغیرهای حالت دارند.

  • امنیت: توجه به سطح دسترسی متغیرها برای جلوگیری از دسترسی غیرمجاز بسیار مهم است. متغیرهای حساس را با کلمه کلیدی private یا internal تعریف کنید.

  • نوع داده مناسب: انتخاب نوع داده مناسب برای متغیرها می تواند به بهینه سازی قرارداد کمک کند. برای مثال، استفاده از uint8 به جای uint256 در صورت امکان.

 

جمع بندی

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

۴
فانکشن ها در سالیدیتی , توابع در سالیدیتی

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

تعریف و نحوه استفاده از توابع

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

function functionName(parameterList) visibility modifier returns (returnType) {
    // Function logic
}

مثال:

function setName(string memory _name) public {
    name = _name;
}

در این مثال، فانکشن setName یک رشته را به عنوان ورودی دریافت کرده و متغیر حالت name را تنظیم می کند.

سطوح دسترسی توابع در سالیدیتی

سطوح دسترسی تعیین می کنند که چگونه و از کجا می توان به فانکشن ها دسترسی داشت. سطوح دسترسی در سالیدیتی عبارت اند از:

  • public: فانکشن هایی که از هر جایی، چه داخل و چه خارج از قرارداد، قابل دسترسی هستند.
  • private: فانکشن هایی که تنها در داخل قرارداد تعریف شده قابل دسترسی هستند.
  • internal: فانکشن هایی که در داخل قرارداد و قراردادهای مشتق شده قابل دسترسی هستند.
  • external: فانکشن هایی که فقط از خارج قرارداد قابل دسترسی هستند و نمی توانند از داخل قرارداد فراخوانی شوند مگر با استفاده از this.

توابع View و Pure

  • View Functions: فانکشن هایی که فقط خواندن داده ها را انجام می دهند و هیچ تغییری در حالت قرارداد ایجاد نمی کنند. این فانکشن ها هزینه گس ندارند.
function getName() public view returns (string memory) {
    return name;
}
  • Pure Functions: فانکشن هایی که نه حالت قرارداد را می خوانند و نه تغییر می دهند. این فانکشن ها معمولاً برای انجام محاسبات استفاده می شوند.
function add(uint a, uint b) public pure returns (uint) {
    return a + b;
}

توابع Payable

فانکشن های payable به قرارداد اجازه می دهند تا اتر دریافت کند. این فانکشن ها برای تراکنش هایی که شامل انتقال اتر (ether) هستند، ضروری اند.

function deposit() public payable {
    balance += msg.value;
}

توابع Fallback و Receive

  • Fallback Function: فانکشنی که زمانی فراخوانی می شود که هیچ فانکشن دیگری با امضای مشخص شده پیدا نشود.
fallback() external {
    // Logic
}
  • Receive Function: فانکشنی که برای دریافت اتر بدون داده فراخوانی می شود.
receive() external payable {
    // Logic
}

اصلاح کننده ها در سالیدیتی (Modifiers)

اصلاح کننده ها برای تغییر رفتار فانکشن ها استفاده می شوند، بدون اینکه نیاز به تغییر کد تابع باشد. این امکان را می دهد تا کنترل دسترسی، اعتبارسنجی ورودی و دیگر عملیات قبل یا بعد از اجرای فانکشن انجام شود.

modifier onlyOwner() {
    require(msg.sender == owner, "Not the contract owner");
    _;
}

function setName(string memory _name) public onlyOwner {
    name = _name;
}

بهترین شیوه ها در استفاده از فانکشن ها

  • بهینه سازی گس: کدهای خود را بهینه کنید تا هزینه گس کاهش یابد.
  • امنیت: از اصلاح کننده ها و سطوح دسترسی مناسب برای حفاظت از فانکشن ها استفاده کنید.
  • خوانایی: کدهای خود را به صورت تمیز و مستند بنویسید تا برای دیگران قابل فهم باشد.

 

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

 

۵
واریز و برداشت از اسمارت کانترکت , payable

یکی از قابلیت های مهم در توسعه قراردادهای هوشمند با سالیدیتی، امکان واریز و برداشت اتر (ETH) از اسمارت کانترکت ها است. این قابلیت با استفاده از کلمه کلیدی payable در توابع سالیدیتی امکان پذیر می شود. درک صحیح از نحوه استفاده از توابع payable و مدیریت وجوه در اسمارت کانترکت ها برای ایجاد برنامه های کاربردی غیرمتمرکز (DApps) امن و کارآمد ضروری است. در سالیدیتی کورسز، ما به شما آموزش می دهیم که چگونه توابع واریز و برداشت را در قراردادهای هوشمند خود پیاده سازی کنید و بهترین شیوه های مدیریت وجوه را بیاموزید.

توابع payable در سالیدیتی

کلمه کلیدی payable به توابع در سالیدیتی اجازه می دهد تا اتر دریافت کنند. بدون این کلمه کلیدی، تلاش برای ارسال اتر به یک تابع منجر به شکست تراکنش می شود. مثال زیر یک تابع payable ساده را نشان می دهد:

function deposit() public payable {
    // Logic for deposit
}

در این مثال، تابع deposit با استفاده از کلمه کلیدی payable تعریف شده و می تواند اتر دریافت کند.

واریز اتر به اسمارت کانترکت

برای واریز اتر به یک اسمارت کانترکت، باید یک تابع payable تعریف کنید. در داخل این تابع می توانید مقدار واریزی را با استفاده از msg.value به دست آورید:

function deposit() public payable {
    uint amount = msg.value;
    // ذخیره مقدار واریزی برای کاربر
    balances[msg.sender] += amount;
}

در این مثال، مقدار واریزی به موجودی کاربر اضافه می شود.

برداشت اتر از اسمارت کانترکت

برای برداشت اتر از اسمارت کانترکت، باید تابعی تعریف کنید که اتر را به آدرس کاربر ارسال کند. برای ارسال اتر می توانید از تابع transfer یا call استفاده کنید:

function withdraw(uint _amount) public {
    require(balances[msg.sender] >= _amount, "موجودی کافی نیست");
    balances[msg.sender] -= _amount;
    payable(msg.sender).transfer(_amount);
}

در این مثال، ابتدا بررسی می شود که کاربر موجودی کافی دارد، سپس مبلغ برداشت از موجودی کسر شده و به آدرس کاربر منتقل می شود.

نکات امنیتی در واریز و برداشت

  • پیشگیری از حملات بازگشتی (Reentrancy): هنگام ارسال اتر، از الگوی "چک-اثرات-تعاملات" استفاده کنید. ابتدا شرایط را بررسی کنید، سپس حالت را به روز کنید، و در نهایت انتقال اتر را انجام دهید.

  • استفاده از transfer به جای send یا call: تابع transfer در صورت شکست تراکنش، خطا تولید می کند که امنیت بیشتری را فراهم می کند.

  • محدود کردن دسترسی: مطمئن شوید که فقط کاربران مجاز می توانند واریز و برداشت انجام دهند.

بهترین شیوه ها

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

  • آزمون و تست کامل: قراردادهای خود را به طور کامل تست کنید تا از امنیت و عملکرد صحیح آن ها اطمینان حاصل کنید.

جمع بندی

واریز و برداشت اتر از اسمارت کانترکت ها بخش حیاتی از توسعه DApps و قراردادهای هوشمند در سالیدیتی است. با درک عمیق از توابع payable و بهترین شیوه های امنیتی، می توانید قراردادهایی ایجاد کنید که امن، قابل اعتماد و کارآمد باشند. در سالیدیتی کورسز، ما به شما تمامی جنبه های واریز و برداشت در اسمارت کانترکت ها را آموزش می دهیم تا بتوانید مهارت های خود را به سطح بالاتری ارتقا دهید.

۶
واحد ها در سالیدیتی , یونیت ها در سالیدیتی

در زبان برنامه نویسی سالیدیتی Solidity، مفهوم واحدها بسیار مهم است. واحدها به معنای واحدهای داده ای که برای نمایش و محاسبات اعداد استفاده می شوند می باشند. در Solidity، دو نوع اصلی واحد وجود دارد: اعداد صحیح (integer) و اعداد اعشاری (floating point).

 

اعداد صحیح در سالیدیتی به صورت یک عدد بدون اعشار تعریف می شوند و از نوع های uint و int هستند. uint برای اعداد صحیح مثبت و int برای اعداد صحیح مثبت و منفی استفاده می شود. همچنین، اعداد اعشاری در Solidity با استفاده از نوع fixed و ufixed تعریف می شوند که این واحدها از عدد بدون علامت 16 بیتی مثبت شروع و به ترتیب 16 بیت دیگر به عنوان اعشار را نمایش می دهند.

 

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

۷
نحوه محاسبه gas در اسمارت کانترکت

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

محاسبه gas در اسمارت کانترکت ها به توجه به عملیات انجام شده در هر تابع و تراکنش متفاوت است. معمولاً هر تابع در قرارداد هوشمند میزان مشخصی از gas مصرف می کند که بر اساس پیچیدگی و محتوای کد آن تابع تعیین می شود. از آنجا که هر تابع یا عملیات در بلاکچین اتریوم به اندازه gas مصرفی پرداخت می کند، محاسبه دقیق این مقدار بسیار حیاتی است تا تراکنش ها به درستی انجام شوند و امکان اجرای بی پایان یا حملاتی مانند حملات DOS (حمله به سرویس) به سیستم را به حداقل برسانیم. از این رو، برنامه نویسان باید دقت کنند که عملیات های مصرف gas زیادی را در توابع خود اجرا نکنند و برنامه هایشان را بهینه کنند تا مصرف gas به حداقل برسد و هزینه اجرای تراکنش ها کمتر شود.

۸
آموزش Modifier ها در سالیدیتی

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

تعریف و نحوه استفاده از Modifier ها

یک Modifier به شما این امکان را می دهد که شرایط یا رفتار خاصی را قبل و بعد از اجرای یک تابع تعریف کنید. در داخل یک Modifier، کلمه کلیدی _ نشان دهنده نقطه ای است که در آن، کد اصلی تابع اجرا خواهد شد. ساختار کلی یک Modifier به صورت زیر است:

modifier modifierName() {
    // logic before
    _;
    // logic after
}

مثال از یک Modifier ساده

به عنوان مثال، فرض کنید که می خواهید اطمینان حاصل کنید که فقط صاحب قرارداد بتواند یک تابع خاص را اجرا کند. برای این کار می توانید یک Modifier به نام onlyOwner ایجاد کنید:

modifier onlyOwner() {
    require(msg.sender == owner, "Only the contract owner can call this function");
    _;
}

function setName(string memory _name) public onlyOwner {
    name = _name;
}

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

استفاده از پارامترها در Modifier ها

Modifier ها می توانند مانند توابع پارامتر دریافت کنند. این قابلیت به شما امکان می دهد که از Modifier ها به شکل پویا و منعطف تری استفاده کنید. به عنوان مثال، می توانید یک Modifier برای بررسی حداقل مقدار واریزی تعریف کنید:

modifier minAmount(uint _amount) {
    require(msg.value >= _amount, "Insufficient Ether sent");
    _;
}

function deposit() public payable minAmount(1 ether) {
    // logic for deposit
}

ترکیب Modifier ها

در سالیدیتی، می توانید چندین Modifier را به یک تابع اعمال کنید. این به شما اجازه می دهد که چندین شرط مختلف را قبل از اجرای تابع بررسی کنید. برای مثال:

modifier onlyOwner() {
    require(msg.sender == owner, "Only the owner can call this function");
    _;
}

modifier minAmount(uint _amount) {
    require(msg.value >= _amount, "Insufficient Ether sent");
    _;
}

function deposit() public payable onlyOwner minAmount(1 ether) {
    // logic for deposit
}

در این مثال، تابع deposit تنها توسط مالک قرارداد و با حداقل واریز ۱ اتر قابل فراخوانی است.

کاربرد های رایج Modifier ها

  • کنترل دسترسی: Modifier ها معمولاً برای محدود کردن دسترسی به توابع خاص به کاربران مجاز (مانند مالک قرارداد) استفاده می شوند.

  • بررسی شرایط ورودی: با استفاده از Modifier ها می توانید شرایطی مانند حداقل مبلغ واریزی، مقدار مناسب داده های ورودی و غیره را بررسی کنید.

  • الگوهای امنیتی: Modifier ها می توانند برای پیاده سازی الگوهای امنیتی مانند حفاظت در برابر حملات بازگشتی (Reentrancy) استفاده شوند.

بهترین شیوه ها در استفاده از Modifier ها

  • استفاده از نام های واضح: Modifier ها را با نام های مشخص و قابل فهم تعریف کنید تا کد شما خواناتر باشد.

  • اجتناب از استفاده بیش از حد: استفاده بیش از حد از Modifier ها می تواند کد شما را پیچیده و کمتر خوانا کند. از آن ها با دقت و در مواقع ضروری استفاده کنید.

  • مدیریت خطاها: از دستور require برای کنترل شرایط و ارائه پیام های خطا دقیق استفاده کنید.

جمع بندی

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

دیدگاه‌ها

خریدار دوره
Hossein
۱۹ دی ۱۴۰۲

طرز بیان خیلی خوب بود ، من کاملا متوجه شدم.من منتظر بقیه ویدیو ها هستم.🙏

خریدار دوره
زهرا
۱۹ دی ۱۴۰۲

چقد خوب که اموزش شما رو پیدا کردم پیدا کردن اموزش برای این مطالب خیلی کمه

خریدار دوره
حامد بابایی
۰۸ فروردین ۱۴۰۳

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

ادمین
ادمین
۱۵ خرداد ۱۴۰۳

دوره به تازگی رایگان شده و میتونید هر قسمت رو با کلیک روی جلسات سمت راست دانلود کنید یا به صورت انلاین مشاهده کنید.