أساسيات لغة Solidity: أنواع البيانات والمتغيرات (State Variables)


ماذا سنتعلم اليوم؟ سنتعمق في أساسيات لغة Solidity، مركزين على أنواع البيانات البدائية وكيفية تعريف المتغيرات الحالية (State Variables) لتخزين البيانات على البلوكتشين.

الخطوة 1: المتغيرات الحالية (State Variables) وأنواع الأعداد الصحيحة (Integers)

المتغيرات الحالية هي المتغيرات التي يتم تخزين قيمها بشكل دائم على البلوكتشين كجزء من حالة العقد. سنتعلم كيف نعلن عنها باستخدام أنواع الأعداد الصحيحة.

ملاحظة تقنية: في Solidity، الأعداد الصحيحة بدون إشارة (unsigned integers) مثل uint هي الأكثر شيوعًا لتجنب مشكلات الإشارة السلبية في العمليات المالية. uint256 هو النوع الافتراضي إذا لم يتم تحديد حجم البت.

لنبدأ بعقد بسيط يحدد متغيرًا حاليًا من نوع uint:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BasicDataTypes {
    // تعريف متغير حالي من نوع uint لتخزين رقم، وهو مرئي للجميع على البلوكتشين.
    uint public myNumber = 123;

    // تعريف متغير حالي من نوع uint8، يقبل أرقامًا من 0 إلى 255.
    uint8 public smallNumber = 50;
}

شرح الكود:

  • uint public myNumber = 123;: نعلن عن متغير حالي باسم myNumber من نوع uint (وهو اختصار لـ uint256)، ونجعله public ليتم إنشاء دالة Getter تلقائيًا للوصول إليه. قيمة البدء هي 123.
  • uint8 public smallNumber = 50;: نعلن عن متغير حالي آخر من نوع uint8، مما يعني أنه يمكنه تخزين قيم من 0 إلى 255 فقط. هذا يوفر الغاز عند التخزين.

الخطوة 2: أنواع البيانات المنطقية (Booleans) والعناوين (Addresses)

تُستخدم أنواع البيانات المنطقية (bool) لتخزين القيم true أو false، بينما تُستخدم العناوين (address) لتخزين عناوين حسابات المستخدمين أو العقود الأخرى.

ملاحظة تقنية: نوع address في Solidity هو 20 بايت ويمثل عنوان Ethereum. يمكن أن يكون له وظائف payable إذا كان بإمكانه استلام Ether.

لنضف هذه الأنواع إلى عقدنا:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BasicDataTypes {
    uint public myNumber = 123;
    uint8 public smallNumber = 50;

    // تعريف متغير حالي من نوع bool لتخزين قيمة منطقية (صحيح/خطأ).
    bool public isActive = true;

    // تعريف متغير حالي من نوع address لتخزين عنوان حساب Ethereum.
    address public ownerAddress;

    constructor() {
        // عند نشر العقد، يتم تعيين عنوان من قام بالنشر كـ ownerAddress.
        ownerAddress = msg.sender;
    }
}

شرح الكود:

  • bool public isActive = true;: متغير isActive من نوع bool بقيمة أولية true.
  • address public ownerAddress;: متغير ownerAddress من نوع address.
  • constructor() { ownerAddress = msg.sender; }: الدالة البانية (constructor) التي يتم تنفيذها مرة واحدة فقط عند نشر العقد. هنا، نقوم بتعيين ownerAddress ليكون عنوان الحساب الذي قام بنشر العقد (msg.sender).

الخطوة 3: السلاسل النصية (Strings)، التعدادات (Enums)، والثوابت (Constants/Immutables)

تُستخدم السلاسل النصية (string) لتخزين النصوص، بينما توفر التعدادات (enum) طريقة لإنشاء أنواع مخصصة بقيم محددة. الثوابت (constant و immutable) هي متغيرات قيمها لا تتغير بعد التعيين.

ملاحظة تقنية: string في Solidity هو نوع ديناميكي، مما يعني أنه يمكن أن يستهلك كمية كبيرة من الغاز للتخزين. constant يتم تعيين قيمته وقت الترجمة، بينما immutable يتم تعيين قيمته وقت النشر (في الدالة البانية) ولا يمكن تغييرها بعدها.

لنوسع عقدنا ليشمل هذه الأنواع:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BasicDataTypes {
    uint public myNumber = 123;
    uint8 public smallNumber = 50;
    bool public isActive = true;
    address public ownerAddress;

    // تعريف متغير حالي من نوع string لتخزين نص.
    string public contractMessage = "مرحباً بكم في عقد Solidity!";

    // تعريف نوع تعداد (enum) لتحديد حالات ممكنة.
    enum Status { Pending, Active, Suspended, Deleted }
    // تعريف متغير حالي من نوع التعداد Status.
    Status public currentStatus = Status.Pending;

    // تعريف ثابت (constant) قيمته معروفة وقت الترجمة.
    uint public constant MAX_LIMIT = 1000;

    // تعريف متغير غير قابل للتغيير (immutable) قيمته تحدد وقت النشر.
    string public immutable CONTRACT_NAME;

    constructor(string memory _contractName) {
        ownerAddress = msg.sender;
        CONTRACT_NAME = _contractName; // يتم تعيين القيمة هنا ولا يمكن تغييرها لاحقاً.
    }

    // دالة لتغيير حالة العقد.
    function setStatus(Status _newStatus) public {
        currentStatus = _newStatus;
    }
}

شرح الكود:

  • string public contractMessage = "مرحباً بكم في عقد Solidity!";: متغير contractMessage لتخزين سلسلة نصية.
  • enum Status { Pending, Active, Suspended, Deleted }: تعريف تعداد Status بقيم محددة.
  • Status public currentStatus = Status.Pending;: متغير من نوع التعداد Status بقيمة أولية Pending.
  • uint public constant MAX_LIMIT = 1000;: ثابت MAX_LIMIT بقيمة 1000. لا يمكن تغيير قيمته.
  • string public immutable CONTRACT_NAME;: متغير غير قابل للتغيير CONTRACT_NAME.
  • constructor(string memory _contractName) { CONTRACT_NAME = _contractName; }: يتم تمرير قيمة CONTRACT_NAME للدالة البانية وتعيينها مرة واحدة.
  • function setStatus(Status _newStatus) public { currentStatus = _newStatus; }: دالة لتغيير قيمة currentStatus.

الكود النهائي الكامل

إليك الكود الكامل للعقد الذكي الذي يجمع كل ما تعلمناه، بالإضافة إلى سكريبت Hardhat/Ethers.js لنشره والتفاعل معه.

عقد Solidity (BasicDataTypes.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BasicDataTypes {
    // 1. أنواع الأعداد الصحيحة (Integers)
    uint public myNumber = 123;               // عدد صحيح بدون إشارة، افتراضياً uint256
    uint8 public smallNumber = 50;            // عدد صحيح بدون إشارة بحجم 8 بت (0-255)

    // 2. الأنواع المنطقية (Booleans) والعناوين (Addresses)
    bool public isActive = true;              // قيمة منطقية (صحيح/خطأ)
    address public ownerAddress;              // عنوان حساب Ethereum

    // 3. السلاسل النصية (Strings)
    string public contractMessage = "مرحباً بكم في عقد Solidity!"; // سلسلة نصية

    // 4. التعدادات (Enums)
    enum Status { Pending, Active, Suspended, Deleted } // تعريف تعداد للحالات
    Status public currentStatus = Status.Pending;       // متغير من نوع التعداد

    // 5. الثوابت (Constants) والمتغيرات غير القابلة للتغيير (Immutables)
    uint public constant MAX_LIMIT = 1000;              // ثابت، قيمته معروفة وقت الترجمة
    string public immutable CONTRACT_NAME;              // غير قابل للتغيير، قيمته تحدد وقت النشر

    // الدالة البانية: تُنفذ مرة واحدة عند نشر العقد
    constructor(string memory _contractName) {
        ownerAddress = msg.sender; // تعيين من قام بالنشر كمالك
        CONTRACT_NAME = _contractName; // تعيين اسم العقد من المدخلات
    }

    // دالة عامة لتغيير حالة العقد
    function setStatus(Status _newStatus) public {
        // التأكد من أن المتصل هو المالك (اختياري، لكن ممارسة جيدة للتحكم)
        require(msg.sender == ownerAddress, "فقط المالك يمكنه تغيير الحالة.");
        currentStatus = _newStatus;
    }

    // دالة عامة لتحديث الرقم
    function setMyNumber(uint _newNumber) public {
        require(msg.sender == ownerAddress, "فقط المالك يمكنه تحديث الرقم.");
        myNumber = _newNumber;
    }
}

سكريبت النشر والتفاعل (deploy.js - Hardhat/Ethers.js)

const { ethers } = require("hardhat");

async function main() {
    const [deployer] = await ethers.getSigners();

    console.log("نشر العقد باستخدام الحساب:", deployer.address);

    // نشر العقد BasicDataTypes
    const BasicDataTypes = await ethers.getContractFactory("BasicDataTypes");
    const basicDataTypes = await BasicDataTypes.deploy("My Awesome Contract");

    await basicDataTypes.deployed();

    console.log("تم نشر العقد على العنوان:", basicDataTypes.address);

    // التفاعل مع المتغيرات الحالية
    console.log("\n--- قراءة المتغيرات الحالية ---");
    console.log("My Number:", (await basicDataTypes.myNumber()).toString());
    console.log("Small Number:", (await basicDataTypes.smallNumber()).toString());
    console.log("Is Active:", await basicDataTypes.isActive());
    console.log("Owner Address:", await basicDataTypes.ownerAddress());
    console.log("Contract Message:", await basicDataTypes.contractMessage());
    console.log("Current Status:", (await basicDataTypes.currentStatus()).toString()); // Enum returns its index
    console.log("MAX_LIMIT (Constant):",(await basicDataTypes.MAX_LIMIT()).toString());
    console.log("CONTRACT_NAME (Immutable):"), await basicDataTypes.CONTRACT_NAME());

    // تغيير حالة العقد
    console.log("\n--- تغيير حالة العقد ---");
    // Status.Active هو المؤشر 1 في التعداد
    const newStatusTx = await basicDataTypes.setStatus(1);
    await newStatusTx.wait();
    console.log("تم تغيير الحالة إلى:", (await basicDataTypes.currentStatus()).toString());

    // تحديث الرقم
    console.log("\n--- تحديث الرقم ---");
    const updateNumberTx = await basicDataTypes.setMyNumber(456);
    await updateNumberTx.wait();
    console.log("تم تحديث myNumber إلى:", (await basicDataTypes.myNumber()).toString());

    // محاولة تغيير الحالة من حساب آخر (يجب أن يفشل)
    const [_, otherAccount] = await ethers.getSigners();
    try {
        console.log("\n--- محاولة تغيير الحالة من حساب آخر ---");
        const failedTx = await basicDataTypes.connect(otherAccount).setStatus(2); // Status.Suspended
        await failedTx.wait();
    } catch (error) {
        console.log("فشل متوقع: ", error.message.includes("فقط المالك يمكنه تغيير الحالة.") ? "تم رفض الوصول بنجاح." : error.message);
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });

النتيجة المتوقعة

بعد نشر العقد وتشغيل سكريبت التفاعل (deploy.js)، ستظهر لك في الطرفية (Terminal) مخرجات تشبه ما يلي:

نشر العقد باستخدام الحساب: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
تم نشر العقد على العنوان: 0x5FbDB2315678afecb367f032d93F642f64180aa3

--- قراءة المتغيرات الحالية ---
My Number: 123
Small Number: 50
Is Active: true
Owner Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Contract Message: مرحباً بكم في عقد Solidity!
Current Status: 0
MAX_LIMIT (Constant): 1000
CONTRACT_NAME (Immutable): My Awesome Contract

--- تغيير حالة العقد ---
تم تغيير الحالة إلى: 1

--- تحديث الرقم ---
تم تحديث myNumber إلى: 456

--- محاولة تغيير الحالة من حساب آخر ---
فشل متوقع: تم رفض الوصول بنجاح.

هذه المخرجات توضح كيفية نشر العقد، قراءة قيمه الأولية، تحديث بعض المتغيرات الحالية، وكيفية عمل التحقق من المالك (require) لمنع الوصول غير المصرح به.