EIP 1967 उन जानकारियों को स्टोर करने के स्थान का एक स्टैण्डर्ड (standard) है जिनकी आवश्यकता proxy contracts को निष्पादित (execute) करने के लिए होती है। UUPS (Universal Upgradeable Proxy Standard) और Transparent Upgradeable Proxy Pattern दोनों इसका उपयोग करते हैं।
याद रखें: EIP 1967 केवल यह बताता है कि कुछ storage variables कहाँ जाते हैं और जब वे बदलते हैं तो कौन से logs एमिट (emit) होते हैं, इससे ज्यादा कुछ नहीं। यह यह नहीं बताता कि उन variables को कैसे अपडेट किया जाता है या उन्हें कौन मैनेज कर सकता है। यह लागू (implement) करने के लिए किसी भी public functions को परिभाषित नहीं करता है। इन variables को अपडेट करने का स्पेसिफिकेशन (specification) Transparent Upgradeable Proxy Pattern या UUPS spec में दिया गया है।
एक proxy को संचालित करने के लिए दो महत्वपूर्ण variables की आवश्यकता होती है: implementation address और admin। implementation address वह स्थान है जहाँ proxy कॉल्स को डेलिगेट (delegate) कर रहा है। एक अपग्रेड के दौरान, implementation address को अपग्रेडेड कॉन्ट्रैक्ट में बदल दिया जाता है; बदलाव करने के लिए केवल admin की कॉल्स को ही स्वीकार किया जाएगा।
पूर्व-आवश्यकताएं
यह लेख मानकर चलता है कि पाठक को इस बात का बुनियादी ज्ञान है कि proxies और delegatecall कैसे काम करते हैं, storage slots क्या हैं, function selectors क्या हैं, और proxy के संदर्भ में function selector clashing क्या होती है।
Proxy slots को डिज़ाइन करने का गलत तरीका
निम्नलिखित एक खराब proxy डिज़ाइन है:

सबसे पहले, इस बात की एक गैर-नगण्य (non-negligible) संभावना है कि changeAdmin() के लिए function selector, implementation के किसी function के साथ क्लैश (clash) करेगा। EIP 1967 spec यह नहीं बताता कि इसे कैसे संभाला जाए — इस समस्या को रोकने का उचित तरीका Transparent Upgradeable Proxy spec या UUPS spec में संभाला गया है। EIP 1967 का clashing function selectors से कोई लेना-देना नहीं है।
ERC 1967 जिस समस्या का समाधान करता है वह यह है कि implementation और admin variables के implementation contract में परिभाषित किसी storage variable के साथ क्लैश होने की बहुत अधिक संभावना है। विशेष रूप से, वे storage slots 0 और 1 का उपयोग करते हैं, जिनका उपयोग implementation contracts द्वारा किए जाने की पूरी संभावना होती है।
Collisions को रोकना
चूँकि admin और implementation addresses बदल सकते हैं, इसलिए इन्हें storage variables में होना चाहिए, ये immutable नहीं हो सकते। लेकिन इन्हें ऐसे storage slots में होना चाहिए जो implementation contract के storage variables के साथ टकराएं (collide) नहीं।
यहाँ मुख्य विचार यह है: संभावित storage slots का स्पेस (space) बहुत बड़ा है: 2**256 - 1।
यदि हम बेतरतीब (randomly) ढंग से कोई storage slot चुनते हैं, तो implementation contract के लिए उसी slot को चुनना मूल रूप से असंभव है। एक implementation contract द्वारा उसी slot को चुनने की संभावना लगभग एक hash function collision के समान ही है, इसलिए जोखिम अनिवार्य रूप से शून्य है।
Implementation और address के लिए storage slots
Implementation address को इस slot में स्टोर किया जाता है
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
Admin address को इस slot में स्टोर किया जाता है
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103
ये slots छद्म-यादृच्छिक रूप से (pseudorandomly) क्रमशः
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) और
bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) से प्राप्त (derive) किए गए थे।
डेसिमल (decimal) में, implementation और admin के लिए storage slot क्रमशः
24440054405305269366569402256811496959409073762505157381672968839269610695612
और
81955473079516046949633743016697847541294818689821282749996681496272635257091
हैं।
किसी भी कॉन्ट्रैक्ट में इतने सारे variables नहीं हो सकते, इसलिए storage variables के कारण टकराव (collision) नगण्य (negligible) है। Dynamic mappings और arrays, slot नंबर और key value का hash लेते हैं और इस प्रकार एक pseudorandom storage slot का उपयोग करते हैं। फिर से, pseudorandom numbers के कारण टकराव (collision) नगण्य है।
Storage slots को डिराइव (Derive) करना
यदि हम किसी string का keccak256 hash लेते हैं, तो आउटपुट मूल रूप से एक pseudorandom number होता है। परिणाम में से 1 घटाकर, हम एक ऐसा random number उत्पन्न करते हैं जिसका कोई ज्ञात hash preimage नहीं है, इसलिए ऐसा कोई तरीका नहीं है कि कोई कॉन्ट्रैक्ट keccak256 में कुछ डालकर ऐसा storage slot डिराइव (derive) कर सके जो उनके साथ क्लैश करे।
Storage slot के उपयोग के बारे में मान्यताएं
बेशक, implementation contracts लिखने वाले devs निम्नलिखित कोड के साथ जानबूझकर उन storage slots में लिख सकते हैं
assembly {
// implementation slot
sstore(
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, 0x00
)
}
यह proxy को शून्य पते (zero address) की ओर पॉइंट करके उसे तोड़ (break) देगा! यह मान्यता है कि devs ऐसा नहीं करेंगे।
EIP 1967 Etherscan के लिए यह बताना आसान बनाता है कि क्या वह proxy contract को देख रहा है
यहाँ Compound Finance के proxy contract का एक उदाहरण दिया गया है।

यह देखकर कि क्या किसी कॉन्ट्रैक्ट में ऊपर बताए गए slots में कोई गैर-शून्य (non-zero) वैल्यू है, block explorers बता सकते हैं कि कॉन्ट्रैक्ट एक proxy contract है या नहीं। ऊपर दिए गए स्क्रीनशॉट के बारे में कुछ अवलोकन (observations):
- बैंगनी घेरे (purple circle) में, हम देखते हैं कि Etherscan ने कॉन्ट्रैक्ट को EIP-1967 पैटर्न का पालन करने वाले के रूप में पहचाना है।
- नारंगी घेरे (orange circle) में, हम एक नोट देखते हैं कि implementation contract कहाँ है और पिछला वाला कहाँ था। block explorer केवल वर्तमान implementation slot को देखता है और इसके लिए पिछली वैल्यूज़ को भी याद रखता है।
- लाल घेरे (red circle) में, हम देखते हैं कि हमारे पास proxy या implementation से पढ़ने (reading) और लिखने (writing) के विकल्प हैं। आमतौर पर, हम proxy में पढ़ना या लिखना चाहते हैं क्योंकि वह कॉन्ट्रैक्ट की स्टेट (state) को होल्ड करता है।
Beacon slot क्या है?
यदि आप मूल EIP 1967 पढ़ते हैं, तो आप एक beacon slot का संदर्भ देखेंगे। Beacons का उपयोग व्यवहार में बहुत कम होता है, इसलिए हमने उनकी चर्चा को लेख के अंत तक टाल दिया।
Beacons किसी अन्य लेख का विषय हैं, लेकिन मूल रूप से, वे एक ही समय में कई proxies को अपडेट करने का एक तंत्र (mechanism) हैं। उदाहरण के लिए, हम कई proxies को एक ही implementation contract की ओर पॉइंट कर सकते हैं। चूंकि storage प्रत्येक proxy में व्यक्तिगत रूप से रखा जाता है, इसलिए proxies एक दूसरे के साथ हस्तक्षेप (interfere) नहीं करेंगे।
beacon contract बहुत सरल है: यह सिर्फ implementation contract का address लौटाता है:
interface IBeacon {
function implementation() external view returns (address);
}
Delegatecall करने से पहले प्रत्येक proxy, beacon से पूछता है कि वर्तमान implementation contract address क्या है। beacon में implementation() function की रिटर्न वैल्यू को बदलकर, सभी proxies को एक ही बार में अपडेट किया जा सकता है।
beacon storage slot 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50 है
और इसे bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) से प्राप्त (derive) किया गया है।
उन proxies के लिए जो beacons का उपयोग नहीं करते हैं, यहाँ वैल्यू address(0) स्टोर की जा सकती है (या slot को खाली छोड़ा जा सकता है)।
OpenZeppelin और Solady Implementation
OpenZeppelin के Transparent Upgradeable Proxy और UUPS contracts दोनों इस लेख में चर्चा किए गए variables को स्टोर करने के स्थान को परिभाषित करने के लिए ERC 1967 का उपयोग करते हैं।
Gas-efficient लाइब्रेरी Solady भी एक UUPS proxy implementation प्रदान करती है जो ERC 1967 का उपयोग करती है।
निष्कर्ष
ERC 1967 implementation contract, admin और beacon के लिए storage variables को रखने के स्थान का एक स्टैण्डर्ड (standard) है। यह block explorers को आसानी से पहचानने में सक्षम बनाता है कि कोई कॉन्ट्रैक्ट एक proxy है या नहीं और proxy और implementation के बीच storage clashes की संभावना को समाप्त करता है।
RareSkills के साथ और जानें
यह लेख हमारे एडवांस्ड Solidity Bootcamp का हिस्सा है। अधिक जानने के लिए कृपया प्रोग्राम देखें।
मूल रूप से 20 दिसंबर, 2023 को प्रकाशित