यहाँ से छवि: https://pixabay.com/photos/stormtrooper-star-wars-lego-storm-2899993/
परिचय
EIP-1167, जिसे मिनिमल प्रॉक्सी कॉन्ट्रैक्ट (minimal proxy contract) भी कहा जाता है, सस्ते में प्रॉक्सी क्लोन बनाने के लिए आमतौर पर इस्तेमाल किया जाने वाला एक solidity पैटर्न है।
यदि किसी यूज़ केस में एक समान कॉन्ट्रैक्ट (या बहुत मिलते-जुलते कॉन्ट्रैक्ट) को बार-बार डिप्लॉय करने की आवश्यकता होती है, तो ऐसा करने के लिए यह अधिक गैस एफिशिएंट (gas efficient) तरीका है।
उदाहरण के लिए, gnosis safe नया सेफ बनाते समय clone पैटर्न का उपयोग करता है। जब आप gnosis safe के साथ इंटरैक्ट करते हैं, तो आप वास्तव में इसके एक क्लोन के साथ इंटरैक्ट कर रहे होते हैं।
एक क्लोन कॉन्ट्रैक्ट उस प्रॉक्सी की तरह होता है जिसे अपग्रेड नहीं किया जा सकता। चूँकि इम्प्लीमेंटेशन कॉन्ट्रैक्ट (implementation contract) की तुलना में प्रॉक्सी बहुत छोटे होते हैं, इसलिए उन्हें डिप्लॉय करना सस्ता होता है।
प्रॉक्सी पैटर्न की तरह, क्लोन सभी कॉल्स को इम्प्लीमेंटेशन कॉन्ट्रैक्ट को डेलीगेट (delegate) करते हैं लेकिन स्टेट को अपने स्वयं के स्टोरेज (storage) में रखते हैं।
नियमित प्रॉक्सी पैटर्न के विपरीत, कई क्लोन एक ही इम्प्लीमेंटेशन कॉन्ट्रैक्ट को पॉइंट कर सकते हैं। क्लोन को अपग्रेड नहीं किया जा सकता है।
इम्प्लीमेंटेशन कॉन्ट्रैक्ट का एड्रेस bytecode में स्टोर होता है। स्टोरेज की तुलना में इससे गैस की बचत होती है और यह क्लोन को किसी अन्य इम्प्लीमेंटेशन को पॉइंट करने से रोकता है।
यह डिज़ाइन इसे डिप्लॉय करने में काफी सस्ता बनाता है क्योंकि क्लोन प्रॉक्सी का bytecode आमतौर पर इम्प्लीमेंटेशन कॉन्ट्रैक्ट के bytecode से बहुत छोटा होता है। वास्तव में, EIP-1167 केवल 55 बाइट्स (रनटाइम के लिए 45 बाइट्स) का होता है, जिसमें init code शामिल है। हालाँकि, निष्पादन (execution) के दौरान कॉल्स की लागत अधिक होगी, क्योंकि हमेशा एक अतिरिक्त delegatecall होता है।
यह लेख EIP और initializer फ़ंक्शन दोनों का वर्णन करेगा जिसका उपयोग कंस्ट्रक्टर पैरामीटर (constructor parameters) के समकक्ष को इनिशियलाइज़ करने के लिए किया जाता है।
लेखक
यह लेख RareSkills टेक्निकल राइटिंग प्रोग्राम के हिस्से के रूप में Jesse Raymond (LinkedIn, Twitter) द्वारा सह-लिखा गया था।
EIP-1167 कैसे काम करता है
एक सामान्य प्रॉक्सी के रूप में, यह एक कॉल के माध्यम से ट्रांज़ैक्शन डेटा प्राप्त करता है, इस डेटा को इम्प्लीमेंटेशन स्मार्ट कॉन्ट्रैक्ट पर फॉरवर्ड करता है, एक्सटर्नल कॉल (external call) का परिणाम प्राप्त करता है, और यदि एक्सटर्नल कॉल सफल रहा तो परिणाम लौटाता है या कोई त्रुटि (error) होने पर रिवर्ट (revert) कर देता है।
मिनिमल प्रॉक्सी का bytecode
मिनिमल प्रॉक्सी कॉन्ट्रैक्ट में केवल 55 बाइट्स का संक्षिप्त bytecode होता है। इस bytecode में शामिल हैं:
- init code
- रनटाइम कोड (runtime code) जिसमें ट्रांज़ैक्शन calldata प्राप्त करने के निर्देश शामिल हैं
- 20-बाइट का इम्प्लीमेंटेशन कॉन्ट्रैक्ट एड्रेस
- और एक delegatecall निष्पादित करने के लिए कमांड्स, और
- परिणाम लौटाने (return), या त्रुटि होने पर रिवर्ट (revert) ट्रिगर करने के निर्देश।
यहाँ मिनिमल प्रॉक्सी का bytecode है:
3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
डमी एड्रेस: 0xbebebebebebebebebebebebebebebebebebebebe को इम्प्लीमेंटेशन कॉन्ट्रैक्ट एड्रेस से बदल दिया जाता है।
आइए इसे विस्तार से समझते हैं।

Init code सेक्शन
Bytecode के पहले 10 बाइट्स में init code होता है जो एक बार चलता है और मिनिमल प्रॉक्सी को डिप्लॉय करने के लिए उपयोग किया जाता है।
स्मार्ट कॉन्ट्रैक्ट निर्माण और डिप्लॉयमेंट के बारे में अधिक जानने के लिए, Ethereum smart contract creation code पर हमारा लेख देखें।
यहाँ निम्नलिखित कमांड्स हैं जो EVM में निष्पादित किए जाते हैं।
// copy the runtime bytecode of the minimal proxy
// starting from offset 10, and save it to the blockchain
[00] RETURNDATASIZE
[01] PUSH1 2d
[03] DUP1
//push 10 - offset to copy runtime code from
[04] PUSH1 0a
[06] RETURNDATASIZE
// copy the runtime code and save it to the blockchain
[07] CODECOPY
[08] DUP2
[09] RETURN
calldata को कॉपी करना
Init code कॉन्ट्रैक्ट को डिप्लॉय करता है और रनटाइम bytecode को ऑन-चेन (on-chain) सेव करता है, जो ऑफसेट (offset) 10 (कॉपी calldata सेक्शन) से शुरू होकर bytecode के अंत तक होता है।
एक बार जब मिनिमल प्रॉक्सी डिप्लॉय हो जाता है और उस पर कॉल भेजी जाती है, तो यह ट्रांज़ैक्शन calldata को मेमोरी में कॉपी करता है, इम्प्लीमेंटेशन कॉन्ट्रैक्ट के 20 बाइट्स एड्रेस को पुश (push) करता है, और इम्प्लीमेंटेशन कॉन्ट्रैक्ट पर एक delegatecall निष्पादित करता है।
यह calldata कॉपी करने की प्रक्रिया निम्नलिखित opcodes के साथ की जाती है।
//copy the transaction calldata to memory
[0a] CALLDATASIZE
[0b] RETURNDATASIZE // this is a hack to push 0 onto the stack with less gas than doing PUSH 0
[0c] RETURNDATASIZE
[0d] CALLDATACOPY
[0e] RETURNDATASIZE
[0f] RETURNDATASIZE
[10] RETURNDATASIZE
[11] CALLDATASIZE
[12] RETURNDATASIZE
//pushes the 20 bytes address of the implementation contract
[13] PUSH20
इम्प्लीमेंटेशन कॉन्ट्रैक्ट एड्रेस
ट्रांज़ैक्शन calldata को मेमोरी में कॉपी करने के बाद, स्टैक (stack) को delegatecall करने के लिए तैयार किया जाता है और इम्प्लीमेंटेशन कॉन्ट्रैक्ट के 20 बाइट्स एड्रेस को स्टैक के शीर्ष (top) पर पुश कर दिया जाता है। पिछले सेक्शन में, हम देख सकते हैं कि यह PUSH20 के साथ समाप्त होता है। इसके बाद जो आता है वह इम्प्लीमेंटेशन कॉन्ट्रैक्ट का एड्रेस है।
//push the address of the implementation contract to the stack. The address here is just a dummy address
[13] PUSH20 bebebebebebebebebebebebebebebebebebebebe
delegatecall सेक्शन
ट्रांज़ैक्शन calldata को मेमोरी में कॉपी करने और स्टैक के शीर्ष पर इम्प्लीमेंटेशन कॉन्ट्रैक्ट एड्रेस प्राप्त करने के बाद, मिनिमल प्रॉक्सी इम्प्लीमेंटेशन कॉन्ट्रैक्ट पर delegatecall निष्पादित करने के लिए तैयार हो जाता है।
यदि आपको यह याद करने की आवश्यकता है कि delegatecall कैसे काम करता है, तो delegatecall पर हमारा ट्यूटोरियल पढ़ें।
delegatecall निष्पादित होने के बाद, यदि कॉल सफल रही तो मिनिमल प्रॉक्सी उसका परिणाम लौटाता है या कोई त्रुटि होने पर रिवर्ट कर देता है।
delegatecall सेक्शन में निम्नलिखित opcodes होते हैं।
//perform a delegate call on the implementation contract, and forward all available gas
[28] GAS
[29] DELEGATECALL
//copy the return data of the call to memory
[2a] RETURNDATASIZE
[2b] DUP3
[2c] DUP1
[2d] RETURNDATACOPY
// set up the stack for the conditional jump
[2e] SWAP1
[2f] RETURNDATASIZE
[30] SWAP2
[31] PUSH1 2b
//jump to line 35 and return the result of the call if it was successful, else revert on line 34
[33] JUMPI
[34] REVERT
[35] JUMPDEST
[36] RETURN
यह EIP-1167 का एक अवलोकन है और यह कैसे काम करता है।
कल्पना करें कि इम्प्लीमेंटेशन कॉन्ट्रैक्ट एक ERC20 टोकन है। उस स्थिति में, क्लोन बिल्कुल एक ERC20 टोकन की तरह व्यवहार करेगा।
इनिशियलाइज़ेशन के साथ EIP-1167 स्मार्ट कॉन्ट्रैक्ट इम्प्लीमेंटेशन
कुछ ऐसी स्थितियां होती हैं जहां हम क्लोन के निर्माण को पैरामीटराइज़ (parameterize) करना चाहते हैं। उदाहरण के लिए, यदि हम एक ERC20 टोकन को क्लोन कर रहे थे, तो हर क्लोन में समान totalSupply होगी, जो शायद वांछनीय (desirable) न हो।
इस पैरामीटर को कॉन्फ़िगर करने में सक्षम होने के लिए, “clone with initialization पैटर्न” का उपयोग किया जा सकता है।
आइए देखें कि एक इनिशियलाइज़ेशन फ़ंक्शन के साथ प्रॉक्सी क्लोन बनाने के लिए EIP-1167 का उपयोग कैसे किया जा सकता है। यह चरणों के एक सरल क्रम (sequence) का पालन करता है:
- एक इम्प्लीमेंटेशन कॉन्ट्रैक्ट बनाएँ
- EIP-1167 मानक के साथ कॉन्ट्रैक्ट को क्लोन करें
- क्लोन को डिप्लॉय करें और इनिशियलाइज़ेशन फ़ंक्शन को कॉल करें, जिसे केवल एक बार ही कॉल किया जा सकता है।
केवल एक बार कॉल करने का यह प्रतिबंध आवश्यक है, अन्यथा कोई व्यक्ति डिप्लॉयमेंट के बाद हमारे द्वारा सेट किए गए महत्वपूर्ण पैरामीटर को बदल सकता है, जैसे कि कुल सप्लाई (total supply) को बदलना।
आइए नीचे दिए गए एक उदाहरण के साथ इन चरणों को समझते हैं।
वह इम्प्लीमेंटेशन कॉन्ट्रैक्ट जिसे क्लोन किया जाना है
contractImplementationContract{
boolprivate isInitialized; //initializer function that will be called once, during
deployment.
functioninitializer() external {
require(!isInitialized);
isInitialized =true;
} // rest of the implementation functions go here
}
क्लोन को डिप्लॉय करने के लिए हम इस कोड का उपयोग करते हैं
contract MinimalProxyFactory {
address[] public proxies;
function deployClone(address _implementationContract) external returns (address) {
// convert the address to 20 bytes
bytes20 implementationContractInBytes = bytes20(_implementationContract);
//address to assign a cloned proxy
address proxy;
// as stated earlier, the minimal proxy has this bytecode
// <3d602d80600a3d3981f3363d3d373d3d3d363d73><address of implementation contract><5af43d82803e903d91602b57fd5bf3>
// <3d602d80600a3d3981f3> == creation code which copies runtime code into memory and deploys it
// <363d3d373d3d3d363d73> <address of implementation contract> <5af43d82803e903d91602b57fd5bf3> == runtime code that makes a delegatecall to the implementation contract
assembly {
/*
reads the 32 bytes of memory starting at the pointer stored in 0x40
In solidity, the 0x40 slot in memory is special: it contains the "free memory pointer"
which points to the end of the currently allocated memory.
*/
let clone := mload(0x40)
// store 32 bytes to memory starting at "clone"
mstore(
clone,
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
)
/*
| 20 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000
^
pointer
*/
// store 32 bytes to memory starting at "clone" + 20 bytes
// 0x14 = 20
mstore(add(clone, 0x14), implementationContractInBytes)
/*
| 20 bytes | 20 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe
^
pointer
*/
// store 32 bytes to memory starting at "clone" + 40 bytes
// 0x28 = 40
mstore(
add(clone, 0x28),
0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000
)
/*
| 20 bytes | 20 bytes | 15 bytes |
0x3d602d80600a3d3981f3363d3d373d3d3d363d73b<implementationContractInBytes>5af43d82803e903d91602b57fd5bf3 == 45 bytes in total
*/
// create a new contract
// send 0 Ether
// code starts at the pointer stored in "clone"
// code size == 0x37 (55 bytes)
proxy := create(0, clone, 0x37)
}
// Call initialization
ImplementationContract(proxy).initializer();
proxies.push(proxy);
return proxy;
}
}
MinimalProxyFactory कॉन्ट्रैक्ट के साथ, EIP-1167 क्लोन्स की एक अनंत मात्रा (infinite amount) को डिप्लॉय किया जा सकता है, लेकिन इस उदाहरण के लिए, हम ऊपर दिए गए इम्प्लीमेंटेशन कॉन्ट्रैक्ट को डिप्लॉय करेंगे।
यहाँ एक साधारण hardhat स्क्रिप्ट है जो कॉन्ट्रैक्ट्स को डिप्लॉय करती है और डिप्लॉय किए गए क्लोन के साथ इंटरैक्ट करती है।
const hre = require("hardhat");
async function main() {
const ImplementationContract = await hre.ethers.getContractFactory(
"ImplementationContract"
);
// deploy the implementation contract
const implementationContract = await ImplementationContract.deploy();
await implementationContract.deployed();
console.log("Implementation contract ", implementationContract.address);
const MinimalProxyFactory = await hre.ethers.getContractFactory(
"MinimalProxyFactory"
);
// deploy the minimal factory contract
const minimalProxyFactory = await MinimalProxyFactory.deploy();
await minimalProxyFactory.deployed();
console.log("Minimal proxy factory contract ", minimalProxyFactory.address);
// call the deploy clone function on the minimal factory contract and pass parameters
const deployCloneContract = await minimalProxyFactory.deployClone(
implementationContract.address
);
deployCloneContract.wait();
// get deployed proxy address
const ProxyAddress = await minimalProxyFactory.proxies(0);
console.log("Proxy contract ", ProxyAddress);
// load the clone
const proxy = await hre.ethers.getContractAt(
"ImplementationContract",
ProxyAddress
);
console.log("Proxy is initialized == ", await proxy.isInitialized()); // get initialized boolean == true
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
अब हमने goerli नेटवर्क पर अपने कॉन्ट्रैक्ट्स डिप्लॉय कर दिए हैं और यहाँ 3 कॉन्ट्रैक्ट्स के ट्रांज़ैक्शन विवरण हैं।
ध्यान दें कि Etherscan पहचानता है कि प्रॉक्सी कॉन्ट्रैक्ट केवल एक और स्मार्ट कॉन्ट्रैक्ट नहीं है, बल्कि इम्प्लीमेंटेशन कॉन्ट्रैक्ट को कॉल डेलीगेट (delegate) करता है।
सुविधा के लिए, हमारा कोड डिप्लॉय किए गए क्लोन्स की एक सूची (list) रखता है, लेकिन यह एक आवश्यक विशेषता (feature) नहीं है।
निष्कर्ष
EIP-1167 मिनिमल प्रॉक्सी स्टैंडर्ड (minimal proxy standard) उन कॉन्ट्रैक्ट्स को डिप्लॉय करने का एक कुशल (efficient) तरीका है जो किसी अन्य के इम्प्लीमेंटेशन की नकल (mirror) करते हैं। initializer पैटर्न हमें क्लोन को इस तरह डिप्लॉय करने की अनुमति देता है जैसे कि इसमें एक कंस्ट्रक्टर (constructor) हो जो आर्गुमेंट्स (arguments) लेता है।
इस पैटर्न की लागत (cost) यह है कि प्रत्येक निष्पादन (execution) में एक delegatecall का ओवरहेड (overhead) होता है।
और जानें
हमारे व्यापक पाठ्यक्रम को देखने के लिए हमारा एडवांस blockchain bootcamp देखें।
मूल रूप से 21 फरवरी, 2023 को प्रकाशित