مرحباً أيها المطورون!
ماذا سنتعلم اليوم؟ سنتعمق في فهم الدوال view و pure في Solidity، وكيف يمكنهما تقليل تكاليف رسوم الغاز بشكل كبير عند التفاعل مع العقود الذكية.
الخطوة الأولى: الدوال view - قراءة حالة العقد
الدوال view هي دوال لا تقوم بتعديل حالة العقد (State Variables) بأي شكل من الأشكال. بدلاً من ذلك، هي فقط تقرأ البيانات الموجودة على البلوكتشين. هذا يعني أنها لا تتطلب رسوم غاز عند استدعائها من خارج العقد (أي من واجهة مستخدم أو سكريبت)، لأنها لا تُنشئ معاملة على الشبكة.
ملاحظة تقنية: عند استدعاء دالة
viewمن خارج العقد، يتم تنفيذها محلياً على العقدة التي تتصل بها، ولا يتم بثها كمعاملة على الشبكة. هذا يوفر رسوم الغاز بشكل كامل.
مثال على دالة view:
لنبدأ بعقد بسيط يحتوي على متغير حالة ودالة view لقراءته:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasSaver {
uint public myNumber; // متغير حالة عام يمكن قراءته
constructor(uint _initialNumber) {
myNumber = _initialNumber; // تهيئة المتغير عند نشر العقد
}
// دالة view تقرأ قيمة myNumber
// لا تعدل حالة العقد ولا تكلف غاز عند الاستدعاء الخارجي
function getMyNumber() public view returns (uint) {
return myNumber;
}
// دالة لتعديل قيمة myNumber (ستكلف غاز)
function setMyNumber(uint _newNumber) public {
myNumber = _newNumber; // تعديل حالة العقد
}
}
الخطوة الثانية: الدوال pure - عمليات حسابية بحتة
الدوال pure هي أكثر تقييداً من دوال view. فهي لا تقوم فقط بعدم تعديل حالة العقد، بل لا تقوم أيضاً بقراءة أي متغير حالة من العقد نفسه. يمكنها فقط الوصول إلى المتغيرات التي تمرر لها كمعاملات (parameters) أو المتغيرات المحلية داخل الدالة.
ملاحظة تقنية: تماماً مثل دوال
view، فإن دوالpureلا تتطلب رسوم غاز عند استدعائها من خارج العقد، لأنها لا تتفاعل مع حالة البلوكتشين على الإطلاق. إنها مجرد عمليات حسابية بحتة.
مثال على دالة pure:
لنضف دالة pure إلى عقدنا لإجراء عملية حسابية بسيطة:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasSaver {
uint public myNumber;
constructor(uint _initialNumber) {
myNumber = _initialNumber;
}
function getMyNumber() public view returns (uint) {
return myNumber;
}
function setMyNumber(uint _newNumber) public {
myNumber = _newNumber;
}
// دالة pure تقوم بعملية حسابية بحتة
// لا تقرأ ولا تعدل حالة العقد ولا تكلف غاز عند الاستدعاء الخارجي
function addNumbers(uint a, uint b) public pure returns (uint) {
return a + b; // تقوم بجمع الأرقام المُمررة فقط
}
}
الخطوة الثالثة: التفاعل وتوفير الغاز باستخدام JavaScript (Ethers.js/Hardhat)
الآن، دعونا نرى كيف يمكننا التفاعل مع هذه الدوال ونلاحظ توفير الغاز عملياً باستخدام سكريبت Hardhat (الذي يستخدم Ethers.js داخلياً).
سنقوم بنشر العقد، ثم استدعاء دالة view ودالة pure، وأخيراً دالة تعدل الحالة لنرى الفرق في تكلفة الغاز.
// scripts/deployAndInteract.js
const { ethers } = require("hardhat");
async function main() {
console.log("نشر العقد GasSaver...");
const GasSaver = await ethers.getContractFactory("GasSaver");
const gasSaver = await GasSaver.deploy(100); // نشر العقد بقيمة ابتدائية 100
await gasSaver.deployed();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم نشر العقد GasSaver على العنوان: ${gasSaver.address}</code>);
console.log("\n--- استدعاء دالة view (getMyNumber) ---");
// استدعاء دالة view لا يكلف غاز لأنها تقرأ فقط
const currentNumber = await gasSaver.getMyNumber();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">القيمة الحالية لـ myNumber: ${currentNumber.toString()}</code>);
console.log("\n--- استدعاء دالة pure (addNumbers) ---");
// استدعاء دالة pure لا يكلف غاز لأنها لا تتفاعل مع حالة العقد
const sum = await gasSaver.addNumbers(50, 25);
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">نتيجة 50 + 25 من دالة pure: ${sum.toString()}</code>);
console.log("\n--- استدعاء دالة تعديل الحالة (setMyNumber) ---");
// استدعاء دالة تعديل الحالة يكلف غاز لأنه يغير حالة العقد
const tx = await gasSaver.setMyNumber(200);
await tx.wait(); // انتظار تأكيد المعاملة
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم تعديل myNumber إلى 200 في المعاملة: ${tx.hash}</code>);
console.log("\n--- التحقق من القيمة بعد التعديل باستخدام دالة view ---");
const updatedNumber = await gasSaver.getMyNumber();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">القيمة الجديدة لـ myNumber: ${updatedNumber.toString()}</code>);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
الكود النهائي الكامل
عقد Solidity (contracts/GasSaver.sol):
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GasSaver {
uint public myNumber; // متغير حالة عام يمكن قراءته
constructor(uint _initialNumber) {
myNumber = _initialNumber; // تهيئة المتغير عند نشر العقد
}
// دالة view تقرأ قيمة myNumber
// لا تعدل حالة العقد ولا تكلف غاز عند الاستدعاء الخارجي
function getMyNumber() public view returns (uint) {
return myNumber;
}
// دالة لتعديل قيمة myNumber (ستكلف غاز)
function setMyNumber(uint _newNumber) public {
myNumber = _newNumber; // تعديل حالة العقد
}
// دالة pure تقوم بعملية حسابية بحتة
// لا تقرأ ولا تعدل حالة العقد ولا تكلف غاز عند الاستدعاء الخارجي
function addNumbers(uint a, uint b) public pure returns (uint) {
return a + b; // تقوم بجمع الأرقام المُمررة فقط
}
}
سكريبت Hardhat للتفاعل (scripts/deployAndInteract.js):
// scripts/deployAndInteract.js
const { ethers } = require("hardhat");
async function main() {
console.log("نشر العقد GasSaver...");
const GasSaver = await ethers.getContractFactory("GasSaver");
const gasSaver = await GasSaver.deploy(100); // نشر العقد بقيمة ابتدائية 100
await gasSaver.deployed();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم نشر العقد GasSaver على العنوان: ${gasSaver.address}</code>);
console.log("\n--- استدعاء دالة view (getMyNumber) ---");
// استدعاء دالة view لا يكلف غاز لأنها تقرأ فقط
const currentNumber = await gasSaver.getMyNumber();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">القيمة الحالية لـ myNumber: ${currentNumber.toString()}</code>);
console.log("\n--- استدعاء دالة pure (addNumbers) ---");
// استدعاء دالة pure لا يكلف غاز لأنها لا تتفاعل مع حالة العقد
const sum = await gasSaver.addNumbers(50, 25);
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">نتيجة 50 + 25 من دالة pure: ${sum.toString()}</code>);
console.log("\n--- استدعاء دالة تعديل الحالة (setMyNumber) ---");
// استدعاء دالة تعديل الحالة يكلف غاز لأنه يغير حالة العقد
const tx = await gasSaver.setMyNumber(200);
await tx.wait(); // انتظار تأكيد المعاملة
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">تم تعديل myNumber إلى 200 في المعاملة: ${tx.hash}</code>);
console.log("\n--- التحقق من القيمة بعد التعديل باستخدام دالة view ---");
const updatedNumber = await gasSaver.getMyNumber();
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">القيمة الجديدة لـ myNumber: ${updatedNumber.toString()}</code>);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
النتيجة المتوقعة
عند تشغيل سكريبت Hardhat باستخدام الأمر npx hardhat run scripts/deployAndInteract.js (بعد إعداد مشروع Hardhat بالطبع)، ستكون النتيجة مشابهة لما يلي في سطر الأوامر:
نشر العقد GasSaver...
تم نشر العقد GasSaver على العنوان: 0x5FbDB2315678afecb367f032d93F642f64180aa3 (قد يختلف العنوان)
--- استدعاء دالة view (getMyNumber) ---
القيمة الحالية لـ myNumber: 100
--- استدعاء دالة pure (addNumbers) ---
نتيجة 50 + 25 من دالة pure: 75
--- استدعاء دالة تعديل الحالة (setMyNumber) ---
تم تعديل myNumber إلى 200 في المعاملة: 0x... (معرف المعاملة)
--- التحقق من القيمة بعد التعديل باستخدام دالة view ---
القيمة الجديدة لـ myNumber: 200
لاحظ أن استدعاء الدوال view و pure لم ينتج عنه أي معاملات على الشبكة (وبالتالي لا توجد رسوم غاز)، بينما أدى استدعاء دالة setMyNumber إلى إنشاء معاملة وتغيير حالة العقد، مما يتطلب رسوم غاز.
تهانينا! لقد أتقنت الآن كيفية استخدام دوال view و pure لتصميم عقود ذكية أكثر كفاءة وتوفيراً للغاز.