در زبان برنامه نویسی سالیدیتی، قراردادها می توانند به دو روش با یکدیگر ارتباط برقرار کنند. روش اول استفاده از رابط قرارداد (interface) است که به آن فراخوانی سطح بالا (High-Level Call) می گویند. روش دوم استفاده از تابع call
است که یک فراخوانی سطح پایین (Low-Level Call) محسوب می شود. در این مقاله، تفاوت فراخوانی سطح پایین و سطح بالا در سالیدیتی را بررسی می کنیم تا درک بهتری از رفتار این دو روش در زمان اجرا و مواجهه با خطا به دست آوریم.
هرچند هر دو روش در نهایت از دستور اجرایی CALL
در ماشین مجازی اتریوم استفاده می کنند، اما سالیدیتی آنها را به شکل متفاوتی مدیریت می کند.
در این مقاله، خواهیم دید که چرا فراخوانی سطح پایین هیچ وقت به طور خودکار باعث برگشت نمی شود، اما فراخوانی سطح بالا ممکن است عملیات را متوقف کند. همچنین بررسی می کنیم که چرا فراخوانی سطح پایین با یک آدرس خالی موفق محسوب می شود، ولی فراخوانی سطح بالا با یک قرارداد ناموجود باعث خطا می شود.
چرا فراخوانی سطح پایین (یا delegatecall) باعث برگشت نمی شود، اما فراخوانی از طریق رابط قرارداد ممکن است برگشت داشته باشد؟
پیش از آنکه علت را توضیح بدهیم، بهتر است بخشی از مستندات رسمی سالیدیتی را نقل کنیم که مستقیماً به این موضوع اشاره دارد:
زمانی که یک استثناء (Exception) در فراخوانی یک زیرقرارداد رخ می دهد، به صورت خودکار “به سطح بالا منتقل می شود” (یعنی خطا دوباره پرتاب می شود)، مگر اینکه در یک بلوک try/catch مدیریت شده باشد.
اما یک استثناء در توابع سطح پایین مانندcall
،delegatecall
وstaticcall
این قاعده را دنبال نمی کند. این توابع به جای پرتاب خطا، مقدارfalse
را به عنوان اولین خروجی برمی گردانند.
برای درک بهتر این تفاوت، در ادامه رفتار دو نوع فراخوانی را با هم مقایسه می کنیم: یکی از طریق رابط قرارداد (فراخوانی سطح بالا) و دیگری از طریق تابع call
(فراخوانی سطح پایین). در این مثال، از متد call
استفاده می کنیم، اما همین اصول در مورد delegatecall
نیز صدق می کند.
قرارداد Caller
می تواند تابع ops()
را از قرارداد Called
به دو روش مختلف صدا بزند. دقت کنید که تابع ops()
همیشه عملیات را با خطا متوقف می کند (revert
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
pragma solidity ^0.8.0; contract Caller { // first call to ops() function callByCall(address _address) public returns (bool success) { (success, ) = _address.call(abi.encodeWithSignature("ops()")); } // second call to ops() function callByInterface(address _address) public { Called called = Called(_address); called.ops(); } } contract Called { // ops() always reverts function ops() public { revert(); } } |
با وجود اینکه هر دو روش برای فراخوانی یک تابع مشخص به کار می روند و هر دو از دستور اجرایی CALL
در سطح ماشین مجازی اتریوم (EVM) استفاده می کنند، کامپایلر سالیدیتی در زمان تولید بایت کد، نحوه مدیریت خطاها را به شیوه متفاوتی پیاده سازی می کند.
اگر هر دو تابع callByInterface
و callByCall
را در قرارداد Caller
اجرا کنیم، متوجه می شویم که اجرای callByInterface
باعث برگشت (revert) می شود، در حالی که callByCall
بدون برگشت عمل می کند.
در سطح ماشین مجازی اتریوم، دستور CALL
تنها یک مقدار بولی (true یا false) باز می گرداند که موفقیت یا شکست فراخوانی را مشخص می کند و این مقدار را روی پشته قرار می دهد. خود این دستور، باعث برگشت عملیات نمی شود.
زمانی که فراخوانی از طریق رابط قرارداد انجام می شود، سالیدیتی به صورت خودکار مقدار برگشتی را بررسی می کند. اگر مقدار برگشتی false
باشد، زبان سالیدیتی یک برگشت صریح (revert) ایجاد می کند؛ مگر اینکه فراخوانی داخل یک بلوک try/catch
انجام شده باشد. این رفتار یکی از نکات کلیدی در برنامه نویسی قراردادهای هوشمند است، زیرا درک درست از نحوه مدیریت خطاها میتواند از بسیاری از باگهای رایج جلوگیری کند.
اما در فراخوانی های سطح پایین، مدیریت این مقدار به عهده برنامه نویس است. در این حالت، ما باید مقدار بولی بازگشتی را به صورت دستی بررسی کنیم و در صورت نیاز، خودمان دستور revert
را اجرا کنیم.
1 2 3 4 5 6 7 8 9 10 11 |
contract Caller { //... function callByCall(address address) public returns (bool success) { (success, ) = address.call(abi.encodeWithSignature("ops()")); if (!success) { revert("Something went wront"); } } //... } |
تفاوت فراخوانی مستقیم (call) و فراخوانی از طریق رابط (interface) هنگام فراخوانی با یک آدرس خالی
در سالیدیتی، متد سطح پایین call
پیش از اجرای عملیات، بررسی نمی کند که آیا آدرس مورد نظر واقعاً به یک قرارداد هوشمند اشاره دارد یا خیر. البته در صورت نیاز، خود قرارداد می تواند با استفاده از دستور EXTCODESIZE
(که پشت صحنه ویژگی address.code.length
قرار دارد) بررسی کند که آیا در آن آدرس، کدی مستقر شده یا نه. اگر اندازه کد برابر صفر باشد، مشخص می شود که در آن آدرس هیچ قرارداد هوشمندی مستقر نیست.
اما متد call
این بررسی را انجام نمی دهد و صرف نظر از اینکه آدرس مقصد معتبر باشد یا نه، مستقیماً دستور CALL
را اجرا می کند.
در مقابل، زمانی که از رابط قرارداد برای فراخوانی استفاده می کنیم، سالیدیتی قبل از اجرای CALL
اندازه کد آدرس مقصد را بررسی می کند. در بایت کدی که برای تابع callByInterface
تولید می شود، ابتدا دستور EXTCODESIZE
روی آدرس مورد نظر اجرا می شود. اگر این دستور مقدار صفر برگرداند (یعنی هیچ قراردادی در آن آدرس وجود ندارد)، عملیات پیش از اجرای CALL
متوقف می شود و تابع به صورت خودکار revert
می دهد.
به همین دلیل است که وقتی تابع callByInterface
با یک آدرس ناموجود اجرا می شود، عملیات با خطا مواجه می شود. اما در همان شرایط، اجرای callByCall
به ظاهر موفق انجام می شود، چون بررسی وجود قرارداد در آن انجام نمی شود.
در تصویر زیر، تفاوت نحوه برخورد هر دو روش هنگام فراخوانی با یک آدرس خالی به صورت شماتیک نمایش داده شده است:
در اصل، ماشین مجازی اتریوم (EVM) یک عملیات را زمانی متوقف می کند (revert)، که یکی از شرایط زیر رخ بدهد:
-
اجرای دستور
REVERT
-
تمام شدن گاز (Gas)
-
انجام عملیاتی که ممنوع است، مانند تقسیم عددی بر صفر
اما وقتی که تماس به یک آدرس خالی انجام می شود، هیچکدام از این شرایط اتفاق نمی افتند. چون در آن آدرس، هیچ کدی برای اجرا وجود ندارد، دستوری مانند REVERT
اجرا نمی شود، مصرف گاز به حد بحرانی نمیرسد و هیچ عملیات محاسباتی ممنوعهای رخ نمیدهد.
به همین دلیل است که تماس سطح پایین (call
) به یک آدرس خالی، حتی اگر هیچ اثر واقعی نداشته باشد، از نظر ماشین مجازی اتریوم «موفق» تلقی می شود و برگشت نمی دهد.
راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.
- انتشار: ۶ تیر ۱۴۰۴
دسته بندی موضوعات
- آموزش ارز دیجیتال
- آموزش برنامه نویسی
- آموزش متنی برنامه نویسی
- اطلاعیه و سایر مطالب
- پروژه برنامه نویسی
- دوره های تخصصی برنامه نویسی
- رپورتاژ
- فیلم های آموزشی
- ++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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس