در زبان برنامه نویسی سالیدیتی، قراردادها می توانند به دو روش با یکدیگر ارتباط برقرار کنند. روش اول استفاده از رابط قرارداد (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
- اچ تی ام ال
- بانک اطلاعاتی
- برنامه نویسی سوکت
- برنامه نویسی موبایل
- پاسکال
- پایان نامه
- پایتون
- جاوا
- جاوا اسکریپت
- جی کوئری
- داده کاوی
- دلفی
- رباتیک
- سئو
- سایر کتاب ها
- سخت افزار
- سی اس اس
- سی پلاس پلاس
- سی شارپ
- طراحی الگوریتم
- فتوشاپ
- مقاله
- مهندسی نرم افزار
- هک و امنیت
- هوش مصنوعی
- ویژوال بیسیک
- نرم افزار و ابزار برنامه نویسی
- وردپرس













