Function selector एक 4-byte id है जिसका उपयोग Solidity आंतरिक रूप से (under the hood) functions को पहचानने के लिए करता है।
Function selector के माध्यम से ही एक Solidity contract यह जान पाता है कि आप transaction में किस function को कॉल करने का प्रयास कर रहे हैं।
आप .selector method का उपयोग करके इस 4-byte id को देख सकते हैं:
pragma solidity 0.8.25;
contract SelectorTest{
function foo() public {}
function getSelectorOfFoo() external pure returns (bytes4) {
return this.foo.selector; // 0xc2985578
}
}
यदि कोई function कोई argument नहीं लेता है, तो आप उन 4 bytes (selector) को डेटा के रूप में contract को एक low level call के साथ भेजकर उस function को invoke कर सकते हैं।
निम्नलिखित उदाहरण में, CallFoo contract उपयुक्त 4-byte function selector के साथ FooContract को कॉल करके, FooContract में परिभाषित foo function को कॉल करता है:
pragma solidity 0.8.25;
contract CallFoo {
function callFooLowLevel(address _contract) external {
bytes4 fooSelector = 0xc2985578;
(bool ok, ) = _contract.call(abi.encodePacked(fooSelector));
require(ok, "call failed");
}
}
contract FooContract {
uint256 public x;
function foo() public {
x = 1;
}
}
यदि FooContract और CallFoo deploy किए गए हैं, और callFooLowLevel() function को FooContract के address के साथ execute किया जाता है, तो FooContract के भीतर x का मान 1 हो जाएगा। यह foo function के लिए एक सफल कॉल को इंगित करता है।
msg.sig के साथ Solidity में function calls और selector की पहचान करना
msg.sig एक global variable है जो transaction डेटा के पहले चार बाइट्स लौटाता है, जिससे Solidity contract को पता चलता है कि किस function को invoke करना है।
msg.sig पूरे transaction के दौरान संरक्षित रहता है, यह इस बात पर निर्भर नहीं करता कि वर्तमान में कौन सा function सक्रिय है। इसलिए, भले ही कोई public function कॉल के दौरान किसी अन्य public function को कॉल करता हो, msg.sig उस function का selector होगा जिसे शुरू में कॉल किया गया था।
नीचे दिए गए कोड में, foo msg.sig प्राप्त करने के लिए bar को कॉल करता है, लेकिन foo को कॉल करने पर जो selector वापस आता है, वह foo का function selector होता है, न कि bar का:
आप कोड को Remix में यहाँ टेस्ट कर सकते हैं:
//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
contract SelectorTest {
// returns function selector of `bar()` 0xfebb0f7e
function bar() public pure returns (bytes4) {
return msg.sig;
}
// returns function selector of `foo()` 0xc2985578
function foo() public pure returns (bytes4) {
return bar();
}
function testSelectors() external pure returns (bool) {
assert(this.foo.selector == 0xc2985578);
assert(this.bar.selector == 0xfebb0f7e);
return true;
}
}
Solidity function signature
Solidity में function signature एक string होती है जिसमें function का नाम और उसके बाद उसके द्वारा स्वीकार किए जाने वाले arguments के types होते हैं। Arguments से variable के नाम हटा दिए जाते हैं।
निम्नलिखित स्निपेट में, function बाईं ओर है, और function signature दाईं ओर है:
function setPoint(uint256 x, uint256 y) --> "setPoint(uint256,uint256)"
function setName(string memory name) --> "setName(string)"
function addValue(uint v) --> "addValue(uint256)"
Function selector में कोई स्पेस नहीं होता है। सभी uint types में उनका आकार स्पष्ट रूप से शामिल होना चाहिए (uint256, uint40, uint8, आदि)। calldata और memory types शामिल नहीं किए जाते हैं। उदाहरण के लिए, getBalanceById(uint) एक अमान्य signature है।
Function signature से function selector की गणना कैसे की जाती है
Function selector function signature के keccak256 हैश के पहले चार बाइट्स होते हैं।
function returnFunctionSelectorFromSignature(
string calldata functionName
) public pure returns(bytes4) {
bytes4 functionSelector = bytes4(keccak256(abi.encodePacked(functionName)));
return(functionSelector);
}
ऊपर दिए गए function में “foo()” डालने पर 0xc2985578 प्राप्त होगा।
नीचे दिया गया कोड function signature दिए जाने पर function selector की गणना को प्रदर्शित करता है।
//SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
contract FunctionSignatureTest {
function foo() external {}
function point(uint256 x, uint256 y) external {}
function setName(string memory name) external {}
function testSignatures() external pure returns (bool) {
// NOTE: Casting to bytes4 takes the first 4 bytes
// and removes the rest
assert(bytes4(keccak256("foo()")) == this.foo.selector);
assert(bytes4(keccak256("point(uint256,uint256)")) == this.point.selector);
assert(bytes4(keccak256("setName(string)")) == this.setName.selector);
return true;
}
}
Interfaces को addresses के रूप में encode किया जाता है। उदाहरण के लिए f(IERC20 token) को f(address token) के रूप में encode किया जाता है। किसी address को payable बनाने से signature में कोई बदलाव नहीं होता है।
Internal functions में function selector नहीं होता है
Public और external functions में एक selector होता है लेकिन internal और private functions में नहीं। निम्नलिखित कोड compile नहीं होता है:
contract Foo {
function bar() internal {
}
// does not compile
function barSelector() external pure returns (bytes4) {
return this.bar.selector;
}
}
यदि bar को public या external में बदल दिया जाता है, तो कोड compile हो जाएगा।
Internal functions को function selectors की आवश्यकता नहीं होती है क्योंकि function selectors बाहरी contracts द्वारा उपयोग के लिए होते हैं। यानी, function selector वह तरीका है जिससे एक बाहरी उपयोगकर्ता यह निर्दिष्ट करता है कि वे किस public या external function को कॉल करने का प्रयास कर रहे हैं।
Function के नाम के बजाय function selectors का उपयोग क्यों करें?
Solidity function के नाम मनमाने ढंग से लंबे हो सकते हैं, और यदि function का नाम लंबा है, तो यह transaction के आकार और लागत को बढ़ा देगा। यदि नाम के बजाय function selector प्रदान किया जाता है तो Transactions आम तौर पर आकार में छोटे होते हैं।
क्या fallback function में function selector होता है?
fallback function में function selector नहीं होता है।
यदि msg.sig को fallback function के अंदर लॉग किया जाता है, तो यह केवल transaction डेटा के पहले चार बाइट्स को लॉग करेगा। यदि कोई transaction डेटा नहीं है, तो msg.sig शून्य के चार बाइट्स लौटाएगा। इसका मतलब यह नहीं है कि fallback का function selector पूरी तरह से शून्य है, इसका मतलब है कि msg.sig ने उस डेटा को पढ़ने की कोशिश की जो वहां मौजूद नहीं था।
contract FunctionSignatureTest {
event LogSelector(bytes4);
fallback() external payable {
emit LogSelector(msg.sig);
}
}
ऊपर दिए गए contract को टेस्ट करने के लिए कोड Remix पर यहाँ उपलब्ध है। नीचे एक वीडियो है जो दिखाता है कि Remix पर fallback function को कैसे ट्रिगर किया जाए:
Function selector टकराव (collision) की संभावना
एक function selector में 2**32 - 1 (लगभग 4.2 बिलियन) तक संभावित मान हो सकते हैं। इस प्रकार, दो functions का एक ही selector होने की संभावना बहुत कम लेकिन यथार्थवादी है।
contract WontCompile {
function collate_propagate_storage(bytes16 x) external {}
function burn(uint256 amount) external {}
}
ऊपर दिया गया उदाहरण इस forum post से लिया गया था। इन दोनों functions का function selector 0x42966c68 है।
Function selectors के लिए उपयोगी संसाधन
Function selector calculator। कीवर्ड function जोड़ना सुनिश्चित करें। यह कैलकुलेटर function burn(uint256) जैसे प्रारूप की अपेक्षा करता है।
Function selector database। सर्च में 4 bytes डालें और यह उन ज्ञात functions को दिखाएगा जो इससे मेल खाते हैं।
Function selectors और EVM — Ethereum Virtual Machine
EVM से परिचित पाठकों के लिए, भ्रम का एक सामान्य कारण होता है। चार बाइट function selector Solidity एप्लिकेशन स्तर पर होता है, न कि EVM स्तर पर। Ethereum के specification में ऐसा कुछ भी नहीं कहा गया है कि functions को 4 बाइट्स के साथ पहचाना जाना चाहिए। यह एक स्थापित परंपरा (strong convention) है, लेकिन यह अनिवार्य नहीं है।
वास्तव में, layer 2 applications के लिए एक सामान्य optimization function को 1-byte function selector के साथ पहचानना है। यानी, सभी function कॉल्स fallback function में जाते हैं, फिर fallback function यह निर्धारित करने के लिए transaction में पहले बाइट को देखता है कि किस function को invoke करना है।
RareSkills के साथ और जानें
Smart contract डेवलपमेंट में अधिक उन्नत विषयों के लिए हमारा Solidity bootcamp देखें।
मूल रूप से 30 मार्च, 2024 को प्रकाशित