यह लेख Ethereum स्मार्ट कॉन्ट्रैक्ट्स के स्टोरेज आर्किटेक्चर का परीक्षण करता है। यह समझाता है कि वेरिएबल्स को EVM स्टोरेज में कैसे रखा जाता है और लो-लेवल असेंबली (Yul) का उपयोग करके स्टोरेज स्लॉट्स में कैसे पढ़ा और लिखा जाता है।
यह जानकारी यह समझने के लिए एक पूर्व शर्त है कि Solidity में प्रॉक्सी कैसे काम करते हैं और स्मार्ट कॉन्ट्रैक्ट्स को गैस ऑप्टिमाइज़ कैसे किया जाता है।
लेखक
यह लेख RareSkills के एक रिसर्च इंटर्न Aymeric Taylor (LinkedIn, Twitter) द्वारा सह-लिखित है।
स्मार्ट कॉन्ट्रैक्ट स्टोरेज आर्किटेक्चर
एक स्मार्ट कॉन्ट्रैक्ट में वेरिएबल्स अपनी वैल्यू को दो मुख्य स्थानों पर स्टोर करते हैं: स्टोरेज (storage) और बाइटकोड (bytecode)।

बाइटकोड (Bytecode)
bytecode अपरिवर्तनीय (immutable) जानकारी स्टोर करता है। इनमें immutable और constant वेरिएबल प्रकारों की वैल्यू शामिल हैं,
contract ImmutableVariables{
uint256 constant myConstant = 100;
uint256 immutable myImmutable;
}
साथ ही संकलित (compiled) सोर्स कोड भी (सोर्स कोड नीचे दिया गया पूरा टेक्स्ट है)।
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() फ़ंक्शन में, uint256 x = 20 जैसे हार्डकोडेड लोकल वेरिएबल की वैल्यू भी बाइटकोड में स्टोर की जाएगी।
चूंकि यह लेख स्टोरेज पहलू को कवर करने पर केंद्रित है, इसलिए हम बाइटकोड पर विस्तार से चर्चा नहीं करेंगे।
स्टोरेज (Storage)
स्टोरेज परिवर्तनशील (mutable) जानकारी रखता है। जो वेरिएबल्स अपनी वैल्यू को स्टोरेज में स्टोर करते हैं, उन्हें स्टेट वेरिएबल्स (state variables) या स्टोरेज वेरिएबल्स (storage variables) कहा जाता है।

उनकी वैल्यू स्टोरेज में अनिश्चित काल तक बनी रहती है, जब तक कि आगे के ट्रांज़ैक्शन उन्हें बदल न दें या कॉन्ट्रैक्ट खुद नष्ट (self-destruct) न हो जाए।
स्टोरेज वेरिएबल्स वे सभी प्रकार के वेरिएबल्स होते हैं जो कॉन्ट्रैक्ट के ग्लोबल स्कोप के भीतर घोषित (declare) किए जाते हैं (immutable और constant वेरिएबल्स को छोड़कर)।
contract StorageVariables{
uint256 x;
address owner;
mapping(address => uint256) balance;
// and more...
}
जब हम किसी स्टोरेज वेरिएबल के साथ इंटरैक्ट करते हैं, तो वास्तव में बैकग्राउंड में, हम स्टोरेज से पढ़ और लिख रहे होते हैं, विशेष रूप से उस स्टोरेज स्लॉट (storage slot) पर जहां वेरिएबल अपनी वैल्यू रखता है।
स्टोरेज स्लॉट्स (Storage slots)
एक स्मार्ट कॉन्ट्रैक्ट का स्टोरेज कई स्टोरेज स्लॉट्स में व्यवस्थित होता है। प्रत्येक स्लॉट की एक निश्चित स्टोरेज क्षमता 256 बिट्स या 32 बाइट्स () होती है।

स्टोरेज स्लॉट्स को से तक अनुक्रमित (indexed) किया जाता है। ये नंबर व्यक्तिगत स्लॉट्स का पता लगाने के लिए एक विशिष्ट पहचानकर्ता (unique identifier) के रूप में कार्य करते हैं।
Solidity कंपाइलर स्टोरेज वेरिएबल्स को कॉन्ट्रैक्ट के भीतर उनके डिक्लेरेशन (घोषणा) क्रम के आधार पर अनुक्रमिक (sequential) और निर्धारित तरीके से स्टोरेज स्पेस आवंटित करता है।
नीचे दिए गए कॉन्ट्रैक्ट पर विचार करें, इसमें दो स्टोरेज वेरिएबल्स हैं: uint256 x और uint256 y।
contract StorageVariables {
uint256 public x; // first declared storage variable
uint256 public y; // second declared storage variable
}
चूंकि x को पहले घोषित किया गया है और y को बाद में घोषित किया गया है, इसलिए x को पहला स्टोरेज स्लॉट, slot 0, और y को दूसरा स्टोरेज स्लॉट, slot 1 आवंटित किया जाता है। इस प्रकार, x अपनी वैल्यू को slot 0 पर रखेगा, और y slot 1 पर।

जब क्वेरी की जाती है, तो x और y लगातार अपने संबंधित स्टोरेज स्लॉट्स में स्टोर की गई वैल्यू को ही पढ़ेंगे। एक बार जब कॉन्ट्रैक्ट ब्लॉकचेन पर डिप्लॉय हो जाता है, तो कोई वेरिएबल अपना स्टोरेज स्लॉट नहीं बदल सकता।
यदि x और y की वैल्यू इनिशियलाइज़ नहीं की गई है, तो यह डिफ़ॉल्ट रूप से शून्य (zero) हो जाती है। सभी स्टोरेज वेरिएबल्स तब तक डिफ़ॉल्ट रूप से शून्य रहते हैं जब तक कि उन्हें स्पष्ट रूप से सेट नहीं किया जाता है।
contract StorageVariables {
uint256 public x; // Uninitialized storage variable
function return_uninitialized_X() public view returns (uint256) {
return x; // returns zero
}
}
x की वैल्यू को 20 पर सेट करने के लिए, हम set_x(20) फ़ंक्शन को कॉल कर सकते हैं।
function set_x(uint256 value) external {
x = value;
}
यह ट्रांज़ैक्शन slot 0 में स्टेट चेंज (state change) ट्रिगर करता है, इसकी स्टेट को 0 से 20 में अपडेट करता है।

अनिवार्य रूप से, एक स्मार्ट कॉन्ट्रैक्ट में किए गए सभी स्टेट बदलाव इन स्टोरेज स्लॉट्स के भीतर होने वाले बदलावों के अनुरूप होते हैं।
स्टोरेज स्लॉट्स के अंदर: 256-बिट डेटा
प्रत्येक स्टोरेज स्लॉट डेटा को 256-बिट प्रारूप में स्टोर करता है; यह एक स्टोरेज वेरिएबल की वैल्यू का बिट प्रतिनिधित्व (bit representation) स्टोर करता है।
हमारे पिछले उदाहरण में, uint256 x अपनी वैल्यू slot 0 पर स्टोर करता है। एक uint256 वेरिएबल का आकार 256 बिट/32 बाइट्स होता है, इसलिए यह अपनी वैल्यू को स्टोर करने के लिए slot 0 के भीतर 256 बिट स्टोरेज स्पेस का उपयोग करेगा।
set_x(20)को कॉल करने से पहले, slot 0 अपनी डिफ़ॉल्ट स्टेट (सभी शून्य) पर था

ऊपर दी गई छवि में देखे गए सभी हरे रंग के शून्य उन बिट्स से मेल खाते हैं जिनका उपयोग x की वैल्यू को स्टोर करने के लिए किया जाता है।
set_x(20)को कॉल करने के बाद, slot 0 की स्टेट को uint256 20 के बिट प्रतिनिधित्व में बदल दिया गया था।

रॉ (raw) 256 बिट प्रारूप में स्टोरेज स्लॉट की सामग्री को पढ़ना इंसानों के लिए थोड़ा मुश्किल है, इसलिए, Solidity डेवलपर्स आमतौर पर इसे हेक्साडेसिमल (hexadecimal) प्रारूप में पढ़ते हैं।
रॉ 256 बिट (Raw 256 bit): 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
हेक्साडेसिमल प्रारूप (Hexadecimal format):
0x0000000000000000000000000000000000000000000000000000000000000014
256 बिट के 1 और 0 को केवल 64 हेक्साडेसिमल नंबरों तक कम किया जा सकता है। 1 हेक्साडेसिमल कैरेक्टर 4 बिट्स का प्रतिनिधित्व करता है। 2 हेक्साडेसिमल कैरेक्टर 1 बाइट का प्रतिनिधित्व करते हैं। हेक्साडेसिमल 0x14 समान रूप से दशमलव (decimal) नंबर 20 में अनुवादित होता है। 0x14 (hex) = 10100 (binary) = 20 (decimal)। Binary-to-hex converter.
हम एक आगामी अनुभाग में असेंबली का उपयोग करके हेक्साडेसिमल प्रारूप में या bytes32 प्रकार में स्टोरेज स्लॉट की वैल्यू को आउटपुट करने का तरीका प्रदर्शित करेंगे।
प्रिमिटिव और कॉम्प्लेक्स डेटाटाइप (Primitive and Complex datatype)
इस पूरे लेख में, हमारे उदाहरण केवल प्रिमिटिव डेटाटाइप्स जैसे अनसाइंड इंटिजर्स (uint), इंटिजर्स (int), एड्रेस (address), और बूलियंस (bool) के इर्द-गिर्द घूमेंगे।
contract PrimitiveTypes {
uint256 a;
int256 b;
address owner;
bool isTrue;
}
ये वेरिएबल्स अधिकतम एक स्टोरेज स्लॉट पर कब्जा करते हैं।
कॉम्प्लेक्स डेटाटाइप्स जैसे स्ट्रक्ट्स (struct{}), एरे (array[]), मैपिंग्स (mapping(address => uint256)), स्ट्रिंग्स (string), और बाइट्स (bytes32) का स्टोरेज स्लॉट एलोकेशन अधिक जटिल होता है। इन पर विस्तार से चर्चा करने के लिए एक अलग लेख की आवश्यकता है।
स्टोरेज पैकिंग (Storage Packing)
अब तक, हमने सुविधाजनक रूप से uint256 वेरिएबल्स को देखा है, जो एक स्टोरेज स्लॉट के पूरे 32 बाइट्स में फैले होते हैं। अन्य प्रिमिटिव डेटा प्रकार, जैसे uint8, uint32, uint128, address, और bool, आकार में छोटे होते हैं और कम स्टोरेज स्पेस का उपयोग करते हैं। उन्हें एक ही स्टोरेज स्लॉट के भीतर एक साथ पैक किया जा सकता है।
एक अतिरिक्त जानकारी के रूप में, 8 का कोई भी गुणक (multiple) 256 तक एक वैध uint है, और bytes1, bytes2, सभी निश्चित बाइट आकार bytes1, bytes2, … bytes32 तक सभी वैध डेटाटाइप्स हैं।
नीचे दी गई तालिका कुछ प्रिमिटिव डेटा प्रकारों के स्टोरेज आकार को दर्शाती है।
| Type | Size |
|---|---|
bool |
1 byte |
uint8 |
1 byte |
uint32 |
4 bytes |
uint128 |
16 bytes |
address |
20 bytes |
uint256 |
32 bytes |
उदाहरण के लिए, address प्रकार के एक स्टोरेज वेरिएबल को अपनी वैल्यू को स्टोर करने के लिए 20 बाइट्स स्टोरेज स्पेस की आवश्यकता होगी, जैसा कि ऊपर दी गई तालिका में दर्शाया गया है।
contract AddressVariable{
address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
}
ऊपर दिए गए कॉन्ट्रैक्ट में, owner अपनी वैल्यू को स्टोर करने के लिए slot 0 में उपलब्ध 32 बाइट्स में से 20 बाइट्स का उपयोग करेगा।

Solidity स्टोरेज स्लॉट्स में वेरिएबल्स को लीस्ट सिग्निफिकेंट बाइट (सबसे दाएं बाइट) से शुरू करके पैक करता है और बाईं ओर बढ़ता है।
हम स्लॉट के bytes32 प्रतिनिधित्व को पढ़कर इसे सत्यापित कर सकते हैं:

जैसा कि ऊपर दिए गए आरेख में दिखाया गया है, owner की वैल्यू, 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4, सबसे दाएं बाइट या लीस्ट सिग्निफिकेंट बाइट से शुरू होकर स्टोर की जाती है। slot 0 में बचे हुए 12 बाइट्स अप्रयुक्त स्टोरेज स्पेस होंगे जिस पर कोई अन्य वेरिएबल कब्जा कर सकता है।
जब अनुक्रम में घोषित किया जाता है, तो छोटे आकार के वेरिएबल्स एक ही स्टोरेज स्लॉट में रहते हैं यदि उनका कुल आकार 256 बिट्स या 32 बाइट्स से कम है।
मान लीजिए कि हमने bool (1 बाइट) और uint32 (4 बाइट्स) प्रकार का दूसरा और तीसरा स्टोरेज वेरिएबल घोषित किया, तो उनकी वैल्यू owner के समान स्टोरेज स्लॉट, slot 0 में, अप्रयुक्त स्टोरेज स्पेस पर स्टोर की जाएगी।
contract AddressVariable {
address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
// new
bool Boolean = true;
uint32 thirdvar = 5_000_000;
}
Boolean, जो दूसरा घोषित स्टोरेज वेरिएबल है, अपनी वैल्यू को owner के बाइट अनुक्रम के बाईं ओर पहले बाइट पर, या अप्रयुक्त स्टोरेज स्पेस के लीस्ट सिग्निफिकेंट बाइट पर स्टोर करेगा। याद रखें, Solidity वेरिएबल्स को दाईं से बाईं ओर पैक करता है।

uint32 thirdVar, जो तीसरा स्टोरेज वेरिएबल है, अपनी वैल्यू को Boolean के बाइट अनुक्रम के बाईं ओर स्टोर करेगा।

यदि हम चौथा स्टोरेज वेरिएबल, address admin पेश करते हैं, तो इसकी वैल्यू अगले स्टोरेज स्लॉट, slot 1 में स्टोर की जाएगी।
contract AddressVariable {
address owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
bool Boolean = true;
uint32 thirdVar = 5_000_000;
// new
address admin = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
}

ऐसा इसलिए है क्योंकि admin की वैल्यू पूरी तरह से slot 0 के अप्रयुक्त स्टोरेज स्पेस में फिट नहीं हो सकती। 7 बाइट्स का स्टोरेज स्पेस बचा है लेकिन 20 बाइट्स के लगातार स्टोरेज स्पेस की आवश्यकता है। इसलिए, admin के डेटा को slot 0 और slot 1 (slot 0 में 7 बाइट्स और slot 1 में 13 बाइट्स) के बीच विभाजित करने के बजाय, admin की वैल्यू को एक नए स्टोरेज स्लॉट, slot 1 में स्टोर किया जाएगा।
यदि किसी वेरिएबल की वैल्यू वर्तमान स्टोरेज स्लॉट के बचे हुए स्पेस में पूरी तरह से फिट नहीं हो सकती है, तो इसे अगले उपलब्ध स्लॉट में स्टोर किया जाएगा।
छोटे वेरिएबल्स को एक साथ घोषित करें (Declare smaller variables together)
uint16 public a;
uint256 public x; // uint256 in the middle
uint32 public b;
इस व्यवस्था में, uint16 a और uint32 b को एक साथ पैक नहीं किया जाएगा।
इसके बजाय, a को slot 0 पर, x को slot 1 पर, और b को slot 2 पर स्टोर किया जाएगा, जिससे तीन स्टोरेज स्लॉट्स का उपयोग होगा। स्टोरेज स्लॉट आवंटन नीचे दिए गए आरेख जैसा दिखेगा:

एक बेहतर तरीका यह है कि छोटे डेटाटाइप्स को एक साथ पैक करने की अनुमति देने के लिए घोषणाओं (declarations) को फिर से व्यवस्थित किया जाए।
uint256 public x;
// packed together
uint16 public a;
uint32 public b;
यह कॉन्फ़िगरेशन a और b को एक स्टोरेज स्लॉट साझा करने की अनुमति देता है, जिससे स्टोरेज स्पेस ऑप्टिमाइज़ होता है।

अब जब हम समझ गए हैं कि प्रिमिटिव वेरिएबल्स को स्टोरेज में कैसे रखा जाता है, तो इसके पीछे के सिद्धांत को समझने के बाद, हम अंततः Yul का उपयोग करके उन्हें असेंबली में मैनिपुलेट (manipulate) करना सीखने के लिए तैयार हैं।
असेंबली (Yul) में स्टोरेज स्लॉट मैनिपुलेशन
लो-लेवल असेंबली (Yul) स्टोरेज से संबंधित ऑपरेशन्स करने में उच्च स्तर की स्वतंत्रता देती है। यह हमें सीधे व्यक्तिगत स्टोरेज स्लॉट्स से पढ़ने और लिखने तथा स्टोरेज वेरिएबल के गुणों (properties) तक पहुंचने की अनुमति देता है।
Yul में स्टोरेज से संबंधित दो ओपकोड (opcodes) हैं: sload() और sstore()।
sload()एक विशिष्ट स्टोरेज स्लॉट में स्टोर की गई वैल्यू को पढ़ता है।sstore()एक विशिष्ट स्टोरेज स्लॉट की वैल्यू को एक नई वैल्यू के साथ अपडेट करता है।
दो अन्य महत्वपूर्ण Yul कीवर्ड्स .slot और .offset हैं।
.slotस्टोरेज स्लॉट्स के भीतर लोकेशन देता है।.offsetवेरिएबल का बाइट ऑफ़सेट देता है। (इस पर भाग 2 में चर्चा की जाएगी)
.slot कीवर्ड
नीचे दिए गए कॉन्ट्रैक्ट में तीन uint256 स्टोरेज वेरिएबल्स हैं।
contract StorageManipulation {
uint256 x;
uint256 y;
uint256 z;
}
आपको यह समझ में आ जाना चाहिए कि x, y और z अपनी वैल्यू को क्रमशः slot 0, slot 1 और slot 2 में स्टोर करते हैं। हम .slot कीवर्ड का उपयोग करके स्टोरेज वेरिएबल की प्रॉपर्टी तक पहुंचकर इसे साबित कर सकते हैं।
.slot हमें बताता है कि कोई वेरिएबल अपनी वैल्यू किस स्टोरेज स्लॉट पर रखता है।
उदाहरण के लिए, x के स्टोरेज स्लॉट को क्वेरी करने के लिए, वेरिएबल नाम के आगे .slot जोड़ें: असेंबली में x.slot।
function getSlotX() external pure returns (uint256 slot) {
assembly {// yul
slot := x.slot // returns slot location of x
}
}
x.slot 0 की वैल्यू देता है, जो उस स्टोरेज स्लॉट से मेल खाता है जहां x अपनी स्टेट स्टोर करता है—slot 0।

y.slot 1 देगा, जो y के स्टोरेज स्लॉट से मेल खाता है—slot 1।

z.slot 2 देगा, जो z के स्टोरेज स्लॉट से मेल खाता है—slot 1।

सीधे उनके स्टोरेज स्लॉट से वेरिएबल्स की वैल्यू पढ़ना: sload()
Yul हमें व्यक्तिगत स्टोरेज स्लॉट्स द्वारा स्टोर की गई वैल्यू को पढ़ने की अनुमति देता है। इस उद्देश्य के लिए sload(slot) ओपकोड का उपयोग किया जाता है। इसके लिए एक इनपुट की आवश्यकता होती है, slot, जो स्टोरेज स्लॉट पहचानकर्ता है और यह निर्दिष्ट स्लॉट स्थान पर स्टोर पूरे 256 बिट डेटा को रिटर्न करता है।
स्लॉट आइडेंटिफ़ायर या तो .slot कीवर्ड (sload(x.slot)), एक लोकल वेरिएबल (sload(localvar)) या एक हार्डकोडेड नंबर (sload(1)) हो सकता है।
sload() ओपकोड का उपयोग करने के कुछ उदाहरण यहां दिए गए हैं:
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 (slot 0) में स्टोर 256 बिट डेटा प्राप्त करता है और इसे uint256 प्रारूप में रिटर्न करता है, जो 11 के बराबर है।
function readSlotX() external view returns (uint256 value) {
assembly {
value := sload(x.slot)
}
}
sload(0)slot 0 से पढ़ता है, जो 11 की वैल्यू स्टोर करता है।sload(1)slot 1 से पढ़ता है, जो 22 की वैल्यू स्टोर करता है।sload(2)slot 2 से पढ़ता है, जो 33 की वैल्यू स्टोर करता है।sload(3)slot 3 से पढ़ता है, जो कुछ भी स्टोर नहीं करता है, यह अभी भी अपनी डिफ़ॉल्ट स्टेट में है।
नीचे दिया गया एनीमेशन यह विज़ुअलाइज़ करता है कि sload ओपकोड कैसे काम करता है।
sloadOpcode(slotNumber) फ़ंक्शन हमें किसी भी मनमाने (arbitrary) स्टोरेज स्लॉट की वैल्यू पढ़ने की अनुमति देता है। फिर यह वैल्यू को uint256 प्रारूप में रिटर्न करता है।
function sloadOpcode(uint256 slotNumber)
external
view
returns (uint256 value)
{
assembly {
value := sload(slotNumber)
}
}
विशेष रूप से, sload() कोई टाइप चेक नहीं करता है।
Solidity में, हम bool प्रारूप में एक uint256 वेरिएबल रिटर्न नहीं कर सकते क्योंकि इसमें टाइप एरर आ जाएगी।
function returnX() public view returns (bool ret) {
// type error
ret = x;
}
लेकिन अगर ऑपरेशन्स का यही सेट Yul में किया जाता है, तो कोड अभी भी कंपाइल हो जाएगा।
function readSlotX_bool() external view returns(bool value) {
// return in bool
assembly{
value:= sload(x.slot) // will compile
}
}
हम भाग 2 में विस्तार से चर्चा करेंगे कि यह क्यों संभव है। आपको एक मोटा आइडिया देने के लिए, असेंबली में, प्रत्येक वेरिएबल को अनिवार्य रूप से एक bytes32 प्रकार के रूप में माना जाता है। असेंबली स्कोप के बाहर, वेरिएबल अपना मूल प्रकार फिर से शुरू करेगा और उसके अनुसार डेटा को फॉर्मेट करेगा।
परिणामस्वरूप, हम इस प्रॉपर्टी का उपयोग स्टोरेज स्लॉट की वैल्यू को bytes32 प्रारूप में जांचने के लिए कर सकते हैं।
contract ReadSlotsRaw {
uint256 public x = 20;
function readSlotX_bool() external view returns (bytes32 value) {
assembly {
value := sload(x.slot) // will compile
}
}
}

sstore() ओपकोड का उपयोग करके स्टोरेज स्लॉट में लिखना
Yul हमें sstore() ओपकोड का उपयोग करके स्टोरेज स्लॉट की वैल्यू को संशोधित (modify) करने के लिए सीधे एक्सेस देता है।
sstore(slot, value) एक 32-बाइट लंबी वैल्यू को सीधे एक स्टोरेज स्लॉट में स्टोर करता है। यह ओपकोड दो पैरामीटर लेता है, slot और value:
slot: यह वह लक्षित स्टोरेज स्लॉट है जिस पर हम लिख रहे हैं।value: निर्दिष्ट स्टोरेज स्लॉट पर स्टोर की जाने वाली 32-बाइट वैल्यू। यदि वैल्यू 32 बाइट्स से कम है, तो इसे बाईं ओर शून्य से पैड (left padded) किया जाएगा।
sstore(slot, value) पूरे स्टोरेज स्लॉट को एक नई वैल्यू से ओवरराइट कर देता है।
नीचे दिया गया कॉन्ट्रैक्ट यह प्रदर्शित करता है कि sstore() का उपयोग कैसे करें; हम इसका उपयोग x और y की वैल्यू को बदलने के लिए करते हैं:
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 संदर्भ (reference) देता है, जो प्रभावी रूप से x की वैल्यू को बदल देता है। नीचे दिया गया एनीमेशन यह विज़ुअलाइज़ करता है कि जब हम ओपकोड sstore_x(88) कॉल करते हैं तो क्या होता है।
sstore_x(newVal) और set_x() दोनों समान कार्य करते हैं: वे x की वैल्यू को एक नई वैल्यू के साथ अपडेट करते हैं।
नीचे दिया गया फ़ंक्शन, sstoreArbitrarySlot(slot, newVal), किसी भी स्टोरेज स्लॉट की वैल्यू बदलने में सक्षम है, इसलिए, इसे कभी भी प्रोडक्शन में न डालने की सलाह दी जाती है।
function sstoreArbitrarySlot(uint256 slot, uint256 newVal) public {
assembly {
sstore(slot, newVal)
}
}
sstoreArbitrarySlot(1 , 48) को कॉल करने से y की वैल्यू 22 से बदलकर 48 हो जाएगी। चूंकि y अपनी वैल्यू स्टोरेज slot 1 पर रखता है, यह slot 1 में 22 की वैल्यू को ओवरराइड करता है और इसे 48 में बदल देता है।
sstore() भी टाइप चेक नहीं करता है।
आम तौर पर, जब हम uint256 प्रकार में address प्रकार असाइन करने का प्रयास करते हैं, तो यह टाइप एरर रिटर्न करेगा और कॉन्ट्रैक्ट कंपाइल नहीं होगा:
address public owner;
function TypeError(uint256 value) external {
owner = value; // ERROR: Type uint256 is not implicitly convertible to expected type address.
}
ERROR: Type uint256 is not implicitly convertible to expected type address.
यह त्रुटि sstore() के साथ ट्रिगर नहीं होगी क्योंकि यह टाइप चेक नहीं करता है।
contract WriteStorage {
address public owner;
function sstoreOpcode(uint256 value) public {
assembly {
sstore(owner.slot, value)
}
}
}
Yul भाग 2 में स्टोरेज पैक्ड वेरिएबल्स को मैनिपुलेट करना
sstore और sload 32 बाइट्स की लंबाई पर काम करते हैं। uint256 प्रकार से निपटते समय यह सुविधाजनक है क्योंकि पढ़े या लिखे गए पूरे 32 बाइट्स सीधे uint256 वेरिएबल से मेल खाते हैं। हालांकि, एक ही स्टोरेज स्लॉट के भीतर पैक किए गए वेरिएबल्स से निपटते समय स्थिति अधिक जटिल हो जाती है। उनका बाइट अनुक्रम 32 बाइट्स के केवल एक हिस्से पर कब्जा करता है और असेंबली में, हमारे पास स्टोरेज में उनके बाइट अनुक्रम से सीधे पढ़ने या उसे संशोधित करने के लिए कोई ओपकोड नहीं है।
भाग 2 में, हम बिट-मैनिपुलेशन (bit-manipulation) और बिट-मास्किंग (bit-masking) तकनीकों का उपयोग करके Yul में स्टोरेज-पैक्ड वेरिएबल्स को मैनिपुलेट करने को कवर करेंगे।
मूल रूप से 15 जुलाई, 2024 को प्रकाशित