شیارهای ذخیره سازی در سالیدیتی

در این مقاله، به بررسی شیارهای ذخیره سازی در سالیدیتی و ساختار ذخیره‌سازی قراردادهای هوشمند در شبکه اتریوم می‌پردازیم. ابتدا توضیح می‌دهیم که ماشین مجازی اتریوم (EVM) چگونه مقادیر متغیرها را در حافظه نگه می‌دارد، و در ادامه، نحوه خواندن و نوشتن داده‌ها در شیارهای ذخیره‌سازی را با استفاده از زبان اسمبلی سطح پایین (Yul) آموزش می‌دهیم.

درک این مفاهیم برای افرادی که قصد دارند عملکرد پراکسی‌ ها را در سالیدیتی بفهمند یا مصرف گس قراردادهای خود را بهینه‌ سازی کنند، کاملاً ضروری است.

معماری ذخیره‌ سازی در قراردادهای هوشمند

هر متغیر در قرارداد هوشمند مقدار خود را در یکی از دو بخش اصلی ذخیره می‌کند: حافظه ذخیره‌ سازی (storage) یا بایت‌ کد (bytecode).

متغیرها مقدار خود را یا در بایت‌ کد یا در حافظه ذخیره می‌کنند.

بایت‌ کد

بایت‌ کد اطلاعاتی را در خود نگه می‌دارد که در زمان اجرا تغییر نمی‌کنند. این اطلاعات شامل مقادیر متغیرهایی از نوع constant و immutable هستند.

علاوه بر این، کامپایلر سالیدیتی کل کد منبع قرارداد را به بایت‌ کد تبدیل می‌کند:
در تابع doubleX()، مقدار ثابت متغیر x (یعنی 20) نیز در بایت‌ کد ذخیره می‌شود، چون هنگام اجرا تغییر نمی‌کند و کامپایلر آن را به‌عنوان بخشی از منطق تابع در نظر می‌گیرد.

در این مقاله تمرکز ما روی فضای ذخیره‌ سازی است، بنابراین بایت‌ کد را فقط در همین حد بررسی می‌کنیم.

فضای ذخیره‌ سازی (Storage)

فضای ذخیره‌ سازی محلی برای نگهداری داده‌ های قابل تغییر (mutable) است. متغیرهایی که مقدار خود را در این فضا ذخیره می‌کنند، به عنوان متغیرهای ذخیره‌ سازی یا متغیرهای حالت (state variables) شناخته می‌شوند.

متغیرهای ذخیره‌ سازی، داده‌ های خود را در فضای ذخیره‌ سازی ذخیره می‌کنند.

مقدار این متغیرها به‌طور دائمی در فضای ذخیره‌ سازی باقی می‌ماند، مگر آن‌که تراکنش جدیدی مقدار آن‌ها را تغییر دهد یا قرارداد به‌طور کامل نابود شود (self-destruct).

تمام متغیرهایی که در محدوده سراسری (global scope) یک قرارداد تعریف شده‌اند و نوع آن‌ها نه constant و نه immutable است، در فضای ذخیره‌ سازی قرار می‌گیرند.

برای مثال:

وقتی با یکی از این متغیرها تعامل داریم، در پشت صحنه، قرارداد در حال خواندن یا نوشتن در فضای ذخیره‌ سازی است؛ به طور دقیق‌تر، در همان شیار حافظه‌ای (storage slot) که آن متغیر در آن قرار دارد.

شیارهای ذخیره سازی (Storage Slots)

ماشین مجازی اتریوم (EVM) فضای ذخیره سازی قرارداد هوشمند را به صورت مجموعه ای از شیارهای مشخص سازماندهی می کند. هر شیار فضای ثابتی برابر با ۲۵۶ بیت یا ۳۲ بایت دارد (32=8÷256).

اسلات‌ های ذخیره‌ سازی به صورت نموداری نمایش داده می‌شوند

شیارهای ذخیره سازی در قراردادهای هوشمند از عدد 0 تا 2^256 – 1 شماره گذاری می شوند. این اعداد نقش شناسه های منحصر به فرد را ایفا می کنند تا EVM بتواند هر شیار را به طور دقیق شناسایی و دسترسی پیدا کند.

کامپایلر سالیدیتی فضای ذخیره سازی را به شکل پیوسته و کاملاً قابل پیش بینی، طبق ترتیب تعریف متغیرها در قرارداد، به آن ها اختصاص می دهد.

به عنوان مثال، اگر یک قرارداد شامل دو متغیر ذخیره سازی uint256 x و uint256 y باشد، هرکدام در شیار مشخصی قرار می گیرند.

از آنجا که متغیر x پیش از y تعریف شده است، کامپایلر ابتدا x را در شیار ذخیره سازی شماره ۰ قرار می دهد و سپس y را در شیار شماره ۱ اختصاص می دهد. در نتیجه، مقدار x همواره در شیار ۰ قرار دارد و مقدار y در شیار ۱ نگهداری می شود.

انیمیشن متغیرهای ذخیره‌ سازی که مقدار خود را در اسلات‌ های ذخیره‌ سازی اختصاص داده شده ذخیره می‌کنند

زمانی که مقادیر x و y فراخوانی شوند، سیستم همواره داده ها را از شیارهای ذخیره سازی مشخص شده برای هر متغیر می خواند. پس از استقرار قرارداد روی بلاکچین، هیچ متغیری نمی تواند شیار ذخیره سازی خود را تغییر دهد.

در صورتی که برای x و y مقداردهی اولیه انجام نشود، مقدار پیش‌فرض آن ها صفر خواهد بود. تمامی متغیرهای ذخیره سازی تا زمانی که به صورت صریح مقداردهی نشوند، مقدار صفر را در اختیار دارند.

برای تنظیم مقدار x برابر با ۲۰، می توانیم تابع set_x(20) را فراخوانی کنیم.
این تراکنش باعث تغییر وضعیت در شیار ذخیره سازی شماره ۰ می شود و مقدار آن را از صفر به ۲۰ به‌روزرسانی می کند.

انیمیشن تغییر حالت متغیر x که توسط یک تابع فعال می‌شود

در واقع، تمامی تغییرات وضعیت در یک قرارداد هوشمند، مستقیماً با تغییراتی در همین شیارهای ذخیره سازی مطابقت دارند.

درون شیارهای ذخیره سازی: داده های ۲۵۶ بیتی

هر شیار در فضای ذخیره سازی، داده ها را با فرمت ۲۵۶ بیت نگه می دارد. این داده ها نمایشی بیتی از مقدار یک متغیر ذخیره سازی هستند.

در مثال قبلی، متغیر uint256 x مقدار خود را در شیار شماره ۰ ذخیره می کند. از آنجا که نوع uint256 دقیقاً ۲۵۶ بیت (یا ۳۲ بایت) فضا نیاز دارد، این متغیر کل ظرفیت شیار شماره ۰ را اشغال می کند.

  • پیش از آنکه تابع set_x(20) اجرا شود، شیار ۰ در حالت پیش فرض قرار داشت. در این حالت، تمام بیت های آن برابر با صفر بودند.

وضعیت پیش‌فرض اسلات ذخیره‌سازی به صورت متنی و بیتسان خام نمایش داده می‌شود

تمام صفرهای سبز رنگی که در تصویر بالا دیده می شوند، نشان دهنده بیت هایی هستند که برای ذخیره مقدار متغیر x استفاده می شوند.

  • پس از اجرای تابع set_x(20)، شیار شماره ۰ مقدار جدید خود را دریافت کرد و وضعیت آن به نمایش بیتی عدد uint256 20 تغییر یافت.

نمایش متنی و بیت خام از اسلات ذخیره‌ سازی ۰ با حفظ مقدار ۲۰

خواندن محتوای یک شیار ذخیره سازی به صورت خام و در قالب ۲۵۶ بیت برای انسان دشوار است. به همین دلیل، توسعه دهندگان سالیدیتی معمولاً این مقادیر را در قالب هگزادسیمال (hexadecimal) بررسی می کنند.

فرمت خام ۲۵۶ بیتی:

فرمت هگزادسیمال:

رشته ای که شامل ۲۵۶ بیت صفر و یک است، تنها با ۶۴ کاراکتر هگزادسیمال نمایش داده می شود. هر کاراکتر هگزادسیمال معادل ۴ بیت است و هر دو کاراکتر معادل یک بایت محسوب می شوند. عدد 0x14 در مبنای هگزادسیمال معادل عدد ۲۰ در مبنای دهدهی است.
به بیان دیگر: 0x14 (hex) = 10100 (binary) = 20 (decimal)

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

نوع داده اولیه و پیچیده

در طول این مقاله، تمام مثال ها بر پایه نوع داده های اولیه طراحی شده اند؛ مانند اعداد صحیح بدون علامت (uint)، اعداد صحیح با علامت (int)، آدرس ها (address) و مقادیر بولی (bool).

هر یک از این متغیرها حداکثر یک شیار ذخیره سازی را اشغال می کنند.

در مقابل، نوع داده های پیچیده مانند ساختارها (struct {})، آرایه ها (array[])، نگاشت ها (mapping(address => uint256))، رشته ها (string) و بایت ها (bytes32) از الگوی تخصیص بسیار پیچیده تری پیروی می کنند. توضیح دقیق نحوه تخصیص شیار به این نوع داده ها، نیاز به مقاله ای جداگانه دارد.

فشرده سازی در فضای ذخیره سازی (Storage Packing)

تا این مرحله، فقط با متغیرهای uint256 کار کرده ایم؛ این نوع داده تمام ۳۲ بایت یک شیار ذخیره سازی را اشغال می کند. اما سایر نوع داده های اولیه مانند uint8، uint32، uint128، address و bool اندازه کوچکتری دارند و به همین دلیل فضای کمتری را مصرف می کنند. بنابراین می توان چند متغیر از این نوع داده ها را در یک شیار ذخیره سازی کنار هم قرار داد.

نکته تکمیلی: هر مضرب عدد ۸ تا سقف ۲۵۶، یک نوع معتبر از uint محسوب می شود. همچنین انواع bytes1 تا bytes32 نیز به عنوان نوع داده های ثابت‌شکل بایت، معتبر هستند.

جدول زیر اندازه برخی از نوع داده های اولیه را در فضای ذخیره سازی نشان می دهد:

نوع داده اندازه
bool 1 byte
uint8 1 byte
uint32 4 bytes
uint128 16 bytes
address 20 bytes
uint256 32 bytes

برای نمونه، یک متغیر ذخیره سازی از نوع address تنها به ۲۰ بایت فضا نیاز دارد تا مقدار خود را ذخیره کند؛ همانطور که در جدول بالا مشخص شده است.

در قرارداد بالا، متغیر owner برای ذخیره مقدار خود، ۲۰ بایت از ۳۲ بایت موجود در شیار ذخیره سازی شماره ۰ را مصرف می کند.

تخصیص اسلات ذخیره‌ سازی برای یک متغیر آدرس واحد

سالیدیتی فرآیند فشرده سازی متغیرها در شیارهای ذخیره سازی را از بایت کم‌ارزش‌تر (یعنی بایت سمت راست) آغاز می کند و به سمت چپ ادامه می دهد.

می توان این رفتار را با خواندن مقدار شیار به صورت bytes32 تأیید کرد:

نگاشت مالک آدرس به دنباله بایت آن

همانطور که در نمودار بالا مشاهده می شود، مقدار متغیر owner یعنی 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 از بایت سمت راست یا همان کم‌ارزش‌ترین بایت در شیار ذخیره سازی شروع می شود. در نتیجه، ۱۲ بایت باقی‌مانده در شیار شماره ۰ بلااستفاده باقی می‌ماند؛ فضایی که متغیر دیگری می تواند از آن استفاده کند.

اگر متغیرهای کوچکتری به‌صورت پشت‌سر‌هم تعریف شوند و مجموع اندازه آن ها کمتر از ۲۵۶ بیت (یا ۳۲ بایت) باشد، سالیدیتی آن ها را در همان شیار ذخیره سازی قرار می دهد.

برای مثال، اگر یک متغیر bool (۱ بایت) و یک متغیر uint32 (۴ بایت) به عنوان متغیرهای دوم و سوم تعریف شوند، سالیدیتی آن ها را در همان شیار شماره ۰ و در فضای باقی‌مانده ذخیره می کند.

متغیر Boolean که به عنوان دومین متغیر تعریف شده، مقدار خود را در اولین بایت سمت چپ توالی بایت های owner ذخیره می کند؛ یعنی در کم‌ارزش‌ترین بایت از فضای باقی‌مانده. همانطور که گفته شد، سالیدیتی متغیرها را از راست به چپ درون شیارها قرار می دهد.

نموداری که توالی بایت‌ های متعلق به مالک آدرس و متغیر بولی را نشان می‌دهد

متغیر thirdVar از نوع uint32 که به عنوان سومین متغیر ذخیره سازی تعریف شده است، مقدار خود را در سمت چپ توالی بایت های متغیر Boolean قرار می دهد.

نگاشت سه متغیر (thirdvar، Boolean، owner) به دنباله بایت مربوطه.

اگر متغیر ذخیره سازی چهارمی با نام admin و از نوع address تعریف کنیم، مقدار آن در شیار ذخیره سازی بعدی، یعنی شیار شماره ۱ قرار می گیرد.

نمودار تخصیص حافظه SOT با ۴ متغیر حالت.

در اینجا، مقدار admin به طور کامل در فضای باقیمانده شیار شماره ۰ جا نمی گیرد. تنها ۷ بایت فضای خالی باقی مانده، در حالی که admin به ۲۰ بایت فضای متوالی نیاز دارد. سالیدیتی مقدار admin را بین دو شیار تقسیم نمی کند (یعنی ۷ بایت در شیار ۰ و ۱۳ بایت در شیار ۱ قرار نمی گیرد). در عوض، کل مقدار admin را در شیار جدیدی به نام شیار شماره ۱ قرار می دهد.

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

تعریف متغیرهای کوچکتر به صورت متوالی

در این ترتیب، متغیرهای uint16 a و uint32 b نمی توانند در یک شیار ذخیره سازی قرار بگیرند.

در نتیجه، کامپایلر متغیر a را در شیار شماره ۰، متغیر x را در شیار شماره ۱، و متغیر b را در شیار شماره ۲ ذخیره می کند. به این ترتیب، سه شیار جداگانه مصرف می شود و تخصیص حافظه بهینه نخواهد بود. تخصیص شیارهای ذخیره سازی در این حالت به صورت نمودار زیر خواهد بود:

تخصیص ناکارآمد اسلات ذخیره‌سازی برای یک متغیر uint256 که بین دو متغیر ذخیره‌سازی کوچک‌تر تعریف شده است

روش بهینه‌تر این است که ترتیب تعریف متغیرها را به گونه ای تنظیم کنیم که داده های کوچکتر بتوانند کنار هم قرار بگیرند:

در این ساختار، متغیرهای a و b می توانند با هم در یک شیار ذخیره سازی قرار بگیرند و فضای حافظه به شکل بهینه‌تری مورد استفاده قرار می گیرد.

تخصیص کارآمد اسلات ذخیره‌ سازی برای سه متغیر ذخیره‌ سازی

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

دستکاری شیارهای ذخیره سازی با اسمبلی (YUL)

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

در Yul دو اپکد (Opcode) مرتبط با ذخیره سازی وجود دارد: sload() و sstore().

  • sload() مقدار ذخیره شده در یک شیار مشخص را می خواند.

  • sstore() مقدار جدیدی را در یک شیار مشخص ذخیره می کند.

علاوه بر این، دو کلیدواژه مهم دیگر نیز در Yul وجود دارد: .slot و .offset

  • .slot موقعیت متغیر در بین شیارهای ذخیره سازی را بازمی گرداند.

  • .offset میزان جابجایی بایتی متغیر را نشان می دهد. (توضیح آن در بخش دوم ارائه خواهد شد)

کلیدواژه .slot

در قرارداد زیر، سه متغیر ذخیره سازی از نوع uint256 تعریف شده اند:

با توجه به ترتیب تعریف، می توان نتیجه گرفت که متغیرهای x، y و z به ترتیب مقادیر خود را در شیار ۰، شیار ۱ و شیار ۲ ذخیره می کنند.

برای اثبات این موضوع، می توانیم با استفاده از کلیدواژه .slot به موقعیت هر متغیر در فضای ذخیره سازی دسترسی پیدا کنیم. این کلیدواژه موقعیت دقیق شیار ذخیره سازی مربوط به هر متغیر را نمایش می دهد.

کلیدواژه .slot به ما نشان می دهد که هر متغیر مقدار خود را در کدام شیار ذخیره سازی نگه می دارد.

برای مثال، اگر بخواهیم موقعیت شیار ذخیره سازی متغیر x را بررسی کنیم، در زبان اسمبلی کافی است .slot را به نام متغیر اضافه کنیم: x.slot

نمونه کد زیر این کار را انجام می دهد:

در اینجا، x.slot مقدار ۰ را برمی گرداند؛ یعنی متغیر x مقدار خود را در شیار شماره ۰ ذخیره می کند.

x.slot شماره اسلات متغیر ذخیره‌سازی x را برمی‌گرداند.

دستور y.slot مقدار ۱ را برمی گرداند، که نشان می دهد متغیر y مقدار خود را در شیار ذخیره سازی شماره ۱ نگهداری می کند.

y.slot شماره اسلات متغیر ذخیره‌سازی y را برمی‌گرداند.

دستور z.slot مقدار ۲ را برمی گرداند، که نشان می دهد متغیر z مقدار خود را در شیار ذخیره سازی شماره ۲ نگهداری می کند.

z.slot شماره اسلات متغیر ذخیره‌سازی z را برمی‌گرداند.

خواندن مستقیم مقدار متغیرها از شیار ذخیره سازی: دستور sload()

زبان Yul این امکان را فراهم می کند که مستقیماً مقدار ذخیره شده در یک شیار ذخیره سازی را بخوانیم. برای این منظور از اپکد sload(slot) استفاده می شود. این دستور یک ورودی می گیرد: شناسه شیار ذخیره سازی (slot) و خروجی آن تمام ۲۵۶ بیت داده ای است که در آن شیار قرار دارد. یادگیری این مفاهیم به‌ویژه برای افرادی که به دنبال درک عمیق‌تر فضای حافظه در آموزش برنامه نویسی قراردادهای هوشمند هستند، ضروری است.

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

  • کلیدواژه .slot مانند sload(x.slot)

  • یک متغیر محلی که مقدار اسلات را نگهداری می کند مانند sload(localvar)

  • یک عدد ثابت مانند sload(1)

در ادامه چند مثال از نحوه استفاده از دستور sload() آورده شده است:

تابع readSlotX() مقدار ۲۵۶ بیتی ذخیره شده در x.slot (یعنی شیار شماره ۰) را بازیابی می کند و آن را در قالب uint256 بازمی گرداند. این مقدار در این مثال برابر با ۱۱ است.
  • دستور sload(0) مقدار موجود در شیار شماره ۰ را می خواند؛ در اینجا، این مقدار برابر با ۱۱ است.
  • دستور sload(1) مقدار موجود در شیار شماره ۱ را می خواند، که برابر با ۲۲ است.
  • دستور sload(2) مقدار موجود در شیار شماره ۲ را می خواند، که برابر با ۳۳ است.
  • اما دستور sload(3) به شیار شماره ۳ دسترسی پیدا می کند که هنوز مقداری در آن ذخیره نشده و همچنان در حالت پیش‌فرض قرار دارد؛ یعنی تمام بیت های آن صفر هستند.

انیمیشن زیر نحوه عملکرد اپکد sload را به صورت بصری نمایش می دهد:

تابع sloadOpcode(uint256 slotNumber) به ما این امکان را می دهد که مقدار ذخیره شده در هر شیار دلخواه را بخوانیم. این مقدار در قالب uint256 بازگردانده می شود:

نکته مهم اینجاست که sload() هیچ بررسی‌ای روی نوع داده انجام نمی دهد.

در زبان سالیدیتی، اگر تلاش کنیم یک متغیر از نوع uint256 را به صورت bool بازگردانیم، با خطای نوع مواجه خواهیم شد:

اما اگر همین عملیات را در محیط Yul انجام دهیم، کد بدون خطا کامپایل می شود:

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

بر همین اساس می توانیم از این ویژگی برای بررسی مقدار شیارهای ذخیره سازی در قالب bytes32 استفاده کنیم.

توضیح تصویری بازگرداندن مقدار یک اسلات ذخیره‌سازی بر حسب بایت ۳۲

نوشتن در شیار ذخیره سازی با استفاده از اپکد sstore()

زبان اسمبلی Yul امکان نوشتن مستقیم در شیارهای ذخیره سازی را از طریق اپکد sstore() فراهم می کند.

دستور sstore(slot, value) یک مقدار ۳۲ بایتی را مستقیماً در یک شیار ذخیره سازی ذخیره می کند. این دستور دو ورودی دارد:

  • slot: شماره شیار ذخیره سازی هدف که قرار است مقدار جدید در آن نوشته شود.

  • value: مقداری که باید در شیار مشخص شده ذخیره شود. اگر این مقدار کمتر از ۳۲ بایت باشد، از سمت چپ با صفر پر می شود (left padded with zeroes).

دستور sstore(slot, value) کل محتوای شیار ذخیره سازی را با مقدار جدید بازنویسی می کند.

قرارداد زیر نحوه استفاده از sstore() را نمایش می دهد؛ در این مثال، از این دستور برای تغییر مقدار متغیرهای x و y استفاده می کنیم:

تابع sstore_x(newVal) به صورت مستقیم مقدار ذخیره شده در شیاری را به‌روزرسانی می کند که متغیر x به آن اشاره دارد. این فرآیند عملاً مقدار x را تغییر می دهد.

برای نمونه، زمانی که اپکد sstore_x(88) فراخوانی می شود، مقدار جدید ۸۸ در شیار ذخیره سازی مربوط به x نوشته می شود و مقدار قبلی به‌طور کامل جایگزین می گردد.

انیمیشن زیر نشان می دهد که در پشت صحنه هنگام اجرای این دستور چه اتفاقی می افتد:

هر دو تابع sstore_x(newVal) و set_x() دقیقاً یک عملکرد دارند؛ هر دو مقدار متغیر x را با مقدار جدید جایگزین می کنند.

تابع زیر با نام sstoreArbitrarySlot(slot, newVal) قادر است مقدار هر شیار ذخیره سازی دلخواه را تغییر دهد. به همین دلیل، اکیداً توصیه می شود هرگز از آن در محیط عملیاتی (production) استفاده نشود:

برای مثال، اگر این تابع را با ورودی sstoreArbitrarySlot(1, 48) فراخوانی کنیم، مقدار متغیر y از ۲۲ به ۴۸ تغییر خواهد کرد؛ زیرا متغیر y مقدار خود را در شیار ذخیره سازی شماره ۱ نگه می دارد. این فراخوانی، مقدار موجود در شیار ۱ را بازنویسی می کند.

اپکد sstore() هیچ بررسی‌ای روی نوع داده انجام نمی دهد. به طور معمول، اگر تلاش کنیم یک مقدار از نوع uint256 را به متغیر address اختصاص دهیم، با خطای نوع مواجه می شویم و قرارداد کامپایل نمی شود:

خطا: نوع uint256 به صورت ضمنی قابل تبدیل به نوع مورد انتظار address نیست.

اما اگر همین مقدار را با استفاده از sstore() به شیار مربوط به owner اختصاص دهیم، هیچ خطایی ایجاد نمی شود؛ زیرا این اپکد بررسی نوع انجام نمی دهد:

دستکاری متغیرهای فشرده‌ سازی‌ شده در حافظه با استفاده از Yul – بخش دوم

اپکدهای sstore و sload همواره با طول ثابت ۳۲ بایت کار می کنند. این رفتار زمانی مفید است که با متغیرهایی از نوع uint256 سروکار داریم؛ زیرا کل ۳۲ بایت خوانده یا نوشته‌شده دقیقاً با ساختار داده‌ای این نوع مطابقت دارد.

اما زمانی که چند متغیر در یک شیار ذخیره سازی فشرده‌سازی (packed) شده اند، شرایط پیچیده‌تر می شود. در این حالت، توالی بایتی هر متغیر تنها بخشی از فضای ۳۲ بایتی شیار را اشغال می کند و در زبان اسمبلی Yul، هیچ اپکدی وجود ندارد که بتواند به‌طور مستقیم فقط همان بخش خاص را بخواند یا تغییر دهد.

در بخش دوم، یاد می گیریم که چگونه با استفاده از تکنیک‌های بیت‌مانیتورینگ (bit manipulation) و ماسک‌گذاری بیتی (bit masking)، متغیرهای فشرده‌شده در شیارهای ذخیره سازی را به‌طور دقیق مدیریت کنیم.

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

آموزش پروژه محور طراحی سایت با پایتون و جنگو مختص بازار کار
  • انتشار: ۵ تیر ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید