در این مقاله، به بررسی شیارهای ذخیره سازی در سالیدیتی و ساختار ذخیرهسازی قراردادهای هوشمند در شبکه اتریوم میپردازیم. ابتدا توضیح میدهیم که ماشین مجازی اتریوم (EVM) چگونه مقادیر متغیرها را در حافظه نگه میدارد، و در ادامه، نحوه خواندن و نوشتن دادهها در شیارهای ذخیرهسازی را با استفاده از زبان اسمبلی سطح پایین (Yul) آموزش میدهیم.
درک این مفاهیم برای افرادی که قصد دارند عملکرد پراکسی ها را در سالیدیتی بفهمند یا مصرف گس قراردادهای خود را بهینه سازی کنند، کاملاً ضروری است.
معماری ذخیره سازی در قراردادهای هوشمند
هر متغیر در قرارداد هوشمند مقدار خود را در یکی از دو بخش اصلی ذخیره میکند: حافظه ذخیره سازی (storage) یا بایت کد (bytecode).
بایت کد
بایت کد اطلاعاتی را در خود نگه میدارد که در زمان اجرا تغییر نمیکنند. این اطلاعات شامل مقادیر متغیرهایی از نوع constant
و immutable
هستند.
1 2 3 4 |
contract ImmutableVariables{ uint256 constant myConstant = 100; uint256 immutable myImmutable; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
contract ImmutableVariables { uint256 constant myConstant = 100; uint256 immutable myImmutable; constructor(uint256 _myImmutable) { myImmutable = _myImmutable; } function doubleX() public pure returns (uint256) { uint256 x = 20; return x * 2; } } |
doubleX()
، مقدار ثابت متغیر x
(یعنی 20) نیز در بایت کد ذخیره میشود، چون هنگام اجرا تغییر نمیکند و کامپایلر آن را بهعنوان بخشی از منطق تابع در نظر میگیرد.
در این مقاله تمرکز ما روی فضای ذخیره سازی است، بنابراین بایت کد را فقط در همین حد بررسی میکنیم.
فضای ذخیره سازی (Storage)
فضای ذخیره سازی محلی برای نگهداری داده های قابل تغییر (mutable) است. متغیرهایی که مقدار خود را در این فضا ذخیره میکنند، به عنوان متغیرهای ذخیره سازی یا متغیرهای حالت (state variables) شناخته میشوند.
مقدار این متغیرها بهطور دائمی در فضای ذخیره سازی باقی میماند، مگر آنکه تراکنش جدیدی مقدار آنها را تغییر دهد یا قرارداد بهطور کامل نابود شود (self-destruct).
تمام متغیرهایی که در محدوده سراسری (global scope) یک قرارداد تعریف شدهاند و نوع آنها نه constant
و نه immutable
است، در فضای ذخیره سازی قرار میگیرند.
برای مثال:
1 2 3 4 5 6 |
contract StorageVariables{ uint256 x; address owner; mapping(address => uint256) balance; // and more... } |
شیارهای ذخیره سازی (Storage Slots)
ماشین مجازی اتریوم (EVM) فضای ذخیره سازی قرارداد هوشمند را به صورت مجموعه ای از شیارهای مشخص سازماندهی می کند. هر شیار فضای ثابتی برابر با ۲۵۶ بیت یا ۳۲ بایت دارد (32=8÷256).
شیارهای ذخیره سازی در قراردادهای هوشمند از عدد 0 تا 2^256 – 1 شماره گذاری می شوند. این اعداد نقش شناسه های منحصر به فرد را ایفا می کنند تا EVM بتواند هر شیار را به طور دقیق شناسایی و دسترسی پیدا کند.
کامپایلر سالیدیتی فضای ذخیره سازی را به شکل پیوسته و کاملاً قابل پیش بینی، طبق ترتیب تعریف متغیرها در قرارداد، به آن ها اختصاص می دهد.
به عنوان مثال، اگر یک قرارداد شامل دو متغیر ذخیره سازی uint256 x
و uint256 y
باشد، هرکدام در شیار مشخصی قرار می گیرند.
1 2 3 4 |
contract StorageVariables { uint256 public x; // first declared storage variable uint256 public y; // second declared storage variable } |
x
پیش از y
تعریف شده است، کامپایلر ابتدا x
را در شیار ذخیره سازی شماره ۰ قرار می دهد و سپس y
را در شیار شماره ۱ اختصاص می دهد. در نتیجه، مقدار x
همواره در شیار ۰ قرار دارد و مقدار y
در شیار ۱ نگهداری می شود.
زمانی که مقادیر x
و y
فراخوانی شوند، سیستم همواره داده ها را از شیارهای ذخیره سازی مشخص شده برای هر متغیر می خواند. پس از استقرار قرارداد روی بلاکچین، هیچ متغیری نمی تواند شیار ذخیره سازی خود را تغییر دهد.
در صورتی که برای x
و y
مقداردهی اولیه انجام نشود، مقدار پیشفرض آن ها صفر خواهد بود. تمامی متغیرهای ذخیره سازی تا زمانی که به صورت صریح مقداردهی نشوند، مقدار صفر را در اختیار دارند.
1 2 3 4 5 6 7 |
contract StorageVariables { uint256 public x; // Uninitialized storage variable function return_uninitialized_X() public view returns (uint256) { return x; // returns zero } } |
x
برابر با ۲۰، می توانیم تابع set_x(20)
را فراخوانی کنیم.
1 2 3 |
function set_x(uint256 value) external { x = value; } |
در واقع، تمامی تغییرات وضعیت در یک قرارداد هوشمند، مستقیماً با تغییراتی در همین شیارهای ذخیره سازی مطابقت دارند.
درون شیارهای ذخیره سازی: داده های ۲۵۶ بیتی
هر شیار در فضای ذخیره سازی، داده ها را با فرمت ۲۵۶ بیت نگه می دارد. این داده ها نمایشی بیتی از مقدار یک متغیر ذخیره سازی هستند.
در مثال قبلی، متغیر uint256 x
مقدار خود را در شیار شماره ۰ ذخیره می کند. از آنجا که نوع uint256
دقیقاً ۲۵۶ بیت (یا ۳۲ بایت) فضا نیاز دارد، این متغیر کل ظرفیت شیار شماره ۰ را اشغال می کند.
- پیش از آنکه تابع
set_x(20)
اجرا شود، شیار ۰ در حالت پیش فرض قرار داشت. در این حالت، تمام بیت های آن برابر با صفر بودند.
تمام صفرهای سبز رنگی که در تصویر بالا دیده می شوند، نشان دهنده بیت هایی هستند که برای ذخیره مقدار متغیر x
استفاده می شوند.
- پس از اجرای تابع
set_x(20)
، شیار شماره ۰ مقدار جدید خود را دریافت کرد و وضعیت آن به نمایش بیتی عددuint256 20
تغییر یافت.
خواندن محتوای یک شیار ذخیره سازی به صورت خام و در قالب ۲۵۶ بیت برای انسان دشوار است. به همین دلیل، توسعه دهندگان سالیدیتی معمولاً این مقادیر را در قالب هگزادسیمال (hexadecimal) بررسی می کنند.
فرمت خام ۲۵۶ بیتی:
1 |
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
فرمت هگزادسیمال:
1 |
0x0000000000000000000000000000000000000000000000000000000000000014 |
رشته ای که شامل ۲۵۶ بیت صفر و یک است، تنها با ۶۴ کاراکتر هگزادسیمال نمایش داده می شود. هر کاراکتر هگزادسیمال معادل ۴ بیت است و هر دو کاراکتر معادل یک بایت محسوب می شوند. عدد 0x14
در مبنای هگزادسیمال معادل عدد ۲۰ در مبنای دهدهی است.
به بیان دیگر: 0x14 (hex) = 10100 (binary) = 20 (decimal)
در بخش بعدی، نحوه استخراج مقدار یک شیار ذخیره سازی را با استفاده از اسمبلی و در قالب فرمت هگزادسیمال یا نوع bytes32
نشان خواهیم داد.
نوع داده اولیه و پیچیده
در طول این مقاله، تمام مثال ها بر پایه نوع داده های اولیه طراحی شده اند؛ مانند اعداد صحیح بدون علامت (uint
)، اعداد صحیح با علامت (int
)، آدرس ها (address
) و مقادیر بولی (bool
).
1 2 3 4 5 6 |
contract PrimitiveTypes { uint256 a; int256 b; address owner; bool isTrue; } |
هر یک از این متغیرها حداکثر یک شیار ذخیره سازی را اشغال می کنند.
در مقابل، نوع داده های پیچیده مانند ساختارها (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
تنها به ۲۰ بایت فضا نیاز دارد تا مقدار خود را ذخیره کند؛ همانطور که در جدول بالا مشخص شده است.
1 2 3 |
contract AddressVariable{ address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; } |
در قرارداد بالا، متغیر owner
برای ذخیره مقدار خود، ۲۰ بایت از ۳۲ بایت موجود در شیار ذخیره سازی شماره ۰ را مصرف می کند.
سالیدیتی فرآیند فشرده سازی متغیرها در شیارهای ذخیره سازی را از بایت کمارزشتر (یعنی بایت سمت راست) آغاز می کند و به سمت چپ ادامه می دهد.
می توان این رفتار را با خواندن مقدار شیار به صورت bytes32
تأیید کرد:
همانطور که در نمودار بالا مشاهده می شود، مقدار متغیر owner
یعنی 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
از بایت سمت راست یا همان کمارزشترین بایت در شیار ذخیره سازی شروع می شود. در نتیجه، ۱۲ بایت باقیمانده در شیار شماره ۰ بلااستفاده باقی میماند؛ فضایی که متغیر دیگری می تواند از آن استفاده کند.
اگر متغیرهای کوچکتری بهصورت پشتسرهم تعریف شوند و مجموع اندازه آن ها کمتر از ۲۵۶ بیت (یا ۳۲ بایت) باشد، سالیدیتی آن ها را در همان شیار ذخیره سازی قرار می دهد.
برای مثال، اگر یک متغیر bool
(۱ بایت) و یک متغیر uint32
(۴ بایت) به عنوان متغیرهای دوم و سوم تعریف شوند، سالیدیتی آن ها را در همان شیار شماره ۰ و در فضای باقیمانده ذخیره می کند.
1 2 3 4 5 6 7 |
contract AddressVariable { address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; // new bool Boolean = true; uint32 thirdvar = 5_000_000; } |
Boolean
که به عنوان دومین متغیر تعریف شده، مقدار خود را در اولین بایت سمت چپ توالی بایت های owner
ذخیره می کند؛ یعنی در کمارزشترین بایت از فضای باقیمانده. همانطور که گفته شد، سالیدیتی متغیرها را از راست به چپ درون شیارها قرار می دهد.
متغیر thirdVar
از نوع uint32
که به عنوان سومین متغیر ذخیره سازی تعریف شده است، مقدار خود را در سمت چپ توالی بایت های متغیر Boolean
قرار می دهد.
اگر متغیر ذخیره سازی چهارمی با نام admin
و از نوع address
تعریف کنیم، مقدار آن در شیار ذخیره سازی بعدی، یعنی شیار شماره ۱ قرار می گیرد.
1 2 3 4 5 6 7 8 |
contract AddressVariable { address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4; bool Boolean = true; uint32 thirdVar = 5_000_000; // new address admin = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; } |
در اینجا، مقدار admin
به طور کامل در فضای باقیمانده شیار شماره ۰ جا نمی گیرد. تنها ۷ بایت فضای خالی باقی مانده، در حالی که admin
به ۲۰ بایت فضای متوالی نیاز دارد. سالیدیتی مقدار admin
را بین دو شیار تقسیم نمی کند (یعنی ۷ بایت در شیار ۰ و ۱۳ بایت در شیار ۱ قرار نمی گیرد). در عوض، کل مقدار admin
را در شیار جدیدی به نام شیار شماره ۱ قرار می دهد.
هرگاه مقدار یک متغیر به طور کامل در فضای باقی مانده شیار فعلی جا نشود، سالیدیتی آن را به اولین شیار ذخیره سازی بعدی که به طور کامل در دسترس است منتقل می کند.
تعریف متغیرهای کوچکتر به صورت متوالی
1 2 3 |
uint16 public a; uint256 public x; // uint256 in the middle uint32 public b; |
در این ترتیب، متغیرهای uint16 a
و uint32 b
نمی توانند در یک شیار ذخیره سازی قرار بگیرند.
در نتیجه، کامپایلر متغیر a
را در شیار شماره ۰، متغیر x
را در شیار شماره ۱، و متغیر b
را در شیار شماره ۲ ذخیره می کند. به این ترتیب، سه شیار جداگانه مصرف می شود و تخصیص حافظه بهینه نخواهد بود. تخصیص شیارهای ذخیره سازی در این حالت به صورت نمودار زیر خواهد بود:
روش بهینهتر این است که ترتیب تعریف متغیرها را به گونه ای تنظیم کنیم که داده های کوچکتر بتوانند کنار هم قرار بگیرند:
1 2 3 4 |
uint256 public x; // packed together uint16 public a; uint32 public b; |
a
و b
می توانند با هم در یک شیار ذخیره سازی قرار بگیرند و فضای حافظه به شکل بهینهتری مورد استفاده قرار می گیرد.
اکنون که اصول ذخیره سازی متغیرهای اولیه در سالیدیتی را به خوبی درک کرده ایم، می توانیم وارد بخش بعدی شویم: نحوه دستکاری مستقیم این متغیرها در سطح اسمبلی با استفاده از Yul.
دستکاری شیارهای ذخیره سازی با اسمبلی (YUL)
زبان اسمبلی سطح پایین (Yul) امکان انجام عملیات مربوط به ذخیره سازی را با آزادی عمل بیشتری فراهم می کند. این زبان به ما اجازه می دهد به صورت مستقیم از شیارهای ذخیره سازی داده بخوانیم یا در آن ها بنویسیم و به ویژگی های متغیرهای ذخیره سازی دسترسی داشته باشیم.
در Yul دو اپکد (Opcode) مرتبط با ذخیره سازی وجود دارد: sload()
و sstore()
.
-
sload()
مقدار ذخیره شده در یک شیار مشخص را می خواند. -
sstore()
مقدار جدیدی را در یک شیار مشخص ذخیره می کند.
علاوه بر این، دو کلیدواژه مهم دیگر نیز در Yul وجود دارد: .slot
و .offset
-
.slot
موقعیت متغیر در بین شیارهای ذخیره سازی را بازمی گرداند. -
.offset
میزان جابجایی بایتی متغیر را نشان می دهد. (توضیح آن در بخش دوم ارائه خواهد شد)
کلیدواژه .slot
در قرارداد زیر، سه متغیر ذخیره سازی از نوع uint256
تعریف شده اند:
1 2 3 4 5 |
contract StorageManipulation { uint256 x; uint256 y; uint256 z; } |
با توجه به ترتیب تعریف، می توان نتیجه گرفت که متغیرهای x
، y
و z
به ترتیب مقادیر خود را در شیار ۰، شیار ۱ و شیار ۲ ذخیره می کنند.
برای اثبات این موضوع، می توانیم با استفاده از کلیدواژه .slot
به موقعیت هر متغیر در فضای ذخیره سازی دسترسی پیدا کنیم. این کلیدواژه موقعیت دقیق شیار ذخیره سازی مربوط به هر متغیر را نمایش می دهد.
کلیدواژه .slot
به ما نشان می دهد که هر متغیر مقدار خود را در کدام شیار ذخیره سازی نگه می دارد.
برای مثال، اگر بخواهیم موقعیت شیار ذخیره سازی متغیر x
را بررسی کنیم، در زبان اسمبلی کافی است .slot
را به نام متغیر اضافه کنیم: x.slot
نمونه کد زیر این کار را انجام می دهد:
1 2 3 4 5 |
function getSlotX() external pure returns (uint256 slot) { assembly {// yul slot := x.slot // returns slot location of x } } |
x.slot
مقدار ۰ را برمی گرداند؛ یعنی متغیر x
مقدار خود را در شیار شماره ۰ ذخیره می کند.
دستور y.slot
مقدار ۱ را برمی گرداند، که نشان می دهد متغیر y
مقدار خود را در شیار ذخیره سازی شماره ۱ نگهداری می کند.
دستور z.slot
مقدار ۲ را برمی گرداند، که نشان می دهد متغیر z
مقدار خود را در شیار ذخیره سازی شماره ۲ نگهداری می کند.
خواندن مستقیم مقدار متغیرها از شیار ذخیره سازی: دستور sload()
زبان Yul این امکان را فراهم می کند که مستقیماً مقدار ذخیره شده در یک شیار ذخیره سازی را بخوانیم. برای این منظور از اپکد sload(slot)
استفاده می شود. این دستور یک ورودی می گیرد: شناسه شیار ذخیره سازی (slot) و خروجی آن تمام ۲۵۶ بیت داده ای است که در آن شیار قرار دارد. یادگیری این مفاهیم بهویژه برای افرادی که به دنبال درک عمیقتر فضای حافظه در آموزش برنامه نویسی قراردادهای هوشمند هستند، ضروری است.
شناسه شیار می تواند یکی از موارد زیر باشد:
-
کلیدواژه
.slot
مانندsload(x.slot)
-
یک متغیر محلی که مقدار اسلات را نگهداری می کند مانند
sload(localvar)
-
یک عدد ثابت مانند
sload(1)
در ادامه چند مثال از نحوه استفاده از دستور sload()
آورده شده است:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
contract ReadStorage { uint256 public x = 11; uint256 public y = 22; uint256 public z = 33; function readSlotX() external view returns (uint256 value) { assembly { value := sload(x.slot) } } function sloadOpcode(uint256 slotNumber) external view returns (uint256 value) { assembly { value := sload(slotNumber) } } } |
readSlotX()
مقدار ۲۵۶ بیتی ذخیره شده در x.slot
(یعنی شیار شماره ۰) را بازیابی می کند و آن را در قالب uint256
بازمی گرداند. این مقدار در این مثال برابر با ۱۱ است.
1 2 3 4 5 |
function readSlotX() external view returns (uint256 value) { assembly { value := sload(x.slot) } } |
- دستور
sload(0)
مقدار موجود در شیار شماره ۰ را می خواند؛ در اینجا، این مقدار برابر با ۱۱ است. - دستور
sload(1)
مقدار موجود در شیار شماره ۱ را می خواند، که برابر با ۲۲ است. - دستور
sload(2)
مقدار موجود در شیار شماره ۲ را می خواند، که برابر با ۳۳ است. - اما دستور
sload(3)
به شیار شماره ۳ دسترسی پیدا می کند که هنوز مقداری در آن ذخیره نشده و همچنان در حالت پیشفرض قرار دارد؛ یعنی تمام بیت های آن صفر هستند.
انیمیشن زیر نحوه عملکرد اپکد sload
را به صورت بصری نمایش می دهد:
تابع sloadOpcode(uint256 slotNumber)
به ما این امکان را می دهد که مقدار ذخیره شده در هر شیار دلخواه را بخوانیم. این مقدار در قالب uint256
بازگردانده می شود:
1 2 3 4 5 6 7 8 9 |
function sloadOpcode(uint256 slotNumber) external view returns (uint256 value) { assembly { value := sload(slotNumber) } } |
نکته مهم اینجاست که sload()
هیچ بررسیای روی نوع داده انجام نمی دهد.
در زبان سالیدیتی، اگر تلاش کنیم یک متغیر از نوع uint256
را به صورت bool
بازگردانیم، با خطای نوع مواجه خواهیم شد:
1 2 3 4 |
function returnX() public view returns (bool ret) { // type error ret = x; } |
1 2 3 4 5 6 |
function readSlotX_bool() external view returns(bool value) { // return in bool assembly{ value:= sload(x.slot) // will compile } } |
در بخش دوم بهطور مفصل توضیح می دهیم که چرا این رفتار ممکن است. بهطور خلاصه، در محیط اسمبلی تمام متغیرها در اصل به صورت bytes32
در نظر گرفته می شوند. اما زمانی که از فضای اسمبلی خارج می شویم، زبان سالیدیتی نوع اصلی متغیر را بازیابی می کند و مقدار را طبق آن قالببندی می کند.
بر همین اساس می توانیم از این ویژگی برای بررسی مقدار شیارهای ذخیره سازی در قالب bytes32
استفاده کنیم.
1 2 3 4 5 6 7 8 9 |
contract ReadSlotsRaw { uint256 public x = 20; function readSlotX_bool() external view returns (bytes32 value) { assembly { value := sload(x.slot) // will compile } } } |

نوشتن در شیار ذخیره سازی با استفاده از اپکد sstore()
زبان اسمبلی Yul امکان نوشتن مستقیم در شیارهای ذخیره سازی را از طریق اپکد sstore()
فراهم می کند.
دستور sstore(slot, value)
یک مقدار ۳۲ بایتی را مستقیماً در یک شیار ذخیره سازی ذخیره می کند. این دستور دو ورودی دارد:
-
slot
: شماره شیار ذخیره سازی هدف که قرار است مقدار جدید در آن نوشته شود. -
value
: مقداری که باید در شیار مشخص شده ذخیره شود. اگر این مقدار کمتر از ۳۲ بایت باشد، از سمت چپ با صفر پر می شود (left padded with zeroes).
دستور sstore(slot, value)
کل محتوای شیار ذخیره سازی را با مقدار جدید بازنویسی می کند.
قرارداد زیر نحوه استفاده از sstore()
را نمایش می دهد؛ در این مثال، از این دستور برای تغییر مقدار متغیرهای x
و y
استفاده می کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
contract WriteStorage { uint256 public x = 11; uint256 public y = 22; address public owner; constructor(address _owner) { owner = _owner; } // sstore() function function sstore_x(uint256 newval) public { assembly { sstore(x.slot, newval) } } // normal function function set_x(uint256 newval) public { x = newval; } } |
تابع sstore_x(newVal)
به صورت مستقیم مقدار ذخیره شده در شیاری را بهروزرسانی می کند که متغیر x
به آن اشاره دارد. این فرآیند عملاً مقدار x
را تغییر می دهد.
برای نمونه، زمانی که اپکد sstore_x(88)
فراخوانی می شود، مقدار جدید ۸۸ در شیار ذخیره سازی مربوط به x
نوشته می شود و مقدار قبلی بهطور کامل جایگزین می گردد.
انیمیشن زیر نشان می دهد که در پشت صحنه هنگام اجرای این دستور چه اتفاقی می افتد:
هر دو تابع sstore_x(newVal)
و set_x()
دقیقاً یک عملکرد دارند؛ هر دو مقدار متغیر x
را با مقدار جدید جایگزین می کنند.
تابع زیر با نام sstoreArbitrarySlot(slot, newVal)
قادر است مقدار هر شیار ذخیره سازی دلخواه را تغییر دهد. به همین دلیل، اکیداً توصیه می شود هرگز از آن در محیط عملیاتی (production) استفاده نشود:
1 2 3 4 5 |
function sstoreArbitrarySlot(uint256 slot, uint256 newVal) public { assembly { sstore(slot, newVal) } } |
sstoreArbitrarySlot(1, 48)
فراخوانی کنیم، مقدار متغیر y
از ۲۲ به ۴۸ تغییر خواهد کرد؛ زیرا متغیر y
مقدار خود را در شیار ذخیره سازی شماره ۱ نگه می دارد. این فراخوانی، مقدار موجود در شیار ۱ را بازنویسی می کند.
اپکد sstore()
هیچ بررسیای روی نوع داده انجام نمی دهد. به طور معمول، اگر تلاش کنیم یک مقدار از نوع uint256
را به متغیر address
اختصاص دهیم، با خطای نوع مواجه می شویم و قرارداد کامپایل نمی شود:
1 2 3 4 5 |
address public owner; function TypeError(uint256 value) external { owner = value; // ERROR: Type uint256 is not implicitly convertible to expected type address. } |
خطا: نوع uint256 به صورت ضمنی قابل تبدیل به نوع مورد انتظار address نیست.
اما اگر همین مقدار را با استفاده از sstore()
به شیار مربوط به owner
اختصاص دهیم، هیچ خطایی ایجاد نمی شود؛ زیرا این اپکد بررسی نوع انجام نمی دهد:
1 2 3 4 5 6 7 8 9 |
contract WriteStorage { address public owner; function sstoreOpcode(uint256 value) public { assembly { sstore(owner.slot, value) } } } |
دستکاری متغیرهای فشرده سازی شده در حافظه با استفاده از Yul – بخش دوم
اپکدهای sstore
و sload
همواره با طول ثابت ۳۲ بایت کار می کنند. این رفتار زمانی مفید است که با متغیرهایی از نوع uint256
سروکار داریم؛ زیرا کل ۳۲ بایت خوانده یا نوشتهشده دقیقاً با ساختار دادهای این نوع مطابقت دارد.
اما زمانی که چند متغیر در یک شیار ذخیره سازی فشردهسازی (packed) شده اند، شرایط پیچیدهتر می شود. در این حالت، توالی بایتی هر متغیر تنها بخشی از فضای ۳۲ بایتی شیار را اشغال می کند و در زبان اسمبلی Yul، هیچ اپکدی وجود ندارد که بتواند بهطور مستقیم فقط همان بخش خاص را بخواند یا تغییر دهد.
در بخش دوم، یاد می گیریم که چگونه با استفاده از تکنیکهای بیتمانیتورینگ (bit manipulation) و ماسکگذاری بیتی (bit masking)، متغیرهای فشردهشده در شیارهای ذخیره سازی را بهطور دقیق مدیریت کنیم.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۵ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++C
- ADO.NET
- Adobe Flash
- Ajax
- AngularJS
- apache
- ARM
- Asp.Net
- ASP.NET MVC
- AVR
- Bootstrap
- CCNA
- CCNP
- CMD
- CSS
- Dreameaver
- EntityFramework
- HTML
- IOS
- jquery
- Linq
- Mysql
- Oracle
- PHP
- PHPMyAdmin
- Rational Rose
- silver light
- SQL Server
- Stimulsoft Reports
- Telerik
- UML
- VB.NET&VB6
- WPF
- Xml
- آموزش های پروژه محور
- اتوکد
- الگوریتم تقریبی
- امنیت
- اندروید
- اندروید استودیو
- بک ترک
- بیسیک فور اندروید
- پایتون
- جاوا
- جاوا اسکریپت
- جوملا
- دلفی
- دوره آموزش Go
- دوره های رایگان پیشنهادی
- زامارین
- سئو
- ساخت CMS
- سی شارپ
- شبکه و مجازی سازی
- طراحی الگوریتم
- طراحی بازی
- طراحی وب
- فتوشاپ
- فریم ورک codeigniter
- فلاتر
- کانستراکت
- کریستال ریپورت
- لاراول
- معماری کامپیوتر
- مهندسی اینترنت
- هوش مصنوعی
- یونیتی
- کتاب های آموزشی
- Android
- ASP.NET
- AVR
- LINQ
- php
- Workflow
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس