function selector در سالیدیتی، یک شناسه ۴ بایتی است که در پشت صحنه برای شناسایی توابع مورد استفاده قرار میگیرد.
در واقع، این شناسه مشخص میکند که هنگام ارسال یک تراکنش، کدام تابع از قرارداد هوشمند باید فراخوانی شود.
شما میتوانید این شناسه ۴ بایتی را با استفاده از متد .selector
مشاهده کنید:
1 2 3 4 5 6 7 8 9 10 |
pragma solidity 0.8.25; contract SelectorTest{ function foo() public {} function getSelectorOfFoo() external pure returns (bytes4) { return this.foo.selector; // 0xc2985578 } } |
call
و تنها با ارسال ۴ بایت شناسه تابع (function selector) بهعنوان داده (data) به قرارداد، اجرا کرد.
در مثال زیر، قرارداد CallFoo
تابع foo
را که در قرارداد FooContract
تعریف شده، با استفاده از دستور call
و ارسال شناسه ۴ بایتی مربوط به آن تابع به قرارداد FooContract
اجرا میکند.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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
مستقر شده باشند و تابع callFooLowLevel()
با آدرس FooContract
اجرا شود، مقدار متغیر x
درون FooContract
برابر با ۱ خواهد شد. این نشان میدهد که تابع foo
با موفقیت از طریق دستور call
فراخوانی شده است.
شناسایی تابع و شناسه آن در سالیدیتی با استفاده از msg.sig
در سالیدیتی، متغیر سراسری msg.sig
چهار بایت اول داده تراکنش (transaction data) را بازمیگرداند؛ این همان شناسهای است که قرارداد از آن برای تشخیص اینکه کدام تابع باید اجرا شود استفاده میکند.
نکته مهم این است که مقدار msg.sig
در طول اجرای تراکنش ثابت باقی میماند و بسته به اینکه در حال حاضر در کدام تابع هستیم، تغییر نمیکند. به عبارت دیگر، حتی اگر یک تابع عمومی (public) در حین اجرا تابع عمومی دیگری را فراخوانی کند، مقدار msg.sig
همچنان مربوط به تابع اولیهای خواهد بود که در ابتدا از بیرون فراخوانی شده است.
در کد زیر، تابع foo
تابع bar
را فراخوانی میکند تا مقدار msg.sig
را دریافت کند؛ اما در زمان اجرای foo
، مقدار برگشتی برابر با selector تابع foo
خواهد بود، نه bar
:
میتوانید این کد را در محیط Remix تست کنید:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
//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; } } |
امضای تابع در سالیدیتی (Function Signature)
در سالیدیتی، امضای تابع (Function Signature) رشتهای متنی است که شامل نام تابع و نوع آرگومانهای آن است—اما نام متغیرها در آن لحاظ نمیشود.
برای مثال، در قطعه کد زیر، سمت چپ تعریف تابع است و سمت راست امضای معادل آن:
1 2 3 |
function setPoint(uint256 x, uint256 y) --> "setPoint(uint256,uint256)" function setName(string memory name) --> "setName(string)" function addValue(uint v) --> "addValue(uint256)" |
- در امضای تابع هیچ فاصلهای وجود ندارد.
- تمام نوعهای عدد صحیح (uint) باید بهصورت دقیق با اندازه مشخص شوند؛ مثل uint256، uint40، uint8 و غیره.
- نوع حافظه مانند memory یا calldata در امضا درج نمیشود.
بنابراین، رشتهای مثل “getBalanceById(uint)” یک امضای نامعتبر است، چون اندازه نوع داده مشخص نشده است.
نحوه محاسبه Function Selector از روی Function Signature در سالیدیتی
Function Selector در سالیدیتی از چهار بایت اول هش keccak256
امضای تابع (function signature) به دست میآید.
مثال زیر تابعی را نشان میدهد که از طریق ورودی رشتهای (یعنی همان امضای تابع)، شناسه تابع را محاسبه میکند:
1 2 3 4 5 6 |
function returnFunctionSelectorFromSignature( string calldata functionName ) public pure returns(bytes4) { bytes4 functionSelector = bytes4(keccak256(abi.encodePacked(functionName))); return(functionSelector); } |
"foo()"
باشد، مقدار بازگشتی برابر خواهد بود با: 0xc2985578
این شناسه دقیقاً همان ۴ بایت ابتدایی هش keccak256
از رشته "foo()"
است و توسط سالیدیتی برای شناسایی توابع در زمان اجرای تراکنشها استفاده میشود.
کد زیر نیز همین منطق را پیادهسازی میکند و به شما اجازه میدهد از روی امضای تابع، شناسه مربوط به آن را بهدست آورید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//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; } } |
f(IERC20 token)
، از نظر کدگذاری ABI، معادل f(address token)
در نظر گرفته میشود. همچنین، اگر یک آدرس را به حالت payable
تبدیل کنیم، این تغییر هیچ اثری بر امضای تابع (function signature) ندارد.
توابع داخلی (Internal) فاقد Function Selector هستند
توابع public و external دارای selector هستند، اما توابع internal و private این ویژگی را ندارند. کد زیر کامپایل نمیشود:
1 2 3 4 5 6 7 8 9 10 |
contract Foo { function bar() internal { } // does not compile function barSelector() external pure returns (bytes4) { return this.bar.selector; } } |
اگر سطح دسترسی تابع bar
به public
یا external
تغییر داده شود، این کد کامپایل خواهد شد.
توابع internal نیازی به function selector ندارند، زیرا function selector برای استفاده توسط قراردادهای خارجی طراحی شده است. بهعبارت دیگر، function selector مشخص میکند که یک کاربر خارجی قصد فراخوانی کدام تابع public یا external را دارد.
چرا به جای نام تابع از Function Selector در سالیدیتی استفاده میکنیم؟
نام توابع در سالیدیتی میتواند بهصورت دلخواه و با هر طولی تعریف شود. اما اگر نام تابع طولانی باشد، این موضوع باعث افزایش اندازه و هزینه تراکنش خواهد شد، چرا که دادههای بیشتری باید در تراکنش گنجانده شوند.
استفاده از function selector (که تنها ۴ بایت است) بهجای نام کامل تابع، باعث میشود تراکنش ها کوچکتر و کمهزینهتر باشند. به همین دلیل، در سطح پایین، انتخاب تابع در قراردادهای سالیدیتی از طریق selector انجام میشود، نه نام.
آیا تابع fallback دارای function selector است؟
خیر، تابع fallback هیچ function selector ندارد.
اگر درون تابع fallback مقدار msg.sig
را ثبت (log) کنید، این مقدار تنها چهار بایت اول داده تراکنش (transaction data) را نشان خواهد داد. اگر تراکنش هیچ دادهای نداشته باشد، msg.sig
مقدار 0x00000000
را بازمیگرداند.
اما این به این معنا نیست که function selector تابع fallback برابر با صفر است؛ بلکه فقط به این معنی است که msg.sig
تلاش کرده دادهای را بخواند که وجود نداشته است.
1 2 3 4 5 6 7 8 |
contract FunctionSignatureTest { event LogSelector(bytes4); fallback() external payable { emit LogSelector(msg.sig); } } |
احتمال برخورد (Collision) در Function Selector
یک function selector میتواند تا حداکثر 2**32 – 1 مقدار ممکن را در خود نگه دارد (تقریباً ۴.۲ میلیارد مقدار). به همین دلیل، احتمال اینکه دو تابع دارای selector یکسان باشند، کم اما واقعی است.
1 2 3 4 5 6 |
contract WontCompile { function collate_propagate_storage(bytes16 x) external {} function burn(uint256 amount) external {} } |
0x42966c68
هستند.
function selector و ماشین مجازی اتریوم (EVM)
برای توسعهدهندگانی که با EVM آشنایی دارند، ممکن است یک سوءتفاهم رایج وجود داشته باشد:
شناسه تابع ۴ بایتی یک ویژگی مربوط به سطح زبان سالیدیتی است، نه بخشی از مشخصات رسمی EVM.
در واقع، در مستندات اتریوم هیچ الزامی وجود ندارد که توابع حتماً با ۴ بایت شناسایی شوند. این تنها یک رویه رایج در سالیدیتی و زبانهای مشابه است، نه یک الزام پروتکلی.
بهینهسازی رایج در لایه دومها:
در برخی از اپلیکیشنهای لایه دوم، برای کاهش مصرف گس، تنها از ۱ بایت برای شناسه تابع استفاده میشود. در این روش:
-
همه فراخوانیها به تابع
fallback
هدایت میشوند. -
سپس، تابع
fallback
با بررسی اولین بایت داده تراکنش، تشخیص میدهد که کدام تابع باید اجرا شود.
این تکنیک باعث صرفهجویی در هزینهها و کوچکتر شدن اندازه تراکنش ها در شبکه هایی با محدودیت منابع میشود.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۲۳ اردیبهشت ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس