ربما تكون قد صادفت بعض نصائح Solidity لتحسين مهاراتك في البرمجة من أجل توفير بعض الوقود، ولكن اليوم أريد التركيز أكثر على كيف يمكن لفهم آلة Ethereum الافتراضية أن يوفر لك تكاليف الغاز بشكل فعال على عقودك الذكية.
وبما أننا سنتعمق في عالم الإيثريوم، فسوف أترك هنا مقتطفًا من الورقة الصفراء التي تحدد تكاليف الغاز لأكواد التشغيل، وسنشير إليها خلال المقالة.
نصيحة رقم 1: الوصول البارد مقابل الوصول الدافئ
الحمولة الباردة: 2100 غاز
Gwarmaccess: 100 غاز
لدينا هنا أول OPCODES، والأول يحدد مقدار التكلفة للوصول إلى متغير لأول مرة (أو cold access)، بينما الثاني يحدد مقدار التكلفة للوصول إلى المتغير للمرة الثانية وما بعدها (warm access). كما ترى، فرق السعر كبير جدًا، لذا فإن فهم ذلك يمكن أن يحدث فارقًا كبيرًا في تكاليف معاملات عقدك الذكي. لنرَ مثالًا.
تخزين البيانات داخل دالة في Solidity يمكن أن يؤدي إلى تقليل استهلاك الغاز، حتى لو تطلب ذلك أسطرًا إضافية من الكود. في هذه الحالة، يتم ذلك عبر تغيير مكان المصفوفة: بدلًا من استخدامها من التخزين storage وبالتالي الوصول إليها البارد (cold) في كل مرة داخل الحلقة، يتم تخزينها في الذاكرة memory حيث تكون أرخص في الوصول إليها.
نصيحة رقم 2: القيم صفر مقابل غير صفر وعمليات ردّ الغاز
Gsset = 20,000 غاز
Rsclear = {خصم على سعر التنفيذ}
تغيير قيمة من 0 إلى غير صفر على بلوكشين Ethereum مكلف كما نرى في سعر Gsset، لكن تغيير قيمة من غير صفر إلى 0 يمكن أن يمنحك ردًّا (Refund) في قيمة الغاز وفقًا للأمر Rsclear. ولئلا تحصل على الاستفادة من هذا الردّ بشكل غير متناسب، فقد جرى تحديد أنه لا يمكنك الحصول على ردّ إلا حتى حد أقصى يبلغ 20% من التكلفة الإجمالية للمعاملة.
يمكنك العثور على مثل هذا السيناريو في حالة شائعة جدًا على البلوكشين، وهي تحديث رصيد العناوين في العقود الذكية. لنرَ مثالًا لكل حالة:
في المثال الأول لعقد ZeroToNonZero، التحويل من غير الصفر إلى غير الصفر (5,000 غاز*) + التحويل من الصفر إلى غير الصفر (20,000 غاز) = 25,000 غاز
في المثال الثاني لعقد NonZeroToZero، التحويل من غير الصفر إلى الصفر (5,000 غاز*) + من الصفر إلى غير الصفر (20,000 غاز) — خصم (4,800 غاز) = 21,200 غاز
*2,100 (Gcolssload) + 2,900 (Gsreset) = 5,000 غاز
نصيحة رقم 3: ترتيب متغيرات الحالة يهم
التخزين (Storage) يشبه بنية بيانات على شكل مفتاح-قيمة (key-value) تحتفظ بقيم متغيرات الحالة في عقد Solidity الذكي.
يمكنك اعتبار التخزين مثل مصفوفة (array) للمساعدة على تصور ذلك. كل مساحة في هذا “المصفوفة” تُسمّى خانة slot وتحمل 32 بايت (256 بت) من البيانات، وكل متغير حالة يُعلن في العقد الذكي سيشغل خانة اعتمادًا على موضع إعلانه ونوعه.
ليست كل أنواع البيانات تستهلك كل 32 بايت في كل خانة، إذ توجد بعض الأنواع (مثل bool وuint8 وaddress…) التي تأخذ أقل من ذلك.
الحيلة هنا هي أنه إذا كانت متغيران/ثلاثة أو أكثر معًا يساوي 32 بايت أو أقل، فسيحاول مُصرّف Solidity حشرها معًا في خانة واحدة، لكن يجب تعريف هذه المتغيرات بجانب بعضها البعض.
هنا نحن نستخدم أنواع البيانات bool (1 بايت)، وaddress (20 بايت)، وuint256 (32 بايت). لذا، بمعرفة حجم هذه المتغيرات يمكنك فهم أن المثال الأول في عقد TwoSlots حيث لدينا bool وaddress معًا (1 + 20 = 21 بايت، وهي أقل من 32 بايت) سيشغلان خانة واحدة. أما في عقد ThreeSlots، فـ bool وuint256 لا يمكن أن يكونا في نفس الخانة (1 + 32 = 33 بايت، وهي أكبر من سعة الخانة) وبذلك سنستخدم ثلاث خانات إجمالًا.
لماذا هذا مهم جدًا؟
تعليمـة SLOAD تكلف 2100 غاز، وتُستخدم للقراءة من خانات التخزين، لذا إذا أمكنك تخزين المتغيرات في عدد أقل من الخانات، فسوف توفر بعض الغاز.
نصيحة رقم 4: uint256 أرخص من uint8
لقد تعلمنا في نصيحة رقم 3 أن uint256 (256 بت = 32 بايت) يشغل خانةً لوحده، كما تعلمنا أيضًا أن uint8 أقل من 32 بايت. لذا، رغم أنه من الواضح أن 8 بت أصغر من 256 بت، فكيف يكون uint256 أرخص؟
لفهم سبب أهمية ذلك، من الضروري معرفة أنه إذا لم يملأ متغيرٌ نفسه كامل الخانة (slot)، ولم تكن هذه الخانة ممتلئة بأي متغير آخر، فستقوم EVM بملء باقي البتّات المتبقية بـ “0” من أجل تمكين التعامل معها.
إضافة “0” التي يجريها EVM ستكلف غازًا، أي أنه من أجل توفير غاز المعاملة، من الأفضل استخدام uint256 بدلًا من uint8.
__________________
نأمل أنه أثناء اكتشاف هذه النصائح لتقليل تكاليف الغاز في العقود الذكية الخاصة بك، تكون قد تعلمت أيضًا شيئًا عن كيفية عمل EVM.
__________________
تابع Twitter @TheBlockChainer لمعرفة المزيد من التحديثات اليومية حول العقود الذكية وامن Web3 وSolidity وتدقيق العقود الذكية وغيرها.
__________________